Need advise, how to fix creating multiple nested records in rails.
My code:
Models
class Category < ActiveRecord::Base
has_many :fcatalogs
has_many :features, :through => :fcatalogs
accepts_nested_attributes_for :fcatalogs
end
class Feature < ActiveRecord::Base
has_many :fcatalogs
has_many :categories, :through => :fcatalogs
accepts_nested_attributes_for :fcatalogs
end
class Fcatalog < ActiveRecord::Base
self.table_name = 'categories_features'
belongs_to :category
belongs_to :feature
end
In controller
def new
#category = Category.new
#category.fcatalogs.build
end
def create
#category = Category.new(category_params)
respond_to do |format|
if #category.save
format.html { redirect_to [:admin, #category], notice: category_params } #'Category was successfully created.'
format.json { render action: 'show', status: :created, location: #category }
else
format.html { render action: 'new' }
format.json { render json: #category.errors, status: :unprocessable_entity }
end
end
end
# Never trust parameters from the scary internet, only allow the white list through.
def category_params
params.require(:category).permit(:parent_id, :name, :description, :url, :image, :position, :visible, :meta_title, :meta_keywords, :meta_description,
:fcatalogs_attributes: [:feature_id])
end
In view
<%= f.fields_for :fcatalogs do |builder| %>
<%= builder.label :feature_id, 'Feature' %>
<%= builder.collection_select(:feature_id, Feature.all, :id, :name, {}, {:multiple => true, :size => 5}) %>
<% end %>
If i remove :multiple => true, :size =>5 condition, single nested record successfully creates,
but with :multiple it fails with error: Unpermited param feature_id
If someone else stumbles upon this:
I think you should change your params to :feature_ids, since you're looking for multiple records. Change it in the controller AND in the view!
Related
I have a question.
I have this model:
class Project < ApplicationRecord
has_many :documents
belongs_to :course_unit
belongs_to :user
has_and_belongs_to_many :people
has_one :presentation
has_and_belongs_to_many :supervisors, :class_name => "Person", :join_table => :projects_supervisors
end
and this model:
class Presentation < ApplicationRecord
belongs_to :project
has_and_belongs_to_many :juries, :class_name => "Person", :join_table => :juries_presentations
end
When I create a new project, I have many attributes of the model Project and two attributes (room and date) from Presentation model, so I don't know how to send data from room and date attributes to the presentation model.
So my question is: How can I create a new project that saves data in project table and presentation table?
UPDATE #1
My project controller:
def new
#project = Project.new
end
def edit
end
def create
#project = Project.new(project_params)
#project.build_presentation
respond_to do |format|
if #project.save
format.html { redirect_to #project, notice: 'Project was successfully created.' }
format.json { render :show, status: :created, location: #project }
else
format.html { render :new }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #project.update(project_params)
format.html { redirect_to #project, notice: 'Project was successfully updated.'}
format.json { render :show, status: :ok, location: #project }
else
format.html { render :edit }
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
private
def set_project
#project = Project.find(params[:id])
end
def project_params
params.require(:project).permit(:title, :resume, :github, :grade, :project_url, :date, :featured, :finished, :user_id, :course_unit_id, presentation_attributes: [ :date , :room ])
end
My index view for Projects is:
<%= form_for #project do |f| %>
<%= f.fields_for :presentations do |ff| %>
<%= ff.label :"Dia de Apresentação" %>
<%= ff.date_field :date %>
<%= ff.label :"Sala de Apresentação" %>
<%= ff.text_area :room %>
<% end
<%= f.submit %>
<% end %>
You can try something like this:
project = Project.new(name: 'project 1')
project.build_presentation(room: 'room 1', date: Time.current)
project.save
It will save project with name project 1 and presentation belongs to that project, with room room 1 and date is Time.current.
And you need to update your models to avoid presence validation.
class Project < ApplicationRecord
has_one :presentation, inverse_of: :project
end
class Presentation < ApplicationRecord
belongs_to :project, inverse_of: :presentation
end
I have a nested form question.erb that displays the pre written question (:poll) and then has text_fields (:response) for the answers. The problem that I am running into is that blank questions are being created in the database. I have done a fair bit of research on the issue and it appears that the answer would be adding
accepts_nested_attributes_for :questions, reject_if: proc { |attributes| attributes['poll'].blank? }
to the event model. The issue with this is of course is now I am preventing everything from being written. How would I go about only writing the answers to the database and ignoring (or not creating at all) the blank questions? Please let me know if any of the other files might be needed for the solution.
Thanks!
question.erb
<%= simple_form_for(#event) do |f| %>
<%= f.error_notification %>
<%= f.object.name %>
<%= f.simple_fields_for :questions, #event.questions do |q| %>
<%= q.object.poll%>
<%= q.simple_fields_for :answers, q.object.answers.build do |a|%>
<%= a.text_field :response %>
<% end %>
<% end %>
<%= f.button :submit%>
<% end %>
event.rb
class Event < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions
end
events_controller.rb (selected portions)
def question
#event = Event.find(params[:id])
end
def update
#event = Event.find(params[:id])
if #event.update(event_params)
redirect_to events_path, notice: "Answers saved"
else
redirect_to events_question_path, notice: "Answers not saved"
end
end
def event_params
params.require(:event).permit(
questions_attributes: [:poll, answers_attributes: [:response]])
end
question.rb
class Question < ActiveRecord::Base
belongs_to :user
belongs_to :event
has_many :answers
accepts_nested_attributes_for :answers
end
question_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: [:show, :edit, :update, :destroy]
def index
#questions = Question.all
end
def show
end
def new
#question = Question.new
end
def edit
end
def create
#question = Question.new(question_params)
#question.user = current_user
respond_to do |format|
if #question.save
format.html { redirect_to #question, notice: 'Question was successfully created.' }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
def update
#question.user = current_user.id
respond_to do |format|
if #question.update(question_params)
format.html { redirect_to #question, notice: 'Question was successfully updated.' }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1
# DELETE /questions/1.json
def destroy
#question.destroy
respond_to do |format|
format.html { redirect_to questions_url }
format.json { head :no_content }
end
end
private
def set_question
#question = Question.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def question_params
params.require(:question).permit(:poll, :event_id, answer_attributes: [:response, :question, :event, :user, :event_id, :question_id, :user_id])
end
end
Try this:
class Event < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions, reject_if: :all_blank
end
You can read more about it on the official docs, but in a nutshell that will prevent any question that is completely blank from being saved, and let any populated question through.
class Event < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions
validates_associated :questions
end
This should solve your problem, be careful though not to use this on the other side of your relation, as it will cause infinite loop.
I guess I should add that once you add the validates helper method to your model, you can do this:
e = Event.new
e.valid?
=>false
This may be of some help: http://edgeguides.rubyonrails.org/active_record_validations.html
Try:
#question = Question.create(question_params)
instead of:
#question = Question.new(question_params)
class Person < ActiveRecord::Base
validates :name, presence: true
end
>> p = Person.new
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {}
>> p.valid?
# => false
>> p.errors.messages
# => {name:["can't be blank"]}
>> p = Person.create
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {name:["can't be blank"]}
>> p.save
# => false
>> p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
>> Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
I ended up just deleting a question with a blank :poll after the creation. question model is as follows
class Question < ActiveRecord::Base
belongs_to :user
belongs_to :event
has_many :answers
accepts_nested_attributes_for :answers
after_save { |question| question.destroy if question.poll.blank? }
end
I'm sure theres a better way and will gladly accept better answers. Thanks all for the help.
Yet another solution. A bit more explicit than the accepted answer IMHO.
accepts_nested_attributes_for : answers,
allow_destroy: true,
reject_if: -> { | question | [poll, foo, bar].any? &:blank? }
The code below shows the following setup with Rails 4, cocoon gem, and simple_form. Essentially, I have a bunch of clients in the DB, and I want to associate an existing one with an invoice via a select dropdown (selected by email). When I do select an existing client by email, what happens is that a new client with the same email gets created. This new client is not associated with any user. Why is this happening?
EDIT: I think I may have set it up so that a client can only have one invoice per http://requiremind.com/differences-between-has-one-and-belongs-to-in-ruby-on-rails/ though that should still allow an update of the client child object to occur.
Model setup
class Invoice < ActiveRecord::Base
belongs_to :user
has_one :client
has_many :invoice_items, dependent: :destroy
accepts_nested_attributes_for :invoice_items, allow_destroy: true
accepts_nested_attributes_for :client
end
class Client < ActiveRecord::Base
belongs_to :user
belongs_to :invoice
end
class User < ActiveRecord::Base
has_many :clients, dependent: :destroy
has_many :invoices, dependent: :destroy
end
_form partial to be called with edit/update
= simple_form_for [current_user, #invoice], html: { class: 'form-horizontal' } do |f|
- if #invoice.errors.any?
#error_explanation
%h2= "#{pluralize(#invoice.errors.count, "error")} prohibited this invoice from being saved:"
%ul
- #invoice.errors.full_messages.each do |msg|
%li= msg
.form-group
= f.label :paid, "Paid?"
= f.input_field :paid
%br/
%h3 Client
/ = f.simple_fields_for :client, Client.new do |client|
= f.simple_fields_for :client, Client.new do |client|
= client.select :email, Client.all.map { |c| [c.email, c.email, { class: c.user.id }] }, include_blank: true
= link_to "New Client", new_user_client_path(current_user)
%h3 Invoice Items
= f.simple_fields_for :invoice_items do |invoice_item|
= render 'invoice_item_fields', :f => invoice_item
.links.form-group
= link_to_add_association 'Add Invoice Item', f, :invoice_items, class: 'btn btn-primary'
.actions
= f.submit 'Submit'
Invoices controller:
class InvoicesController < ApplicationController
respond_to :json
before_action :set_invoice, only: [:show, :edit, :update, :destroy]
before_action :get_invoice_client, only: [:create, :update]
before_filter :authenticate_user!
# GET /invoices
# GET /invoices.json
def index
#invoices = current_user.invoices
end
# GET /invoices/1
# GET /invoices/1.json
def show
end
# GET /invoices/new
def new
#invoice = current_user.invoices.build
#invoice.client = #invoice.build_client
#invoice_items = #invoice.invoice_items.build
end
# GET /invoices/1/edit
def edit
end
# POST /invoices
# POST /invoices.json
def create
#invoice = current_user.invoices.build(invoice_params)
#invoice.client = #invoice_client
respond_to do |format|
if #invoice.save
format.html { redirect_to [current_user,#invoice], notice: 'Invoice was successfully created.' }
format.json { render action: 'show', status: :created, location: #invoice }
else
format.html { render action: 'new' }
format.json { render json: #invoice.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /invoices/1
# PATCH/PUT /invoices/1.json
def update
#invoice.client = #invoice_client
binding.pry
respond_to do |format|
if #invoice.update(invoice_params)
format.html { redirect_to [current_user, #invoice], notice: 'Invoice was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #invoice.errors, status: :unprocessable_entity }
end
end
end
# DELETE /invoices/1
# DELETE /invoices/1.json
def destroy
#invoice.destroy
respond_to do |format|
format.html { redirect_to user_invoices_url(current_user) }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_invoice
#invoice = current_user.invoices.find(params[:id])
#invoice_items = #invoice.invoice_items
end
# Never trust parameters from the scary internet, only allow the white list through.
def invoice_params
params.require(:invoice).permit(:paid, :date_sent, :user_id, client_attributes: [:id, :email, :firstname, :lastname, :home_phone, :mobile_phone],
invoice_items_attributes: [:id, :description, :line_total, :unit_cost, :quantity, :item, :invoice_id, :_destroy])
end
def get_invoice_client
#invoice_client = Client.find_by_email(params[:invoice][:client_attributes][:email]) || nil
end
end
Try changing this line
= f.simple_fields_for :client, Client.new do |client|
to
= f.simple_fields_for :client, #invoice.client do |client|
I am not sure I understand the relationship between Clients and Users, however if you have an existing table of clients . . .
Class Client
has_one :user
Class User
belongs_to :client, :dependent => :destroy
has_many :invoices, :dependent => :destroy
has_many :invoice_items, :through => :invoices
Class Invoice
belongs_to :user
has_many :invoice_items, :dependent => :destroy
Class Invoice_item
belongs_to :Invoice
You could possibly consolidate the Client and User tables into one table as there is a one-to-one association between the two, but you can leave it as above.
If your primary form is for #client, then
= simple_form_for #client do |f|
... f.client fields ...
= f.simple_fields_for #user do |u|
... u.user fields ...
= f.simple_fields_for #invoices do |i|
... i.invoice fields ...
= f.simple_fields_for #invoice_items do |t|
... t.invoice_item fields ...
Since you are using Haml, your indentation is critical. If you extract your simple_field_for outputs into separate partials, it will be much easier to read and manage. I left those out so you can see the relationships between your "nested" models.
Hope that helps.
new rails user here. I'm trying to have my schedule form store an array of "days" but after several attempts I just can't make it work.
Here are my codes currently
*schedules/_form.html.erb:*
<%= simple_form_for #schedule do |f| %>
<% if #schedule.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#schedule.errors.count, "error") %> prohibited this schedule from being saved:</h2>
<ul>
<% #schedule.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.input :section_id do %>
<%= f.select :section_id, Section.all.map{|s| [s.seccon, s.id]}, :include_blank => true %>
<% end %>
<%= f.association :subject %>
<%= f.collection_select :day_ids, #days, :id, :name, {}, {:multiple => true, :size => 1} %>
<div class="field">
<%= f.label :start_time %>
<%= f.time_select :start_time %>
</div>
<%= f.input :professor do %>
<%= f.select :professor_id, Professor.all.map{|j| [j.procon, j.id]}, :include_blank => true %>
<% end %>
<%= f.association :room %>
<%= f.button :submit %>
<% end %>
*schedules_controller.rb:*
class SchedulesController < ApplicationController
before_action :set_schedule, only: [:show, :edit, :update, :destroy]
# GET /schedules
# GET /schedules.json
def index
#schedules = Schedule.all
#days = Day.all
end
# GET /schedules/1
# GET /schedules/1.json
def show
end
# GET /schedules/new
def new
#schedule = Schedule.new
#days = Day.all
end
# GET /schedules/1/edit
def edit
end
# POST /schedules
# POST /schedules.json
def create
#schedule = Schedule.new(schedule_params)
#days = Day.all
respond_to do |format|
if #schedule.save
format.html { redirect_to #schedule, notice: 'Schedule was successfully created.' }
format.json { render action: 'show', status: :created, location: #schedule }
else
format.html { render action: 'new' }
format.json { render json: #schedule.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /schedules/1
# PATCH/PUT /schedules/1.json
def update
respond_to do |format|
if #schedule.update(schedule_params)
format.html { redirect_to #schedule, notice: 'Schedule was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #schedule.errors, status: :unprocessable_entity }
end
end
end
# DELETE /schedules/1
# DELETE /schedules/1.json
def destroy
#schedule.destroy
respond_to do |format|
format.html { redirect_to schedules_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_schedule
#schedule = Schedule.find(params[:id])
#days = Day.all
end
# Never trust parameters from the scary internet, only allow the white list through.
def schedule_params
params.require(:schedule).permit(:section_id, :subject_id, :start_time, :finish_time_id, :professor_id, :room_id, :day_ids)
end
end
schedule.rb:
class Schedule < ActiveRecord::Base
belongs_to :section
belongs_to :subject
belongs_to :finish_time
has_and_belongs_to_many :days
accepts_nested_attributes_for :days, :allow_destroy => true
validates :section_id, :subject_id, :start_time, :professor_id, :room_id, :presence => true
belongs_to :professor
belongs_to :room
end
day.rb:
class Day < ActiveRecord::Base
has_and_belongs_to_many :schedules
default_scope { order(:id)}
has_paper_trail
validates :name, :desc, :presence => true
end
As was said here, the best thing to do is to create a has_many model relationship between Schedule and Day. You'll need a separate join table to make the relationship work. It will have: schedule_id and day_id as the two columns. You'd do this because you have a many > many relationship. There can be many schedules that belong to a day and many days that belong to a schedule.
I used this scenario in my app:
Recipe.rb
class Recipe < ActiveRecord::Base
has_and_belongs_to_many :wines
default_scope { order(:name) }
end
Wine.rb
class Wine < ActiveRecord::Base
has_and_belongs_to_many :recipes
accepts_nested_attributes_for :recipes, :allow_destroy => true
end
Migration
class AddRecipesWinesJoinTable < ActiveRecord::Migration
def self.up
create_table :recipes_wines, :id => false do |t|
t.column :recipe_id, :integer, :null => false
t.column :wine_id, :integer, :null => false
end
add_index :recipes_wines, [:wine_id]
end
def self.down
remove_index :recipes_wines, [:wine_id]
drop_table :recipes_wines
end
end
_wine_form.html.erb
# #recipes is Recipe.all generated by the controller
<%= w.collection_select :recipe_ids, #recipes, :id, :name, {}, {:multiple => true, :size => 6, :style => 'width:100%'} %>
Hope this helps.
I have three models:
category.rb
class Category
include Mongoid::Document
# Relationships
has_many :posts, :autosave => true
has_many :boards, :autosave => true
accepts_nested_attributes_for :boards
accepts_nested_attributes_for :posts
#fields
field :name
#attr
attr_accessible :name
end
My model board.rb
class Board
include Mongoid::Document
# Relationships
has_many :posts, :dependent => :destroy , :autosave => true
accepts_nested_attributes_for :posts
belongs_to :category
#fields
field :name
field :description
#attr
attr_accessible :name, :posts_attributes, :description, :category_id
end
My post.rb
class Post
include Mongoid::Document
# Relationships
belongs_to :board
belongs_to :category
belongs_to :user
#fields
field :content
#attr
attr_accessible :content :board_id, :category_id
end
In my posts_controller
def create
#post = current_user.posts.new(params[:post])
#board = #post.board
#board.user = current_user
if #board.category_id?
#post.category_id = #board.category_id
end
respond_to do |format|
if #post.save
format.html { redirect_to root_url, notice: 'Post was successfully created.' }
format.json { render json: root_url, status: :created, location: #post }
else
format.html { render action: "new" }
format.json { render json: #post.errors, status: :unprocessable_entity }
end
end
end
In my view new action:
<%= form_for(#post) do |f| %>
<%= f.text_area :content %>
<%= f.collection_select :board_id, Board.where(:user_id => current_user.id), :id, :name%>
<%= f.submit :id => "button_submit_pin_edit" %>
<% end %>
The boards in select field, may or may not have a parent category already assigned.
I want get the attributes from category (the name of the category for this event) in my post view without using a select field, or an input field.
with this code in posts.controller.rb
if #board.category_id?
#post.category_id = #board.category_id
end
I see in my console for Post.first e.j.:
<Post _id: 4f1d96241d41c8280800007c, _type: nil, created_at: 2012-01-23 17:17:24 UTC, user_id: BSON::ObjectId('4f0b19691d41c80d08002b20'), board_id: BSON::ObjectId('4f1455fa1d41c83988000510'), category_id: BSON::ObjectId('4f1c2d811d41c8548e000008'), content: "nuevo post">
If I write:
post = Post.first
and
post.board
I get the object board fine. This does works fine.
but If I write:
post.category
I get:
=> nil
I have try in view new post add hidden field:
hidden_field(:category, :name)
How can I get the params of object category?
Thanks
Don't set it using the ID fields. Instead of this in your PostsController:
if #board.category_id?
#post.category_id = #board.category_id
end
Try this line:
#post.category = #board.category
Which will set the #post.category belongs_to relationship to point to #board.category assuming it exists (is not nil). Mongoid will handle the setting of the category_id field for you. Read the mongoid documentation about relationships for a more detailed explanation.