I'm trying to build a simple nested form, checking a lot of resources online, but can't find what is it that I'm missing!
I have the following:
class Configuration < ActiveRecord::Base
has_many :configoptions
accepts_nested_attributes_for :configoptions
end
class Configoption < ActiveRecord::Base
belongs_to :configuration
has_many :items
end
Now, I'm trying to make a simple form when you select a configuration so it would show the
configoptions belonging to it, but nothing works!
This is the view without any html
<%= form_for :config do |f| %>
<%= f.text_field(:name)%>
<% f.fields_for #options do |option|%>
<% end %>
<% end %>
In the controller I have:
def show
#config = Configuration.find(params[:id])
#options = #config.configoptions
end
But i end up getting the error:
undefined method `model_name' for Array:Class
Does anyone have advice for me? Thanks a lot!
FYI, Ryan Bates (RailsCasts) has created a gem to handle much of this. I'm using it now and it works great!
See https://github.com/reu/simple_nested_form for the details.
You'll have to specify a model_name if you are using a collection (like an array).
<%= form_for :config do |f| %>
<%= f.text_field(:name)%>
<%= f.fields_for :configoptions, #options do |option|%>
<%= option.text_field(:some_attribute) %>
<% end %>
<% end %>
In fields_for I am passing two arguments, :configoptions as the model name and #options as the collection to use. http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html has a ton of great nested attribute examples if you scroll down just above half way.
Alternatively (and my personal preferred method) is to loop through the collection and call fields_for for each object.
<%= form_for :config do |f| %>
<%= f.text_field(:name)%>
<% #options.each do |configoption| %>
<%= f.fields_for configoption do |option|%>
<%= option.text_field(:some_attribute) %>
<% end %>
<% end %>
<% end %>
Try:
class Configuration < ActiveRecord::Base
has_many :configoptions, :dependent => :destroy
accepts_nested_attributes_for :configoptions, :allow_destroy => true
end
You used:
<% f.fields_for #options do |option|%>
try using
<%= f.fields_for #options do |option|%>
I had the same problem, it was solved by this trivial idea.
Related
7 Patterns to Refactor Fat ActiveRecord Models - here is a great article about different refactoring approaches using PORO. Under the 3rd caption there is a Form Object pattern, which I really liked and already implemented in one of the projects. There is only an example using one nested resource, but I would like to implement this pattern for multiple nested resources. Maybe someone here had already dealt with this? I don't necessarily need any code examples, just the basic idea would be fine.
Update
Consider this example. I have two models.
class Company
has_many :users
accepts_nested_attributes_for :users
end
class User
belongs_to :company
end
In case of one nested user for company using Form Object Pattern I would write the following:
<%= form_for #company_form do |f| %>
<%= f.text_field :name %>
<%= f.text_field :user_name %>
<%= f.submit %>
<% end %>
Form Object
class CompanyForm
include Virtus
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :company, :user
def user
#user ||= company.users.build
end
def company
#company ||= Company.new
end
def submit(params={})
company.name = params[:name]
user.name = params[:user_name]
persist!
end
private
def persist!
company.save!
user.save!
end
end
But what if I have a form, where a company with multiple users can be created. The usual approach is to write it like this, using nested_form:
<%= nested_form_for #company do |f| %>
<%= f.text_field :name %>
<%= fields_for :users, do |user_form| %>
<%= user.form.text_field :name %>
<% end %>
<%= f.link_to_add "Add a user", :users %>
<%= f.submit %>
<% end %>
What I am asking is how do I implement that Form Object Pattern in this case?
the rails fields_for helper checks for a method in this format: #{association_name}_attributes=
so, if you add this method to CompanyForm:
def users_attributes=(users_attributes)
# manipulate attributes as desired...
#company.users_attributes= users_attributes
end
def users
company.users
end
the fields_for generators will generate the nested users fields for a CompanyForm as if it were a Company. the above could be rewritten as a delegation since nothing is happening in the methods:
delegate :users, :users_attributes=, :to => :company, :prefix => false, :allow_nil => false
I have a Sezzion model:
attr_accessible :description
has_many :session_instructors, :dependent => :destroy
has_many :instructors, :through => :session_instructors
accepts_nested_attributes_for :session_instructors
accepts_nested_attributes_for :instructors
Instructor model:
attr_accessible :bio
has_many :sezzions, :through => :session_instructors
has_many :session_instructors, :dependent => :destroy
SessionInstructor model:
attr_accessible :instructor_id, :sezzion_id
belongs_to :sezzion
belongs_to :instructor
Lastly, User model:
has_many :sezzions
has_many :instructors
I'm trying to create a form for Sezzion with nested form for SessionInstructor which has multiple select option for Instructors.
How can I do the following:
nested form for SessionInstructor
use multiple select option to get all the selected Instructor's instructor_id
hidden field to pass in the created/updated session_id with each select instructor
I have the following code as of now:
<%= form_for(#sezzion) do |f| %>
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.label :instructors %>
<%= fields_for :session_instructors do |f| %>
<select multiple>
<% current_user.instructors.each do |instructor| %>
<option><%= instructor.name %></option>
<% end %>
</select>
<% end %>
<%= f.submit %>
<% end %>
Thank you so much!
This is something that seems ridiculously hard in Rails.
I think something like this might work:
<%= f.fields_for :session_instructors do |si| %>
<%= si.collection_select :instructor_ids, current_user.instructors, :id, :name, multiple: true>
<% end %>
This should create a form element which will set sezzion[session_instructors_attributes][instructor_ids].
Although I'm not sure if that's actually what you want. I've never tried this using a multi select. If it doesn't work, you could also try getting rid of the fields_for and just using f.collection_select. If you're willing to use a checkbox, I can show you how to do that for sure.
I hope that helps.
Edit:
Here's how I would usually do it with a check_box:
<%= f.fields_for :session_instructors do |si| %>
<%= si.hidden_field "instructor_ids[]" %>
<% current_user.instructors.each do |i| %>
<%= si.check_box "instructor_ids[]", i.id, i.sezzions.include?(#sezzion), id: "instructor_ids_#{i.id}" %>
<%= label_tag "instructor_ids_#{i.id}", i.name %>
<% end %>
<% end%>
There are a couple "gotchas!" with this method. When editing a model, if you deselect all checkboxes then it won't send the parameter at all. That's why the hidden_field is necessary. Also, you need to make sure each form element has a unique id field. Otherwise only the last entry is sent. That's why I manually set the value myself.
I copy pasted and then edited. Hopefully I got the syntax close enough where you can get it to work.
FINAL EDIT:
Per Sayanee's comment below, the answer was a bit simpler than I thought:
<%= f.collection_select :instructor_ids, current_user.instructors, :id, :name, {}, {:multiple => true} %>
#Sayanee, can you post how your instructors, sezzions table look like. Also for note, you can not get instructor_ids from Instructor object, hence you are getting "undefined method" error. With the current association that you shared, you can get instructor_ids from a Sezzion object. So you need to loop through current_user.sezzions in stead of current_user.instructors.
This is a way to implement fields_for nested form with html multiple_select in case of a has_many :through association. Solved it by doing something like this. The form view:
<%= form_for(#sezzion) do |f| %>
...
<%= fields_for :session_instructors do |g| %>
<%= g.label :instructor, "Instructees List (Ctrl+Click to select multiple)" %>:
<%= g.select(:instructor_id, Instructor.all.collect { |m| [m.name, m.id] }, {}, { :multiple => true, :size => 5 }) %>
<%= g.hidden_field :your_chosen_variable_id, value: your_chosen.id %>
<% end %>
...
<%= f.submit %>
<% end %>
Note:Since the #sezzion would not be saved at the time of generating the form you cannot pass that id (#sezzion.id) in place of your_chosen.id through the form. You could handle that save in the controller.
Make sure that your controller Initializes the Variables while generating the form: Your def new could look something like this:
def new
#sezzion = Sezzion.new
#sezzion.session_instructor.build
#sezzion.instructors.build
end
Now the create controller has to be able to accept the strong params required for the multiple select, so the sezzion_params method may look something like this:
def sezzion_params
params.require(:sezzion).permit(:description, :any_other_fields,
:session_instructors_attributes =>
[:instructor_id => [], :your_chosen_id => Integer])
end
In the create function, the first session_instructor variable is linked to the #sezzion instance variable through our new function. The other session_instructors in our multiple select must be built after the Sezzion instance is saved, if you want to pass in the created #sezzion.id with each select instructor. .
def create
#sezzion = Sezzion.new(sezzion_params)
#startcount=1 #The first element of the array passed back from the form with multiple select is blank
#sezzion.session_instructors.each do |m|
m.instructor_id = sezzion_params["session_instructors_attributes"]["0"]["instructor_id"][#startcount]
m.your_chosen_variable_id = your_chosen.id
#startcount +=1
end
if #sezzion.save
sezzion_params["session_instructors_attributes"]["0"]["instructor_id"].drop(#startcount).each do |m|
#sezzion.session_instructors.build(:instructor_id => sezzion_params["session_instructors_attributes"]["0"]["instructor_id"][#startcount],
:your_chosen_variable_id => your_chosen.id).save
#startcount += 1
end
flash[:success] = "Sezzion created!"
redirect_to root_url
else
flash[:danger] = "There were errors in your submission"
redirect_to new_sezzion_path
end
end
I have problem in getting below code to work.
class Page < ActiveRecord::Base
has_many :page_parts, :through => :page_parts_pages
has_many :page_parts_pages
accepts_nested_attributes_for :page_parts, :allow_destroy => true
accepts_nested_attributes_for :page_parts_pages, :allow_destroy => true
end
class PagePart < ActiveRecord::Base
has_many :page_parts_pages
has_many :pages, :through => :page_parts_pages
end
class PagePartsPage < ActiveRecord::Base
belongs_to :page
belongs_to :page_part
end
Table Structure:-
pages
id, title
pages_parts
id, title
page_parts_pages
id, page_id, page_part_id
View code
<% page_fragment.each do |k,v| %>
<% if v.nil? or v.blank? or v.empty? %>
<% parts = f.object.page_parts.build if f.object.page_parts.blank? %>
<%= f.fields_for :page_parts, parts do |p| %>
<%= render 'page_part_form_field', :f => p %>
<% end %>
<% else %>
<% parts_page = f.object.page_parts_pages.build if f.object.page_parts_pages.blank? %>
<%= f.fields_for :page_parts_pages, parts_page do |p| %>
<%= render 'page_part_page_form_field', :f => p %>
<% end %>
<% end %>
<% end %>
Actually the scenario is, I have to display the fields for page_parts and page_parts_pages on condition basis. If condition is satisfied, display fields for page_parts else display fields for page_parts_pages.
It's working perfectly fine for new action but for edit action it is not displaying correctly.
Any help is highly appreciated.
Thanks in advance
You are creating new page_parts in this form:
parts = f.object.page_parts.build if f.object.page_parts.blank?
parts_page = f.object.page_parts_pages.build if f.object.page_parts_pages.blank?
'build' creates new objects (it will not persist them in database though). So, no wonder it works for new, but not for edit.
You can try this:
<% page_fragment.each do |k,v| %>
<% if v.blank? %>
<%= f.fields_for :page_parts do |p| %>
<%= render 'page_part_form_field', :f => p %>
<% end %>
<% else %>
<%= f.fields_for :page_parts_pages do |p| %>
<%= render 'page_part_page_form_field', :f => p %>
<% end %>
<% end %>
<% end %>
rails api has pretty good documentation of forms_for and other form helpers.
Given a User who can possibly be an Artist:
class User < ActiveRecord::Base
has_one :artist
end
I've got a User & Artist nested form (using Formtastic gem):
<h1>Artist registration</h1>
<% #user.build_artist unless #user.artist %>
<%= semantic_form_for #user, :url => create_artist_path do |f| %>
<%= f.inputs :username %>
<%= f.semantic_fields_for :artist do |a| %>
<%= a.input :bio %>
<% end %>
<%= f.buttons do %>
<%= f.commit_button 'Register as Artist' %>
<% end %>
<% end %>
The problem is the :artist fields are not rendered.
I've also tried f.inputs :for => :artist do |a|.
For some reason, using #user.build_artist does not display the artist's fields in the form. If I try #user.artist = Artist.new I get an error, because it tries to save the Artist and validation fails.
How should I initialize the Artist model so I get the benefit of formtastic generators in a nested form? (Note that #user here is not a :new_record?)
Did you remember to set accepts_nested_attributes_for :artist in user.rb?
It's my first time here, and first time I use nested_form gem. Everything seemed to be ok, but the data from my "parent" model doesn't save.
Here is my code
<%= nested_form_for #project do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :tasks %>
<p><%= f.link_to_add "Add a task", :tasks %></p>
<%= f.submit %>
<% end %>
so, when I "submit", just the tasks are saved ok, but not the project name.
Any clue for me? did I miss something??
You need to add the attribute. eg name to attr_accessible.
# app/models/project.rb
class Project < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
accepts_nested_attributes_for :tasks, :allow_destroy => true
attr_accessible :name,:tasks_attributes ## <-- you need this line
end
Your fields_for declaration isn't quite right
<%= f.fields_for :tasks %>
Should be
<%= f.fields_for :tasks do |task_builder| %>
you are also missing an end for that declaration and a render to render the partial that has the nested fields for the associated object.
So you should end up with something like this
<%= f.fields_for :tasks do |task_builder| %>
<%= render 'task_fields', :f => task_builder %>
<% end %>
<p><%= f.link_to_add "Add a task", :tasks %></p>
That should do the trick. all you need to do now is create a _task_field.html.erb partial and add the task fields to it in the usual way using f.label, f.text_field etc...
p.s.
Your code could not possibly have ever worked. You would have had errors so something is probably missing from your opening post.