multiple nested resources for form_for - ruby-on-rails

I've being following the rails getting started tutorial and have been changing the model to help my understading of rails.
I have an article model which has_many comments:
class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
validates :title, presence: true,
length: { minimum: 5 }
end
class Comment < ActiveRecord::Base
belongs_to :article
end
routes.rb
resources :articles do
resources :comments
end
The view to create the comment is in a partial (as per the rails tutorial)
<%= form_for([#article, #article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
This works fine but I wanted to split this out a bit to make a commenter a separate model (I know its not great OO but I just experimenting here!)
I therefore created a Commenter model:
class Commenter < ActiveRecord::Base
belongs_to :comment
end
changed the Comment to :
class Comment < ActiveRecord::Base
has_one :commenter
belongs_to :article
end
routes.rb
resources :commenters
resources :articles do
resources :comments
end
resources :comments do
resource :commenter
end
I'd like to create the Comment and the commenter at the same time in a single form but I'm stuck on how to change the view to achieve this as the view builds the comments model, do I also need to build the commenter model here? if so how do I achieve this?
<%= form_for([#article, #article.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :body %><br>
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>

For what your looking to do, you need to implement Nested Forms.
From the code you have provided, below I have shown how I would change it.
Models
class Article < ActiveRecord::Base
has_many :comments, dependent: :destroy
accepts_nested_attributes_for :comments # This is required for #article to save the forms nested within it
validates :title, presence: true,
length: { minimum: 5 }
end
class Comment < ActiveRecord::Base
belongs_to :article
accepts_nested_attributes_for :commenter # Required to save nested Commenter form
end
class Commenter < ActiveRecord::Base
belongs_to :comment
end
Article Controller
In the action that will be called when someone decides to comment on an article, you will need to select the #article and create a comment and also a commenter for the comment. These have to be created before the form is rendered, otherwise they won't be displayed.
def create_comment
#article.find(:id_of_article)
#comment = #article.comments.find(:id_of_new_comment)
#comment.create_commenter
end
View
Finally the form
<%= form_for(#article) do |f| %>
<%= f.fields_for(:comments, #comment) do |comment| %> # As article will have many comments, you need to specify the new comment you want to display
<%= comment.label :comment %><br>
<%= comment.text_field :comment %>
<%= comment.fields_for(:commenter) do |commenter| %>
<%= commenter.label :commenter_name %>
<%= commenter.text_field :commenter_name %>
<%end%>
<%end%>
<%= f.submit %>
<% end %>
Anyway, I hope this helps. Although if you're still doing your tutorials, you should learn this fairly soon anyway.

Related

Nested attributes don't afford me access to associated model data

Im looking for the proper way to build a form for the following data structure:
class Profile < ActiveRecord::Base
attr_accessible :name
has_many :weights
accepts_nested_attributes_for :weights
end
class Tag < ActiveRecord::Base
attr_accessible :name
has_many :weights
end
class Weight < ActiveRecord::Base
attr_accessible :weight, :profile_id, :tag_id
belongs_to :profile
belongs_to :tag
end
In the edit profile form I want to pull in all the weights and allow users to update them. I've been able to do this with nested attributes like so:
<%= form_for [:admin, #profile] do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %>
<%= f.text_field :name %>
</p>
<div class='weights'>
<%= f.fields_for :weights do |ff| %>
<%= ff.label :weight %>
<%= ff.text_field :weight %>
<% end %>
</div>
<%= f.submit %>
<% end %>
The thing is that I actually want to pull in the title of the associated tag_id on each weights row as well (so people know which weight's tag they are changing). I don't see a way to pull this info in, should I be doing some sort of join before I write this form out? Is this a silly approach?
Thanks everyone
-Neil
You should be able to get at the weight through ff.object and tag through ff.object.tag.title. Have you tried this?

Nested forms and accepts_nested_attributes_for

I am trying to understand how to make a nested form of my models but I am struggeling with understanding how and what I need to do it. I have been reading the Rails documentation and looked at the railscast but they just mention the accepts_nested_attributes_for method etc without explaining. Can someone please help?
Per API of Rails it's said:
Nested attributes allow you to save attributes on associated records through the parent...
Example: it shows how we can manage posts through Member, fields_for is used to manage associated fields in a form, passing it the name of the associated model and then loop through all of the associated post records and create a form builder for each of them.
#controller
def new
#member = Member.new
end
#model
class Post < ActiveRecord::Base
belongs_to :member
end
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
#form
<%= form_for #member do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :posts do |builder| %>
<p>
<%= builder.label :account %><br />
<%= builder.text_area :account %>
</p>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
Rails API: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

How to loop through two alternating resources on a form?

I'm trying to make a dynamic form of questions and answers, like so:
Question _______
Answer _______
Question _______
Answer _______
I can't figure out how to loop through the two resources as alternating pairs. I have tried this:
<%= semantic_fields_for [#question, #answer] do |h, i| %>
<%= f.inputs :for => #question do |h|%>
<%= h.input :question %>
<% end %>
<%= f.inputs :for => #answer do |i|%>
<%= i.input :answer %>
<% end %>
<% end %>
But it gives me the error "Undefined method `model_name' for Array:Class."
My controller:
def new
#post = Post.new
#question = #post.questions.new
#answer = #question.build_answer
respond_to do |format|
format.html
end
end
And my models:
class Post < ActiveRecord::Base
has_many :questions
has_many :answers
end
class Question < ActiveRecord::Base
belongs_to :post
has_one :answer
end
class Answer < ActiveRecord::Base
belongs_to :question
belongs_to :post
end
So I don't personally use formtastic but I understand it follows similar lines to simple_form. Your error is coming from trying to pass an Array to semantic_fields_for which only takes a single object:
<%= semantic_form_for #questions do |q| %>
<%= q.input :question %>
<%= q.semantic_fields_for #answer do |a| %>
<%= a.inputs :answer %>
<% end %>
<%= q.actions %>
<% end %>
Don’t forget your models need to be setup correctly with accepts_nested_attributes_for
class Question < ActiveRecord::Base
belongs_to :post
has_one :answer
accepts_nested_attributes_for :answers
end
You'll want to check out the formtastic docs at https://github.com/justinfrench/formtastic
That should get your form showing correctly in the view but you'll need to add some more to your questions controller to make sure it saves the answers (someone correct me if I'm mistaken).
Also just so it's clear do your Questions and Answers tables really have a question and answer column? If the columns are actually something like :body you'll want to replace the relevant symbols in the above code.
I think what you need is exactly what is described in these railcasts:
Nested Model Form Part 1
Nested Model Form Part 2
I think you should also refactor a bit, Posts should not have questions. You might notice a little difference from the railcasts but that's because you have only one answer per question whereas in the railcasts a question has many answers. In the part 2 it shows how to add AJAX calls to add/remove questions and answers (probably you won't need this if you only have one answer).
Mandatory reading so you have a better understanding of associations and how nested attributes work:
A Guide to Active Record Associations
Active Record Nested Attributes
And this is an example that will probably work, with some minimum tweaking. I haven't used semantic fields, just the standard form builder.
class Post < ActiveRecord::Base
has_many :questions
accepts_nested_attributes_for :questions, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
class Question < ActiveRecord::Base
belongs_to :post
has_one :answer, :dependent => :destroy
accepts_nested_attributes_for :answers, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
class Answer < ActiveRecord::Base
belongs_to :question
end
# posts_controller.rb
def new
#post = Post.new
# lets add 2 questions
2.times do
question = #post.questions.build
question.build_answer
respond_to do |format|
format.html
end
end
# views/posts/_form.html.erb
<%= form_for #post do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :questions do |builder| %>
<%= render "question_fields", :f => builder %>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
# views/posts/_question_fields.html.erb
<p>
<%= f.label :content, "Question" %><br />
<%= f.text_area :content, :rows => 3 %><br />
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove Question" %>
</p>
<%= f.fields_for :answers do |builder| %>
<%= render 'answer_fields', :f => builder %>
<% end %>
# views/posts/_answer_fields.html.erb
<p>
<%= f.label :content, "Answer" %>
<%= f.text_field :content %>
<%= f.check_box :_destroy %>
<%= f.label :_destroy, "Remove" %>
</p>

Multiple models in the same form in Rails 3.1?

I am using Rails 3.1 and am working on a discussion forum. I have a model called Topic, each of which has many Posts. When the user makes a new topic, they should also make the first Post as well. However, I am not sure how I can do this in the same form. Here is my code:
<%= form_for #topic do |f| %>
<p>
<%= f.label :title, "Title" %><br />
<%= f.text_field :title %>
</p>
<%= f.fields_for :post do |ff| %>
<p>
<%= ff.label :body, "Body" %><br />
<%= ff.text_area :body %>
</p>
<% end %>
<p>
<%= f.submit "Create Topic" %>
</p>
<% end %>
class Topic < ActiveRecord::Base
has_many :posts, :dependent => :destroy
accepts_nested_attributes_for :posts
validates_presence_of :title
end
class Post < ActiveRecord::Base
belongs_to :topic
validates_presence_of :body
end
... but this doesn't seem to be working. Any ideas?
Thanks!
#Pablo's answer seems to have everything you need. But to be more specific...
First change this line in your view from
<%= f.fields_for :post do |ff| %>
to this
<%= f.fields_for :posts do |ff| %> # :posts instead of :post
Then in your Topic controller add this
def new
#topic = Topic.new
#topic.posts.build
end
That should get you going.
A very good explanation from Ryan Bates here and here
For your particular case: you are using a model (:post), instead of an association (:posts) when you call fields_for.
Also check for the proper use of <%= ... %>. In rails 3.x the bahaviour of the construct changed. Block helpers (form_for, fields_for, etc) dont need it, and inline helpers (text_field, text_area, etc) do need it.

Rails 3 Nested Model Form

I am having some issues with nested models in a form, using Rails 3.1rc4.
I presently have models that look like this:
class Sale < ActiveRecord::Base
attr_accessible :customer_id, :vehicle_id, :sale_date
belongs_to :customer
accepts_nested_attributes_for :customer
end
and
class Customer < ActiveRecord::Base
attr_accessible :dealership_id, :first_name, :last_name, :address1, :email
belongs_to :dealership
has_many :sales
has_many :vehicles, :through => :sales
end
I've obviously truncated these slightly, but all the important info is there.
I am attempting to set up a sale form that will also allow me to create a new customer, hence the accepts_nested_attributes_for :customer line in the sale model.
My form view looks like (again truncated, only the important part):
<%= form_for #sale, :html => {:class => 'fullform'} do |f| %>
<%= f.error_messages %>
<%= field_set_tag 'Customer Details' do %>
<% f.fields_for :customer do |builder| %>
<%= builder.label :first_name %><br>
<%= builder.text_field :first_name %>
<% end %>
<% end %>
<% end %>
The problem I am having is that neither the text field nor the label for :first_name are showing up when the form is rendered - there is no error message, it just doesn't appear.
I should mention that I have tried both with and without #sale.customer.build in the new method of my controller, but it seems to have had no effect.
Thanks!
Can anyone suggest what I am doing wrong?
EDIT: For the avoidance of doubt, my sales controller's new method looks like:
def new
#sale = Sale.new
#sale.customer.build
end
Add customer_attributes to your attr_accessible in the Sale model.
Another mistake; Replace:
<% f.fields_for :customer do |builder| %>
With:
<%= f.fields_for :customer do |builder| %>

Resources