Ruby on rails newbie here, I have previously been using CakePHP and wanted to allow my create comments controller to create events for two models, any help please?
My comments controller:
def create
#event = Event.find(params[:event_id])
#comment = #event.comments.create(params[:comment].permit(:commenter, :body))
redirect_to event_path(#event)
end
def create
#venue = Venue.find(params[:venue_id])
#comment = #venue.comments.create(params[:comment].permit(:commenter, :body))
redirect_to venue_path(#venue)
end
My create comments view:
<h2>Add a comment:</h2>
<%= form_for([#event, #event.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
This is a classic example for polymorphic association.
There would be a bit of tweaking to get it to work right.
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
...
end
class Venue < ActiveRecord::Base
has_many :comments, as: :commentable
...
end
class Event < ActiveRecord::Base
has_many :comments, as: :commentable
...
end
This will add to your comments Model another attribute called commentable_type so you could differ the types of comments for each Model (Venue, Event)
You would have to run a migration that looks mostly like this
def change
create_table :comments do |t|
t.integer :commenter
t.text :body
t.integer :commentable_id
t.string :commentable_type
t.timestamps
end
end
Now when you migrate you can go to your rails console and see that if you try
Venue.first.comments << Comment.create!(:body => "Body", :commenter => "Guy") # or commenter => 1 depending on your schema
It will be saved to the database as a comment and you can also do the same thing for Event
Now as for your comments controller, I would advise against creating a global comments controller and rather have follow the RESTful approach and have each controller handle his comments.
I.E
# routes.rb
resources :venues
resources :comments
end
resources :events do
resources :comments
end
This way you can both tweak your views according to each controller (venue / events), you follow the RESTful approach as you can use this with either HTML/JSON/XML, you get nicer routes
/events/1/comments # index for all the comments for event 1
/events/1/comments/new # your add a comment form
and same goes for venue.
You can find more info on associations in here http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Good luck!
Related
I've tried every commenting gem out there and they pretty much all suck.
Take a look at this question I previously asked:Finding user total votes recieved on posts by other users
Per recommendation, I've decided to build my own commenting system from scratch.
Here is the goal:
To have a post model, user model(using devise), comment model.
The users can create comments. These comments are votable. The amount sum of votes users receive on the comments they made is their score or karma.
How do I implement something like this?
So far this is what I have:
I ran
rails generate model Comment commenter:string body:text post:references
The migration
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :post
t.timestamps
end
add_index :comments, :post_id
end
end
In my comment model
class Comment < ActiveRecord::Base
belongs_to :post
attr_accessible :commenter, :body
end
In my post model
class Post < ActiveRecord::Base
has_many :comments
end
My routes file
resources :posts do
resources :comments
end
My comments controller
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create(params[:comment].permit(:commenter, :body))
redirect_to post_path(#post)
end
end
Within my posts show view
<h2>Comments</h2>
<% #post.comments.each do |comment| %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<% end %>
<%= form_for([#post, #post.comments.build]) do |f| %>
<p>
<%= f.label :commenter %><br />
<%= f.text_field :commenter %>
</p>
<p>
<%= f.label :body %><br />
<%= f.text_area :body %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
<%= link_to 'Edit Post', edit_post_path(#post) %> |
<%= link_to 'Back to Posts', posts_path %>
I really need help here: Instead of picking a commenter name, I need the controller to require the user to be logged in, and pass the current user as the commentor of the comment. How do I implement in place editing of a comment?
I then need to use either one of these gems to make the comments votable:
https://github.com/bouchard/thumbs_up
https://github.com/ryanto/acts_as_votable
Lastly I need to be able to calculate the total votes a given user has received on all of their posted comments. Something like #user.comments.votes.size
To handle assigning the current_user to the comment, first you'll need to change the commenter column to an id that references Users (I would also rename it to commenter_id). So, you'll want to generate the model like so:
rails generate migration ChangeCommentsCommenterToCommenterId
# db/migrate/<timestamp>_change_comments_commenter_to_commenter_id.rb
class ChangeCommentsCommenterToCommenterId < ActiveRecord::Migration
def change
remove_column :comments, :commenter
add_column :comments, :commenter_id, :integer, null: false
add_index :comments, :commenter_id
end
end
Or, regenerate the model from scratch:
rails generate model Comment commenter_id:integer:index body:text post:references
Note that I've added an index to the column. In your Comment model:
# app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :commenter, class_name: 'User'
attr_accessible :body
end
Note that, since we're using belongs_to here, when you send the commenter message to an instance of Comment, you'll get back an instance of User.
Next you'll need to update your controller to make the proper user assignment. I would also recommend a bit of refactoring to private methods to make the implementation more expressive of the domain:
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.commenter = current_user
end
redirect_to post_path(post)
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post ||= Post.find(params[:post_id])
end
end
Since you're redirecting to post_path, I've assumed that you don't need to keep the #comment instance variable.
Finally, you'll want to remove the commenter field from the form in the view. Does that do the trick?
[EDIT: Adding this section to address the question about voting...]
I'm not completely clear on exactly what parts of the voting you're looking for help with, but at least from a high-level perspective, I'd guess you'd probably want a VotesController that's accessible from a nested route:
# config/routes.rb
...
resources :comments do
resources :votes, except: :index
end
Hope this helps!
Within my rails app, I currently have comments setup to work with my posts model, which is functioning properly. How do I add comments to my books model?
Here is what I have so far:
Here is what I have in my schema for the comments:
create_table "comments", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "post_id"
t.integer "book_id"
end
In my user model:
class User < ActiveRecord::Base
has_many :comments
acts_as_voter
end
In my post model:
class Post < ActiveRecord::Base
has_many :comments
end
In my book model:
class Book < ActiveRecord::Base
has_many :comments
end
In my comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :book
belongs_to :user
acts_as_votable
end
In my comments controller:
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.user = current_user
end
respond_to do |format|
format.html {redirect_to post_path(post)}
end
end
def upvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.liked_by current_user
respond_to do |format|
format.html {redirect_to #post}
end
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post = Post.find(params[:post_id])
end
end
In my routes file:
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
end
end
end
In my view:
<% #post.comments.each do |comment| %>
<%= comment.body %>
<% if user_signed_in? && (current_user != comment.user) && !(current_user.voted_for? comment) %>
<%= link_to “up vote”, like_post_comment_path(#post, comment), method: :put %>
<%= comment.votes.size %>
<% else %>
<%= comment.votes.size %></a>
<% end %>
<% end %>
<br />
<%= form_for([#post, #post.comments.build]) do |f| %>
<p><%= f.text_area :body, :cols => "80", :rows => "10" %></p>
<p><%= f.submit “comment” %></p>
<% end %>
What do I add to my comments controller to get comments working on both posts and books? What do I add to my routes file?
Thanks in advance for any help.
You don't want to specify each type of object that can hold Comment objects. That creates a headache of if-elsif-else blocks all over the place. Instead, you want things to be Commentable, and they all will have .comments on them.
This is called a polymorphic association in Active Record. So you would have your models something like:
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
class Post < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Book < ActiveRecord::Base
has_many :comments, as: :commentable
end
And modify your database accordingly, it's all in the linked article. Now when you build a Comment object for a form, it will have pre-populated a commentable_id and commentable_type, which you can toss in hidden fields. Now it doesn't matter what the Comment is associated with, you always treat it the same.
I'd leave User as a separate association, since it's not really the same idea.
Hi I have the following model:
Boys have_many relationships
Girls have_many relationships
Relationship belongs to boy
Relationship belongs to girl
I have the following so far:
def create
#boy = Boy.find(current_boy.id)
#relationship = #boy.relationships.create(:relationship)
redirect_to boy_path(#boy)
end
This is my home.html.erb
<%= form_for([#boy, #boy.relationships.build]) do |f| %>
<div class="field">
<%= f.label :points %><br />
<%= f.number_field :points %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Migration file:
def change
create_table :relationships do |t|
t.integer :points
t.references :boy
t.references :girl
t.timestamps
end
add_index :relationships, :boy_id
add_index :relationships, :girl_id
end
So relationships have "points." When I submit the form, I want to specify the email of the girl as well.
How do I put that as part of the form and fix the create method so when I submit this form, I create a relationship between a boy and a girl?
Many thanks.
First, I'd consider having a Person (or whatever you prefer) model with a gender attribute. That'll greatly simplify the following:
Nest your resources:
resources :people do
resources :relationships
end
Tell the model about it:
class Person < ActiveRecord::Base
...
accepts_nested_attributes_for :relationships
...
end
In app/views/relationships/_form the form_for should take #person and #relationship.
Then, after a person is created they can navigate to /people/1/relationships#index where they can do all the CRUD stuff.
This is out of date but still useful: http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
I need some help with nested resource actions. I have three nested resources: Jobs, Questions and Answers. I am currently only trying to get the edit/update method to work for the questions controller. The relationship is as so: Jobs has_many questions and Questions belong to Jobs.
I am using the edit action on the questions and am getting an error:
No route matches "/jobs/1/questions"
and I cannot figure out why.
I currently have this code as my edit and update action in my Questions controller:
def edit
#job = Job.find(params[:job_id])
#question = #job.questions.find(params[:id])
end
def update
#job = Job.find(params[:job_id])
#question = #job.questions.find(params[:id])
if #question.update_attributes(params[:question])
redirect_to(#question)
end
end
Models:
class Job < ActiveRecord::Base
has_many :questions
class Question < ActiveRecord::Base
belongs_to :job
Routes:
resources :jobs do
resources :questions do
resources :answers
end
end
The things that I don't understand are:
a) why is it redirecting me to the questions index path, when I didn't redirect it there, and
b) It says that is not a valid route, but if I refresh that exact URL the page loads properly.
I have tried multiple options, but I can't figure out the solution.
Thanks for the help. Let me know if you need more info.
p.s. here is my rake routes : https://gist.github.com/1077134
To get you started,
In view/jobs/show.rb :
<%= link_to 'Edit', edit_jobs_path(#job) %>
In view/questions/show.rb :
<%= link_to 'Edit', edit_job_question_path(#question.job, #question) %>
In view/questions/edit.rb :
<%= link_to 'Show', job_question_path %>
What I'm showing is that the links need to have a nested pattern. If your answers had many comments, you might end up with things like:
edit_job_question_answer_comment(#job, #question, #answer, #comment)
where the #symboled variables are derived in the controller.
Hope this helps!
You may later want:
class Job < ActiveRecord::Base
has_many :questions
has_many :answer, :through => :questions
# If you want to edit the questions of a job whilst editing a job then research accepts nested attributes
#accepts_nested_attributes_for :questions, :allow_destroy => true
end
So it turns out that my issue was a little more involved than I had originally thought. My database and tables were not setup properly and they were having trouble finding the proper :ids for my resources. I had to start by normalizing my tables like so:
class CreateQuestions < ActiveRecord::Migration
def self.up
create_table :questions do |t|
t.references :job
t.text :question1
t.text :question2
t.text :question3
t.text :question4
t.text :question5
t.text :question6
t.text :question7
t.text :question8
t.text :question9
t.text :question10
t.timestamps
end
end
This set up was repetitive and dirty and it was messing up questions controller actions. So I changed it to:
def self.up
create_table :questions do |t|
t.references :job
t.text :question
t.timestamps
end
end
and created nested_forms with loops in my jobs (the parent resource) new_form view.
<%= form_for(#job) do |f| %>
<%= f.label :name %><br />
<%= f.text_field :name %>
<%= f.fields_for :questions do |builder| %>
<%= f.label :question, "Question" %><br \>
<%= f.text_area :question, :rows => 10 %>
<% end %>
After doing this all of my controller methods were cleaner and the edit/update action was working properly.
This is how I solved the issue, it may not be the best way to do so. Also, if you have anything to add or any questions about my code, just let me know and I'll see if I can help.
Thanks!
I am trying to follow Ryan Bates screencast but have an error message. I did the following:
1) Create table
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.references :commentable, :polymorphic => true
2) Setup models
class Comment < ActiveRecord::Base
belongs_to :commentable, :polymorphic => true
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
has_many :comments, :as => :commentable
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
has_many :comments, :as => :commentable
3) Change controller show action
class CategoriesController < ApplicationController
def show
#category = Category.find_by_permalink(params[:id])
#commentable = #category
#comment = Comment.new(:commentable => #category)
end
4) Add a form to template views/categories/show.html.erb
<% form_for [#commentable, Comment.new] do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :content %><br />
<%= f.text_area :content %>
</p>
<p>
<%= f.submit 'Submit' %>
</p>
<% end %>
5) After that I get error message by accessing /categories/my-category-permalink
NoMethodError in Categories#show
undefined method `category_comments_path' for #<ActionView::Base:0x69a9254>
Could you help me to understand what I did wrong?
In the original screencast Ryan accesses comments by /categories/permalink/comments using nested associations, but I don't need that. I want to write comments directly from my polymorphic objects.
Thanks
The problem was in routes settings. I thought that since I don't use nested resources, I can keep routes unchanged. Well, now I know that I was wrong... :) Add this to fix the problem:
map.resources :categories :has_many => :comments
map.resources :products, :has_many => :comments