Rails 3 Nested Resources Edit/Update method - routing error - ruby-on-rails

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!

Related

RAILS 4 - How to show NAME instead of ID in index view?

rails newbie here.
I want my questions/view/index to show the name of the language associated with a question, rather than the language_id.
My question model is:
class Question < ActiveRecord::Base
validates :phrase, presence: true
has_many :answers, dependent: :delete_all
belongs_to :language
end
class CreateQuestions < ActiveRecord::Migration
def change
create_table :questions do |t|
t.string :phrase
t.string :language
t.timestamps
end
end
end
class AddLanguageIdToQuestions < ActiveRecord::Migration
def change
add_column :questions, :language_id, :integer
end
end
My language model is:
class Language < ActiveRecord::Base
end
class CreateLanguages < ActiveRecord::Migration
def change
create_table :languages do |t|
t.string :name
t.timestamps
end
end
end
In my questions controller:
def index
#questions = Question.all
#language = Language.find(#questions.language_id)
end
In the questions/_form.html.erb:
<p>
<%= f.label :language_id %><br>
<%= f.select :language_id, #languages.map { |l| [l.name, l.id] }, {:prompt => 'select language'} %>
</p>
And in the questions/view/index.html.erb:
<% #questions.each do |question| %>
<li>"<%= link_to question.phrase, question %>" in <%= question.language.name %>?%></li>
<% end %>
The error I keep getting, despite trying several variations of "question.language.name" (which works just fine in the show view) is "undefined method "language_id" in the index view.
Any help would be very much appreciated.
just change your index action to:
def index
#questions = Question.all.includes(:language)
end
edit
<% #questions.each do |question| %>
<% unless question.language.nil? %>
<li>"<%= link_to question.phrase, question %>" in <%= question.language.name %>?%></li> <% end %>
<% end %>
OR
<% #questions.each do |question| %>
<li>"<%= link_to question.phrase, question %>" in <%= question.language.name unless question.language.nil? %>?%></li>
<% end %>
Both will work fine depends on you what do you want.
feel free to ask if problem continues or not solved.
Your Problem
What you were doing wrong was:
finding all questions and then this line was wrong finding all question's language at once.
#language = Language.find(#questions.language_id)
And to avoid this: better solution is to avoid N + 1 query problem using includes
ActiveRecord Associations
This sounds like a job for ActiveRecord Associations, specifically the has_many association:
ActiveRecord associations basically use a foreign_key in your database to pull relational data & append to your object. Currently, you're only focused on using a single object, without any associated data.
--
Your problem can be fixed using the following:
#app/models/question.rb
Class Question < ActiveRecord::Base
belongs_to :language
end
#app/models/language.rb
Class Language < ActiveRecord::Base
has_many :questions
end
This will allow you to call the following in your controller & view:
#app/controllers/questions_controller.rb
Class QuestionsController < ApplicationController
def index
#questions = Question.all
end
end
#app/views/questions/index.html.erb
<% #questions.each do |question| %>
<%= question.language.name %>
<% end %>
--
Bonus
You can use the .delegate method to provide you with the ability to stop the law of dementer issue:
#app/models/question.rb
Class Question < ActiveRecord::Base
belongs_to :language
delegate :name, to: :language, prefix: true #-> #question.language_name
end
#questions.language_id is incorrect because you are trying to retrieve a single id from an array. i.e. Question.all returns an array. Also, Language.find requires a single id parameter to return a single language.
What exactly are you trying to return with Language.find(#questions.language_id) ? An array of languages that have a question that belong to it?
Also, where is the _form.html.erb partial called? By default, rails scaffold will call the partial in the new and edit actions and therefore if you are trying to set #languages for the select field in the form partial, then you would not do it in the index action. Also, Consider using a collection select for this field as it is for an association.

Simple comments with voting and karma

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!

Adding threading and email subscriptions to comments [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I've built a simple rails app with three models, Posts, Users, and Comments.
I've tried every commenting gem out there and they all have some shortfall.
So I build my own comments system.
Users can comment on Posts. Every comment is votable (using acts_as_votable gem). A users score is made up by the sum total votes they have received on their comments.
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"
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 comment model:
class Comment < ActiveRecord::Base
belongs_to :post
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 %>
In the user profile views: (this shows the users score
<%= (#user.comments.map{|c| c.votes.count}.inject(:+))%>
How do I implement threading?(at one level, I'm assuming multiple levels just makes it really messy)
How do I make the threaded comments votable?(both the parents and children) What has to be done with the routes?
How do I ad a simple email notification a user can subscribe to to receive a simple message saying that a new comment has been posted to their thread?
How do I pull the users score calculated by all votes received on comments made by the user including child comments?
If I understand the question correctly, you want to allow comments to be commented on. In that case, in your Comment model you will need a parent_id:integer attribute. Then add the following associations :
class Comment
...
belongs_to :parent, class_name: 'Comment'
has_many :children, class_name: 'Comment', foreign_key: 'parent_id'
...
end
Now you have a tree structure for your comments. This allows comments of comments of comments and so on.
if my_comment.parent.nil? then you are at the root. if my_comment.children.empty? then there are no comments on the comment.
Trees can be expensive to move through, so adding a max depth could be smart.
How do you implement threading? (To answer one of your questions)
Make the comment-to-user association polymorphic, and then you can add a comment-to-comment association the same way.
What was the 'shortfall' you found with existing gems that prevented you doing this? (Since acts_as_commentable supports this out if the box)

Create comments for two models from comments controller ruby on rails

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!

rails: connecting model with 2 other models

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

Resources