I am having troubles with a polymorphic association in Rails. I have an application where it should be possible to comment on different models, such as Posts, Images, Projects
Right now I just have Posts to comment on. On the start page there is an index view of the latest Posts and each Post has a small Comment form underneath to comment on via Ajax, very much like Facebook.
My models look like this:
class Post < ActiveRecord::Base
belongs_to :post_category
belongs_to :user
has_many :comments, :as => :commentable
validates_presence_of :user_id
validates_presence_of :post_category_id
validates_presence_of :title
validates_presence_of :body
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, :polymorphic => true
end
Now in my Comments controller I added the following method (I think I took it from railscasts or something), which I assume tries to find out the #commentable dynamically when creating an comment.
But this always returns the error undefined methodcomments' for nil:NilClass`
# find commentable (parent) item
def find_commentable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value) unless name == 'user_id'
end
end
nil
end
def create
#commentable = find_commentable
#comment = #commentable.comments.build(params[:comment])
if #comment.save
redirect_to #comment, :notice => 'Comment was successfully created.'
redirect_to :id => nil
else
render :action => "new"
end
end
The two things I tried in my partial were:
leaving the commentable info out of the form
= form_for [#commentable, Comment.new], :remote => true do |f|
#new_comment.add_comment
= f.hidden_field :user_id, :value => current_user.id
= f.text_field :content, :size => 55, :value => 'leave a comment...', :class => 'comment_form'
= f.submit "send"
and 2. passing the commentable_id and commentable_type
= form_for [#commentable, Comment.new], :remote => true do |f|
#new_comment.add_comment
= f.hidden_field :user_id, :value => current_user.id
= f.hidden_field :commentable_id, :value => post_id
= f.hidden_field :commentable_type, :value => 'Post'
= f.text_field :content, :size => 55, :value => 'leave a comment...', :onfocus => 'this.select()', :class => 'comment_form'
= f.submit "send"
both without luck. Any help would be highly appreciated.
the whole comments controller code is in this gist: https://gist.github.com/1334286
It seems like the commentable is not assigned correctly in the comments controller. This could have multiple reasons. Here is a setup that should work for you:
In the Posts controller, e.g. action "show":
#post = Post.find(params[:id])
In the posts/show view comments form:
= form_for [#post, #post.comments.new], :remote => true do |f|
You should be able to use your comments controller as it - but you should change the render to e.g. a redirect_to :back in the create action since the comments controller will most probably not have a "new" view on its own (it is dependent from the commentable)
Also, make sure that you have nested routes for all resources that can act as a commentable, like so:
resources :posts do
resources :comments do
end
resources :comments do
resources :comments # subomments
end
UPDATED to reflect information in the comments
Don't use #commentable in the post show view, since it's only defined in the comments controller.
Do this instead:
_comment.html.erb: (the comment partial in the post show view)
<%= form_for ([comments, #vote]), remote: true do |f| %>
posts/posts_controller.rb:
<%= form_for ([#post, #vote]), remote: true do |f| %>
Related
I am attempting to add the ability for users to add comments to posts on my rails site.
I have a Posts table and a Users table in my database. I'm using resourceful routes to display individual posts on the 'show' action of my Posts controller. I want to be able to have a comment box show up under the post that the user can enter a comment into, then click submit and have it create the comment.
I tried making a model for comments, giving them a belongs_to relationship for both user and post. I also added the has_many relationship to the user and post models. I then tried to have a Comment Controller use a 'create' action in order to process a form on each posts' 'show' action.
I am running into the issue of not being able to get the post_id to inject into the new comment. I can get the user_id by grabbing it from the user's session, but the only thing passed to the 'create' action on the Comments Controller is the actual text of the comment through the form.
Is this a good way of going about adding this feature? There must be a better way of doing it, or maybe I'm just missing something.
My Posts Controller 'show' action:
#PostsController.rb
def show
#post = Post.where(:id => params[:id]).first
if #post.nil?
flash[:error] = "Post does not exist"
redirect_to(root_path)
end
#comment = Comment.new
end
The form on the 'show' view for the 'show' action on PostsController:
#views/posts/show.html.erb
<%= form_for #comment do |f| %>
<%= f.text_area(:content, :size => '20x10', :class => 'textarea') %>
<%= f.submit('Create Post', class: 'button button-primary') %>
<% end %>
My Comments Controller 'create' action:
#CommentsController.rb
def create
#comment = Comment.new(params.require(:comment).permit(:content, :post_id, :user_id))
#comment.user_id = session[:user_id]
#Need to set post_id here somehow
if #comment.valid?
#comment.save
flash[:success] = "Comment added successfully."
redirect_to(post_path(#comment.post))
else
#error = #comment.errors.full_messages.to_s
#error.delete! '[]'
flash.now[:error] = #error
render('posts/show')
end
end
My Post model:
class Post < ApplicationRecord
belongs_to :subject
belongs_to :user
has_many :comments
validates :title, :presence => true,
:length => {:within => 4..75}
validates :content, :presence => true,
:length => {:within => 20..1000}
end
My Comment model:
class Comment < ApplicationRecord
belongs_to :user
belongs_to :post
validates :content, :presence => true,
:length => {:within => 6..200}
end
In your post controller show action, make the new comment belong to the post
def show
#post = Post.where(:id => params[:id]).first
if #post.nil?
flash[:error] = "Post does not exist"
redirect_to(root_path)
end
#comment = #post.comments.new # <--- here's the change
end
Then add the post_id field to the form as a hidden field
<%= form_for #comment do |f| %>
<%= f.hidden_field :post_id %>
<%= f.text_area(:content, :size => '20x10', :class => 'textarea') %>
<%= f.submit('Create Post', class: 'button button-primary') %>
<% end %>
And you should be good to go without changing the comment controller create action
So I have seen other articles here on stack about this and a lot of the time people are not doing #post = post.new. I read some where to use the plural...??
any ways I am getting this error on my discussion code:
Model
class Discussion < ActiveRecord::Base
has_many :comment
belongs_to :author
attr_accessible :author_id, :content, :title
validate :comment, :presence => true
validate :title, :presence => true
end
Discussion Controller
class DiscussionsController < ApplicationController
def index
#discussion = Discussion.new
#discussions = Discussion.all
end
def create
#discussion = Discussion.create(params[:discussion])
if #discussion.save
redirect_to tasks_path, :flash => {:success => 'Created a new discussion'}
else
redirect_to tasks_path, :flash => {:error => 'Failed to create a discussion'}
end
end
end
Discussion Form
<%= form_for #discussion do |f| %>
<p><%= f.label :title %>
<%= f.text_field :title %></p>
<p><%= f.label :content %>
<%= f.text_area :content %></p>
<% end %>
Discussion Routes
resources :discussions do
resources :comments
end
Now as far as I know I am doing this right, because I have a task form set up essentially the same way - but I have looked at my code for hours and have googled and tried other examples and now i see this:
undefined method `model_name' for NilClass:Class
Extracted source (around line #1):
1: <%= form_for #discussion do |f| %>
2:
3: <p><%= f.label :title %>
4: <%= f.text_field :title %></p>
Which should mean that I am missing something from my controller.....is it as asilly as a spelling mistake? >.>
Have you tried putting this in your discussion controller?
def new
#discussion = Discussion.new
end
I believe your problem is that you are trying to create a discussion on a task form but have only defined the discussion controller and not the task controller.
u have to add :method => :post to the form for creating the object else the form will get submitted with GET request.
<%= form_for #discussion , :method => :post do |f| %>
Is that the index view that has the form_for?
If not, then you should add a new action to the controller and do #discussion = Discussion.new there and not in your index action.
if ur model relationships are exactly as u have provided, then they are incorrect
class Discussion < ActiveRecord::Base
has_many :comment #has_many :comments
belongs_to :author
attr_accessible :author_id, :content, :title
validate :comment, :presence => true #valide :comments, :presence => true
validate :title, :presence => true
end
Currently I am have a comment which belongs to a micropost, but the issue is, when a user creates a comment, the comment gets stored in the database with a micropost id but the id is not for the specific micropost rather it seem as though the comment just incremented the micropost id by + 1. Very confused and would very much appreciate any help. Thank you!
Comment Model
class Comment < ActiveRecord::Base
attr_accessible :content, :user_id, :micropost_id
belongs_to :micropost
belongs_to :user
validates :content, presence: true, length: { maximum: 140 }
default_scope order: 'comments.created_at DESC'
end
Micropost Model
class Micropost < ActiveRecord::Base
attr_accessible :title, :content, :view_count
belongs_to :user
has_many :comments
accepts_nested_attributes_for :comments
end
Comments Controller
class CommentsController < ApplicationController
def create
#micropost = Micropost.find(params[:micropost_id])
#comment = #micropost.comments.build(params[:comment])
#comment.user_id = current_user.id
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
Form
<div class="CommentField">
<%= form_for ([#micropost, #micropost.comments.new]) do |f| %>
<%= f.text_area :content, :class => "CommentText", :placeholder => "Write a Comment..." %>
<div class="CommentButtonContainer">
<%= f.submit "Comment", :class => "CommentButton b1" %>
</div>
<% end %>
</div>
Routes
resources :microposts do
resources :comments
end
Raked Routes
micropost_comments GET /microposts/:micropost_id/comments(.:format) comments#index
POST /microposts/:micropost_id/comments(.:format) comments#create
new_micropost_comment GET /microposts/:micropost_id/comments/new(.:format) comments#new
edit_micropost_comment GET /microposts/:micropost_id/comments/:id/edit(.:format) comments#edit
micropost_comment GET /microposts/:micropost_id/comments/:id(.:format) comments#show
PUT /microposts/:micropost_id/comments/:id(.:format) comments#update
DELETE /microposts/:micropost_id/comments/:id(.:format) comments#destroy
I think the issue here is how much work you are putting into this. Rails is built to know about most of this without the need to do what you are doing. My suggestion would be to change your comments controller to something like this
class CommentsController < ApplicationController
def create
#comment = Comment.new(params[:comment])
#comment.save
respond_to do |format|
format.html
format.js
end
end
end
since you are rendering your comments partial form through another partial you'll need to pass along the local variable of the associated post above it.
"comments/form", :locals => { :micropost => micropost } %>
and your form to something like this
<div class="CommentField">
<%= form_for ([micropost, #comment]) do |f| %>
<%= f.text_area :content, :class => "CommentText", :placeholder => "Write a Comment..." %>
<div class="CommentButtonContainer">
<%= f.submit "Comment", :class => "CommentButton b1" %>
</div>
<% end %>
</div>
In all my rails apps, that is all the association I would need to do for it to properly assign the Ids by itself. I'm sure that will fix the issue.
I have a model "Issue" and a nested Model "Relationship"
In the issue.rb I have mentioned:
has_many :relationships, :dependent => :destroy
accepts_nested_attributes_for :relationships, :allow_destroy => true
In relationship.rb I have mentioned:
belongs_to :issue
Following Ryan Bates Railcast#196 I have the following in my issues_controller:
relationship = #issue.relationships.build
However, I am encountering an error "unknown attribute: relationship"
Am I doing something incorrectly here? I do see the Relationships Attributes being passed to the server in the log however, this error does not let the create to be successful.
My expertise with rails is beginners level so kindly excuse me if I am asking a question which maybe deemed trivial.
Thanks for the help.
EDIT: The relevant Controller code:
#relationship = #issue.relationships.build
##relationship = Relationship.new(params[:relationship])
if #relationship.issue_id = ''
#relationship.issue_id = #issueid
end
if #relationship.cause_id = ''
#relationship.cause_id = #issueid
end
#relationship.save
redirect_to(:back, :notice => 'New Relationship was created')
What I see on the trace:
ActiveRecord::UnknownAttributeError in IssuesController#create
unknown attribute: relationship
Among the Issue parameters, I see the Relationship params being passed as expected:
"relationship"=>{"issue_id"=>"100",
"cause_id"=>""}
ANOTHER UPDATE
Posting the form_for code:
- form_for Issue.new do |f|
.field
= f.text_field :description, :class=>"formfield", :id=>"frm_descr"
.field
= f.hidden_field :wiki_url, :class=>"formfield", :id=>"frm_wiki_url"
.field
= f.hidden_field :short_url, :class=>"formfield", :id=>"frm_img_url"
.field
= f.hidden_field :title, :class=>"formfield", :id=>"frm_title"
= f.fields_for :relationship do |builder|
= builder.text_field :issue_id, :class=>"form_field", :id=>"frm_rel_issue_id", :value=>#issue.id
= builder.text_field :cause_id, :class=>"form_field", :id=>"frm_rel_cause_id"
.actions
= f.submit 'Create', :class=>"save_button", :name=>"save_issue_rel_button", :id=>"val_collector"
Change this line
= f.fields_for :relationship do |builder|
to this:
= f.fields_for :relationships do |builder|
Your issue has_many relationships - plural. That will give you the correct relationships_attributes parameters.
Here is the working skeleton code:
I created a new project and tried the combination of the other answers, and finally made it to work.
Here is my solution, after that are the things to watch out for. I am using different models so bear with me:
My models are: discussion has_many posts.
Discussion has no attributes.
Posts has content:text and discussion_id:integer.
Working Code
(model) discussion.rb
has_many :posts
accepts_nested_attributes_for :posts
(model) post.rb
belongs_to :discussion
routes.rb
resources :discussions do
resources :posts
end
(discussion view) _form.html.erb
<%= form_for(#discussion) do |f| %>
<%= f.fields_for :posts, #post do |p| %>
<%= p.text_area :content %>
<% end %>
<%= f.submit %>
<% end %>
(controller) discussions_controller.rb
def new
#discussion = Discussion.new
#post = #discussion.posts.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #discussion }
end
end
def create
#discussion = Discussion.new(params[:discussion])
respond_to do |format|
if #discussion.save
format.html { redirect_to(#discussion, :notice => 'Discussion was successfully created.') }
format.xml { render :xml => #discussion, :status => :created, :location => #discussion }
else
format.html { render :action => "new" }
format.xml { render :xml => #discussion.errors, :status => :unprocessable_entity }
end
end
end
Possible things that can go wrong
First, Thilo was right, I get unknown attribute: post if I do
# WRONG!
f.fields_for :post
Second, I have to have the #post instance variable in new action otherwise the post.context textarea will not show up.
# REQUIRED!
#post = #discussion.posts.build
Third, If I use the f.fields_for #post, the create action will complain unknown attribute: post too.
# WRONG!
f.fields_for #post do |p|
Use this instead:
# RIGHT!
f.fields_for :posts, #post do |p|
The End
So yeah, I wish we get to see more documentations on this (can't see any useful ones). For example I see some use of form_for [#discussion, #post] but I can never get it to work.
By using accepts_nested_attributes, you have created a setter method relationship_attributes=.
There are a couple of things I noticed that need to change.
You don't need to set
#relationship = #issue.relationships.build
Your form should be the following (you have f.fields_for :relationship)
= form_for #issue do |f|
# your issue fields here
= f.fields_for :relationships do |r|
# your relationship fields here
The beauty here is that you won't have to set any ids or anything.
I assume you are constructing the relationship in your controller and then trying to use it in the view. In order for this to be visible, you must make it an instance variable. All you need to do is throw an # symbol in from of the name of relationship, as you have done with #issue.
#relationship = #issue.relationships.build
Edit: due to further information provided by the OP after the original question was asked, this answer is now clearly not applicable.
I can't seem to find an example that is complete in all the components. I am having a hard time deleting image attachments
Classes
class Product
has_many :product_images, :dependent => :destroy
accepts_nested_attributes_for :product_images
end
class ProductImage
belongs_to :product
has_attached_file :image #(etc)
end
View
<%= semantic_form_for [:admin, #product], :html => {:multipart => true} do |f| %>
<%= f.inputs "Images" do %>
<%= f.semantic_fields_for :product_images do |product_image| %>
<% unless product_image.object.new_record? %>
<%= product_image.input :_destroy, :as => :boolean,
:label => image_tag(product_image.object.image.url(:thumb)) %>
<% else %>
<%= product_image.input :image, :as => :file, :name => "Add Image" %>
<% end %>
<% end %>
<% end %>
<% end %>
Controller
class Admin::ProductsController < AdminsController
def edit
#product = Product.find_by_permalink(params[:id])
3.times {#product.product_images.build} # added this to create add slots
end
def update
#product = Product.find_by_permalink(params[:id])
if #product.update_attributes(params[:product])
flash[:notice] = "Successfully updated product."
redirect_to [:admin, #product]
else
flash[:error] = #product.errors.full_messages
render :action => 'edit'
end
end
end
Looks good, but, literally nothing happens when I check the checkbox.
In the request I see:
"product"=>{"manufacturer_id"=>"2", "size"=>"", "cost"=>"5995.0",
"product_images_attributes"=>{"0"=>{"id"=>"2", "_destroy"=>"1"}}
But nothing gets updated and the product image is not saved.
Am I missing something fundamental about how 'accepts_nested_attributes_for' works?
From the API docs for ActiveRecord::NestedAttributes::ClassMethods
:allow_destroy
If true, destroys any members from the attributes hash with a _destroy key and a value that evaluates to true (eg. 1, ‘1’, true, or ‘true’). This option is off by default.
So:
accepts_nested_attributes_for :product_images, allow_destroy: true