I am following Ryan Bate's tutorial: http://railscasts.com/episodes/163-self-referential-association
But my setup is slightly different.
I am making comments that are self-referential so that comments can be commented on.
The form displays in the view, but when I submit, I get this :
Routing Error
No route matches "/conversations"
And my url says this : http://localhost:3000/conversations?convo_id=1
models
#conversation.rb
belongs_to :comment
belongs_to :convo, :class_name => "Comment"
#comment.rb
belongs_to :post
has_many :conversations
has_many :convos, :through => :conversations
My form :
- for comment in #comments
.grid_7.post.alpha.omega
= comment.text
%br/
- form_for comment, :url => conversations_path(:convo_id => comment), :method => :post do |f|
= f.label 'Comment'
%br/
= f.text_area :text
%br/
= f.submit 'Submit'
My conversations_controller:
def create
#conversation = comment.conversations.build(:convo_id => params[:convo_id])
The app fails here in its creation as it never makes it to the redirect portion of the create method.
There are several pieces to work on here, but the good news is I think the answer you're looking for is simpler than what you already have. If I understand you correctly, you want comments to have many child comments of their own. This is how YouTube works, letting members reply to existing comments. For this, you don't need the has_many :through solution you've implemented. You don't need the conversations object at all. A comment may have many replies (child comments), but a reply isn't going to have more than one parent.
The answer for this is using polymorphism, which is easier to implement than it is to pronounce :) You want your comments to either belong to a post, or to another comment. Polymorphism lets an object belong to one of possibly many things. In fact, comments are the most common use for this.
I cover polymorphism with the example of addresses in this blog post:
http://kconrails.com/2010/10/19/common-addresses-using-polymorphism-and-nested-attributes-in-rails/
But I can show you how it applies to your case more specifically. First, drop the conversation model/controller/routes entirely. Then, change your comments table:
change_table :comments do |t|
t.integer :commentable_id
t.string :commentable_type
t.remove :post_id
end
We don't need post_id anymore, because we're going to change how we associate with other tables. Now let's change the models:
# app/models/post.rb
has_many :comments, :as => :commentable
# app/models/comment.rb
belongs_to :commentable, :polymorphic => true
has_many :comments, :as => :commentable
Notice we dropped the comment belonging to a post directly. Instead it connects to the polymorphic "commentable" association. Now you have an unlimited depth to comments having comments.
Now in your Post#show action, you'll want to create a blank comment like so:
get show
#post = Post.find(params[:id])
#comment = #post.comments.build
end
#comment will now have commentable_id and commentable_type set for you, automatically. Now in your show page, using erb:
<% form_for #comment do |f| %>
<%= f.hidden_field :commentable_type %>
<%= f.hidden_field :commentable_id %>
/* other fields go here */
<% end %>
Now when Comments#create is called, it works like you'd expect, and attaches to the right parent. The example above was showing a comment being added directly to a post, but the process is essentially the same for a comment. In the controller you'd call #comment.comments.build, and the form itself would stay the same.
I hope this helps!
The app is failing sooner than you think - it's not finding a route to that action, so it's not reaching it at all. In your routes.rb file, you need to add:
# rails 3
resources :conversations
# rails 2
map.resources :conversations
This should fix it.
Related
I have two models in my app. One is called meetings and the other is outcome. I wanted to create the outcome of each meeting using: #outcome=current_meeting.outcome.build(params[:outcome]). Further, each meeting would have only one outcome so it is clearly a has_one relationship. I am really confused about getting the current_meeting. I have the meetings and outcomes models as:
Meeting Model
class Meeting < ActiveRecord::Base
attr_accessible :info, :meeting_date, :name, :venue
has_many :participants, :dependent => :destroy
has_one :outcome, :dependent => :destroy
validates_presence_of :name, :info, :meeting_date, :venue
end
Outcome Model
class Outcome < ActiveRecord::Base
attr_accessible :result
belongs_to :meeting
validates :meeting_id, presence: true
end
I want the new outcome to be present inside the show of the meeting, if there are none present and if there is one present creating new outcome should be made impossible
meetings/show.html.erb
<% if #meeting.outcomes.any? %>
<%= render #outcomes %>
<% else %>
<%= link_to "Add the outcome of meeting", new_outcome_path %>
<% end %>
My controllers are:
Meetings controller
def show
#meeting = Meeting.find(params[:id])
#outcomes = #meeting.outcomes.all
end
Outcomes controller
def new
#outcome = current_meeting.microposts.new
end
def create
#outcome = current_meeting.outcomes.build(params[:outcome])
if #outcome.save
flash[:success] = "Outcome created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
I don't know how to find out the current_meeting. Please help.
First of all, the question is very confusing as to the plurality of outcome vs outcomes. If a Meeting has_one Outcome then you you would use the singular form when referring to the reference. Basically, given has_one :outcome, ":outcome" is the method name to be used. So you'd say meeting.outcome instead of meeting.outcomes. And the build method for has_one would be like meeting.build_outcome instead of meeting.outcomes.build. The latter is the api for a has_many relationship.
With that out of the way, if you want to get the current Meeting from the Outcomes controller, the best way to do this is with nested resources. So in the routes file you'd have, e.g.:
resources :meetings do
resources :outcomes
end
After you do that, run rake routes to see the routes available to you. In there you'll see an expected url format of POST /meetings/:id/outcomes which you would use here. So in this case, the create method would get the Meeting object from params[:id], from which the outcome relationship can be created.
At first glance it does not seem like you are defining current_meeting anywhere. You probably already know this and if so the case would be that you are unsure of how/where to define it. You will probably need to do that somewhere in the code. This could mean saying something like this meeting is current because it is during the current time and/or today. This is based on how your app works to determine this logic.
In your controller or in a helper you will need to write a method that gives you the current meeting if one exists. From there the current_meeting variable in your controller will be set correctly and should call your other methods right.
If I have misunderstood the issue I apologize and please provide any other details you can and I can try to help.
Ok, am still a newbie in ruby on rails trying to learn my way around. I have two models (User model and Comment model). Basically a user has a simple profile with an 'about me' section and a photo's section on the same page. Users must be signed in to comment on other users profiles.
My User Model
class User < ActiveRecord::Base
attr_accessible :email, :name, :username, :gender, :password, :password_confirmation
has_secure_password
has_many :comments
.
.
end
My Comment Model
class Comment < ActiveRecord::Base
belongs_to :user
attr_accessible :content
.
.
end
In my comments table, I have a user_id column that stores the id of the user whose profile has been commented on and a commenter_id column that stores the id of the user commenting on the profile.
Comment Form
<%= form_for([#user, #user.comments.build]) do |f| %>
<%= f.text_area :content, cols: "45", rows: "3", class: "btn-block comment-box" %>
<%= f.submit "Comment", class: "btn" %>
<% end %>
My comments Controller
class CommentsController < ApplicationController
def create
#user = User.find(params[:user_id])
#comment = #user.comments.build(params[:comment])
#comment.commenter_id = current_user.id
if #comment.save
.........
else
.........
end
end
end
This works fine storing both user_id and commenter_id in the database. My problem comes when displaying the user comments on the show page. I want to get the name of the user who commented on a specific profile.
In my user controller
def show
#user = User.find(params[:id])
#comments = #user.comments
end
I want to get the name of the user from the commenter_id but it keeps throwing errors undefined method 'commenter' for #<Comment:0x007f32b8c37430> when I try something like comment.commenter.name. However, comment.user.name works fine but it doesn't return what I want. Am guessing am not getting the associations right.
I need help getting the correct associations in the models so as to get the name from the commenter_id.
My last question, how do I catch errors in the comments form? Its not the usual form_for(#user) where you do like #user.errors.any?.
routes.rb
resources :users do
resources :comments, only: [:create, :destroy]
end
Try something like this in your models
class User < ActiveRecord::Base
has_many :received_comments, :class_name => "Comment", :foreign_key => "user_id"
has_many :given_comments, :class_name => "Comment", :foreign_key => "commenter_id"
end
class Comment < ActiveRecord::Base
belongs_to :user # comment about profile
belongs_to :commenter, :class_name => "User", :foreign_key => "commenter_id"
end
check out: http://guides.rubyonrails.org/association_basics.html
you can probably come up with better naming on the has_many collections, received and given were the best I could do on short notice :)
Note: foreign_key is option in many cases, left it in above - i think it helps with clarity
has_many fk refers to the the column in the many table (other table)
belongs_to fk refers to the column in the many table (this table)
I'm reading Beginning Rails 3. It creates a blog with Users who can post Articles and also post Comments to these Articles. They look like this:
class User < ActiveRecord::Base
attr_accessible :email, :password, :password_confirmation
attr_accessor :password
has_many :articles, :order => 'published_at DESC, title ASC',
:dependent => :nullify
has_many :replies, :through => :articles, :source => :comments
class Article < ActiveRecord::Base
attr_accessible :body, :excerpt, :location, :published_at, :title, :category_ids
belongs_to :user
has_many :comments
class Comment < ActiveRecord::Base
attr_accessible :article_id, :body, :email, :name
belongs_to :article
in app/views/comments/new.html.erb there's a form which begins like this:
<%= form_for([#article, #article.comments.new]) do |f| %>
My confusion lies in why form_for() has two parameters. What do they resolve to and why are they necessary?
thanks,
mike
Actually, in your example, you are calling form_forwith one parameter (which is Array). If you check the documentation you will see parameters it expects: form_for(record, options = {}, &proc).
In this case a record can be ActiveRecord object, or an Array (it can be also String, Symbol, or object that quacks like ActiveRecord). And when do you need to pass it an Array?
The simplest answer is, when you have a nested resource. Like in your example, you have defined Article has many Comments association. When you call rake routes, and have correctly defined routes, you will see that Rails has defined for you different routes for your nested resource, like: article_comments POST /article/:id/comments.
This is important, because you have to create valid URI for your form tag (well not you, Rails does it for you). For example:
form_for([#article, #comments])
What you are saying to Rails is: "Hey Rails, I am giving you Array of objects as a first parameter, because you need to know the URI for this nested resource. I want to create new comment in this form, so I will give you just initial instance of #comment = Comment.new. And please create this comment for this very article: #article = Article.find(:id)."
This is roughly similar to writing:
form_for(#comments, {:url => article_comments_path(#aticle.id)})
Of course, there is more to the story, but it should be enough, to grasp the idea.
This is a form for commenting on an article. So you, you need the Article you're commenting on (#article) and a new Comment instance (#article.comments.new). The form action for this form will be something like:
/articles/1/comments
It contains the id of the article you're commenting on, which you can use in your controller.
If you omit the #article like this: form_for #article.comments.new, the form action will looks like this:
/comments
In the controller you would have no way of knowing to which article the comment belongs.
Note that for this to work, you need to define a nested resource in your routes file.
Foreword (because it is my first rails post)
I love rails but currently it drives me crazy!
I played around with rails 2.3 two years ago. Now I started with rails 3.1. All just for fun. Rails is the best and most professional I've ever seen in web development.
But... Since one line of code has so much power it is pretty hard to learn it!!!
The rail magic is hard to get :-)
Problem
I've got a nested models and routs. One page has_many articles.
I render pages/show.html.haml and in the page I list all articles. Each article shall have buttom underneath to 'new','edit' and 'destroy'.
render #articles
Here is my views/articles/_article.html.haml
%li.article_list{:id=>"a#{article.id}"}
.article
.article_head
%h2
=article.title:class => "icon"), '#'
.clear
.article_content
= raw parse_content(article.content)
-if admin?
#article_menu
%ul
%li= link_to icon_new + 'New...', new_page_article_path(#page)
%li=# link_to icon_edit + 'Edit', edit_page_article_path(WHAT SHALL BE IN HERE???)
%li= link_to icon_destroy + 'Destroy', [article.page, article], :confirm => "#{article.title}\n\nAre you sure?", :method => :delete
My problem is this line
%li=# link_to icon_edit + 'Edit', edit_page_article_path(WHAT SHALL BE IN HERE???)
I want to edit the article but I don't get the clue how to! I've tried and googled for hours!
In general I've been following this nice guide: http://guides.rubyonrails.org/getting_started.html#associating-models
Additional information
models/article.rb
class Article < ActiveRecord::Base
attr_accessible :title, :content, :publish_at, :fan_only
belongs_to :page
validates :content, :presence => true
validates :page_id, :presence => true
default_scope :order => 'articles.created_at DESC'
end
models/page.rb
class Page < ActiveRecord::Base
attr_accessible :short_name, :title, :content, :fan_only
has_many :articles, :dependent => :destroy
end
routes.rb
resources :pages do
resources :articles
end
part of the articles_controller.rb
# GET /articles/1/edit
def edit
#page = Page.find(params[:page_id])
#article = #page.articles.find(params[:id])
end
rake routes should show you all the paths, and give you a hint as to what goes in there.
Based in the code you posted, the answer to what goes in there is that is should be a reference to a PageArticle, a Page or an Article - I'm not sure which it is, because I don't know what your models are.
With two models, Page and Article, and nested resources, then you should have a nested route in your routes.rb file that looks something like:
resources :pages do
resources :articles
end
...which sets up a nested route to /pages/:page_id/articles/:id/edit
...and which looks like edit_page_article_path(#page, #article) in your app
More detail on routing+nested resources: http://edgeguides.rubyonrails.org/routing.html#nested-resources
I have a couple classes that can each have comments:
class Movie < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Actor < ActiveRecord::Base
has_many :comments, :as => :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
end
How do I create a form for a new movie-comment? I added
resources :movies do
resources :comments
end
to my routes.rb, and tried new_movie_comment_path(#movie), but this gives me a form containing commentable_id and commentable_type [which I want to be populated automatically, not entered by the user directly]. I also tried creating the form myself:
form_for [#movie, Comment.new] do |f|
f.text_field :text
f.submit
end
(where "text" is a field in the Comment table)
but this doesn't work either.
I'm not actually sure how to associate a comment with a movie at all. For example,
c = Comment.create(:text => "This is a comment.", :commentable_id => 1, :commentable_type => "movie")
doesn't seem to create a comment associated to the movie with id 1. (Movie.find(1).comments returns an empty array.)
As you have created the polymorphic association in your model, you need not worry about that anymore in the view. You just need to do this in your Comments controller.
#movie = Movie.find(id) # Find the movie with which you want to associate the comment
#comment = #movie.comments.create(:text => "This is a comment") # you can also use build
# instead of create like #comment = #movie.comments.create(:text => "This is a comment")
# and then #comment.save
# The above line will build your new comment through the movie which you will be having in
# #movie.
# Also this line will automatically save fill the commentable_id as the id of movie and
# the commentable_type as Movie.
You're going to have to be more descriptive than "...but this doesn't work either," but the general idea is:
#movie.comments.create( :text => params[:movie][:comment][:text] )
More typically:
#movie.comments.create( params[:comment] ) # or params[:movie][:comment]
The important thing is that you find #movie first and create your associated objects through it. That way you won't have to worry about Commentable or types or anything.