Ruby on Rails Association Form - ruby-on-rails

So i'm making a web app using ROR and I can't figure out what the right syntax for this form is. I'm currently making an association type of code for comments and posts.
<%= form_for #comment do |f| %>
<p>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.label :comment %><br />
<%= f.text_area :comment %>
</p>
<p>
<%= f.submit "Add Comment" %>
</p>
<% end %>

Your form is fine, except the first line (and you don't need a hidden field for the user_id, thats done through your relationship):
<%= form_for(#comment) do |f| %>
Should be:
<%= form_for([#post, #comment]) do |f| %>
Now you render a form for creating or updating a comment for a particular post.
However, you should change your model and controller.
class Post
has_many :comments
end
class Comment
belongs_to :post
end
This will give you access to #post.comments, showing all comments belonging to a particular post.
In your controller you can access comments for a specific post:
class CommentsController < ApplicationController
def index
#post = Post.find(params[:post_id])
#comment = #post.comments.all
end
end
This way you can access the index of comments for a particular post.
Update
One more thing, your routes should also look like this:
AppName::Application.routes.draw do
resources :posts do
resources :comments
end
end
This will give you access to post_comments_path (and many more routes)

Related

Ancestry Gem for Nested Comments with Rails causing undefined method error

I have been trying to fix an error associated with using the Ancestry gem for comments on my app for Rails 4. I used railscast episode 262 as a guide. However, unlike the episode, my comments model is a nested resource inside another model.Before I go further, I will supply the necessary code for reference. If you like to read the error right away, it is mentioned right after all the code snippets.
The Relevant Models:
class Comment < ActiveRecord::Base
has_ancestry
belongs_to :user
belongs_to :scoreboard
end
class Scoreboard < ActiveRecord::Base
#scoreboard model is like an article page on which users can post comments
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
Relevant code in the route file:
resources :scoreboards do
resources :comments
resources :teams, only: [:edit, :create, :destroy, :update]
end
The Scoreboards Controller Method for the page on which one can post comments:
def show
#scoreboard = Scoreboard.find_by_id(params[:id])
#team = #scoreboard.teams.build
#comment = #scoreboard.comments.new
end
The Comments Controller:
class CommentsController < ApplicationController
def new
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new(:parent_id => params[:parent_id])
end
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment = #scoreboard.comments.new comment_params
if #comment.save
redirect_to scoreboard_url(#comment.scoreboard_id)
else
render 'new'
end
end
private
def comment_params
params.require(:comment).permit(:body, :parent_id).merge(user_id: current_user.id)
end
end
I will include the migration for the ancestry gem if any mistakes were made on that :
class AddAncestryToComments < ActiveRecord::Migration
def change
add_column :comments, :ancestry, :string
add_index :comments, :ancestry
end
end
The following code shows the view code:
Scoreboard#show View which is giving me the error in the last line:
<div class= "comment-section">
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %> #is it needed to include this here? because this form is for new comments not replies
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
<%= nested_comments #scoreboard.comments.reject(&:new_record?).arrange(:order => :created_at) %>
</div>
The (comments partial)_comment.html.erb View:
<div class=" comment-div">
<p> Posted by <%= link_to "#{comment.user.name}", comment.user %>
<%= time_ago_in_words(comment.created_at) %> ago
</p>
<div class="comment-body">
<%= comment.body %>
<%= link_to "Reply", new_scoreboard_comment_path(#scoreboard, comment, :parent_id => comment) %>
</div>
</div>
The helper method to render comments:
def nested_comments(comments)
comments.map do |comment, sub_comment| #the comments.map also gives me an error if I choose to render the comments without the .arrange ancestry method
render(comment) + content_tag(:div, nested_comments(sub_comment), class: "nested_messages")
end.join.html_safe
end
The new.html.erb for Comments which one is redirected to for the replies form submission:
<%= form_for [#scoreboard, #comment] do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.text_area :body, class: "comment-field" %>
<%= f.hidden_field :parent_id %>
<%= f.submit "Join the discussion...", class: " comment-button btn btn-primary" %>
<% end %>
Upon creating a scoreboard, I am redirected to the show page, where i get the following error:
undefined method `arrange' for []:Array
Even though the array of comments is empty, I get the same error if it wasnt. I have tried .subtree.arrange but that gives me the same error. Also, the ancestry documentation said that .arrange works on scoped classes only. I don't know what that means. I would appreciate some help on making the page work so the comments show properly ordered with the replies after their parent comments. If this is the wrong approach for threaded comments(replies and all), I would appreciate some guidance on what to research next.
.reject(&:new_record?) this will return an array. The error sounds like arrange is a scope on ActiveRecord. So move the reject to the end and it should work.
#scoreboard.comments.arrange(:order => :created_at).reject(&:new_record?)
In regards your comment nesting, I have implemented this before, and found the Railscasts recommendation of a helper to be extremely weak.
Passing parent_id to a comment
Instead, you're better using a partial which becomes recursive depending on the number of children each comment has:
#app/views/scoreboards/show.html.erb
<%= render #comments %>
#app/views/scoreboards/_comment.html.erb
<%= link_to comment.title, comment_path(comment) %>
<div class="nested">
<%= render comment.children if comment.has_children? %>
</div>

How to use simple form for comments from a nested resource?

So I'm creating a blog rails app and I'm trying to create a comment session on the blog. I'm trying to render a form using simple form, but I'm having a hard time getting the simple form to work. For now I have:
<%= simple_form_for ([#user, #post.comments.build]) do |f| %>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
but it says that post.comments isn't a defined path.
My comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
post belongs to user and has_many comments
user has many post and has many comments
Here are my current routes:
resources :posts do
resources :comments
end
Any advice?
Thanks!
Why are you sending #user in simple_form_for?
Use #post in place of #user.
I found a fix to this by generating a migration for the comment. I just had to make sure that everything with an association actually had the columns in the database. After that I just made sure I was rendering the #post.comment instead of comment/comment. Hope this helps anyone that came across the same problem.
remove user from the form.
<%= simple_form_for [#post, #post.comments.build] do |f| %>
<%= f.input :comment %>
<%= f.button :submit %>
<% end %>
and then you will assign the user value in the controller using something like current_user if you are using devise.
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.build(comment_params)
#comment.user = current_user
#comment.save
redirect_to #post
end
def comment_params
params.require(:comment).permit(:comment)
end
it's a bad idea to have a model named comment and a field into it named comment. I would prefer to call it content

How to update child record in nested form in rails

How would I update a collection of child 'Product' rows when submitting the form below.
Many thanks
class User < ActiveRecord::Base
has_many :products, :class_name => 'Product', :inverse_of => :user
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
belongs_to :user, :inverse_of => :products
end
The View
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :products do |builder| %>
<% builder.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is somewhat minimal update action:
# app/controller/users_controller.rb
def update
# Get the user in param
#user = User.find(params[:id])
# Update attributes
if #user.update_attribute(params[:user])
# Redirect to :show for this user.
redirect_to :show
else
# update_attriubtes failed. Render the edit view.
render :edit
end
end
Secondly, you also need to ensure the attributes can be mass assigned such that the call to #user.update_attributes succeeds. Since you've not tagged your question with the appropriate Rails version, following is what you can do for Rails 3 and Rails 4:
Rails 3 - Define each attribute you want to mass assign as attr_accessible
Rails 4 - Permit parameters in controllers params.require(:user).permit(...).
Note that in your view you are not outputting the result of the helper text_field on this line:
<% builder.text_field :name %>
You need to use <%=...%> (note the equals = sign) in order to output the result of expressions within <%=...%>. This might have just been a typo here as you've done this correctly on all other elements. Replace the line with:
<%= builder.text_field :name %>
Also, suggest reading the "Action Controller Overview" of guides for details on Controllers.

Rails 4: accepts_nested_attributes_for and mass assignment

I am trying to reproduce railscast #196 in Rails 4. However, I'm experiencing some problems.
In my example I try to generate a Phonebook - each Person could have multiple PhoneNumbers
These are important parts of my controller:
class PeopleController < ApplicationController
def new
#person = Person.new
3.times{ #person.phones.build }
end
def create
#person = Person.create(person_params)
#person.phones.build(params[:person][:phones])
redirect_to people_path
end
private
def person_params
params.require(:person).permit(:id, :name, phones_attributes: [ :id, :number ])
end
end
and this is my new view
<h1>New Person</h1>
<%= form_for :person, url: people_path do |f| %>
<p>
<%= f.label :name %> </ br>
<%= f.text_field :name %>
</p>
<%= f.fields_for :phones do |f_num| %>
<p>
<%= f_num.label :number %> </ br>
<%= f_num.text_field :number %>
</p>
<% end %>
<p>
<%= f.submit %>
</p>
<% end %>
needless to say i have has_many :phones and accepts_nested_attributes_for :phones in the my person model and belongs_to :person in the phone model.
I have the following issues:
Instead of 3 phone-number-fields there is just one in the new form
When I submit the form I get an error:
ActiveModel::ForbiddenAttributesError
in the line
#person.phones.build(params[:person][:phones])
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"l229r46mS3PCi2J1VqZ73ocMP+Ogi/yuYGUCMu7gmMw=",
"person"=>{"name"=>"the_name",
"phones"=>{"number"=>"12345"}},
"commit"=>"Save Person"}
In principle I would like to do this whole thing as a form object, but I think if I don't even get it with accepts_nested_attributes, I have no chance to do it as a form object :(
In order to get three phones in the view change form_for :person to form_for #person (you want to use the object you've built here) as follows:
<%= form_for #person, url: people_path do |f| %>
This should fix the ForbiddenAttributes error as well.
And your create action could be:
def create
#person = Person.create(person_params)
redirect_to people_path
end
Update:
<%= form_for :person do |f| %> creates a generic form for the Person model and is not aware of the additional details you apply to a specific object (in this case #person in your new action). You've attached three phones to the #person object, and #person is not the same as :person which is why you didn't see three phone fields in your view. Please reference: http://apidock.com/rails/ActionView/Helpers/FormHelper/form_for for further details.

Error during the creation of hidden fields for nested objects

I am using rails 2.3.5. I have a Blog model and Blog has many comments. This is my Blog controller show action
def show
#blog = Blog.find(params[:id])
#comment = Comment.new
end
I would display the Blog and at the end would have an option for creating comment. So I add this in blogs/show.html.erb.
<% form_remote_for #comment do |f| %>
<%= f.label :content %>
<%= f.text_area :content, :rows => 6 %>
<%= f.hidden_field :blog => #blog %>
<%= f.submit %>
<% end %>
But i get the following error when i run this
NoMethodError in Blogs#show
Showing app/views/blogs/show.html.erb where line #270 raised:
undefined method `blog#<Blog:0xb677d8d0>' for #<Comment:0xb67762b0>
Extracted source (around line #270):
Comment model should have belongs_to :blog
Blog model should have has_many :comments
Initialize the comment in the controller like this:
#blog.comments.new
The view should be like this:
<%= f.hidden_field :blog_id %>
you have to hide the id of the blog not the blog object.
<%= f.hidden_field :blog_id%>
The problem is your f.hidden_field line. The first parameter should be the attribute name of the #comment you want in the field, but in your code it's a Hash.
I'd suggest adjusting your show action to set #comment = #blog.comments.build, and change the view to read f.hidden_field :blog_id.

Resources