I'm trying to implement a comment system for users in which each comment can be upvoted. I have a model UpVote which polymorphically belongs_to multiple models, including Comments, while each User has_many Comments.
UpVote Model:
class UpVote < ActiveRecord::Base
belongs_to :voteable, polymorphic: true
end
Comment Model:
class Comment < ActiveRecord::Base
belongs_to :user
has_many :up_votes, as: :voteable
end
User Model:
class User < ActiveRecord::Base
has_many :comments
has_many :up_votes, as: :voteable
end
User Show: I put asterisks around the line with the error.
<% #user.comments.each do |c| %>
<%= c.text %>
<%= c.username %>
<%= c.id %>
***<%= link_to "upvote", upvote_comment_path(c.id), method: :post, :class => "btn btn-small" %>***
<% end %>
Comment Controller:
def upvote
#comment = Comment.find(params[:id])
UpVote.create!(voteable_id: params[:id], voteable_type: 'Comment')
redirect_to root_path
end
Routes:
post 'comments/:id/upvote' => 'comments#upvote', as: 'upvote_comment'
But when I submit the UpVote for the Comment on the User page, I get the following error:
ActionController::UrlGenerationError in Users#show
No route matches {:action=>"upvote", :controller=>"comments", :id=>nil} missing required keys: [:id]
It seems like the link_to isn't accepting the c.id, which is strange because c.id on its own prints the #comment.id normally. What am I doing wrong?
ERROR IN BOUNTY MESSAGE: I meant "#comment.id", not "#comment.up_vote.count" in the bounty message, but it doesn't appear I can edit that.
I suspect that you have a comment in the #user.comments that has an id of nil because it has not yet been persisted. This can happen if you are setting up a new comment on with either #user.comments.new or #user.comments.build. Both of these methods will add the blank comment to the #user.comments array.
There are a number of ways you can avoid displaying comments that are not persisted (see this question). I think the best solution is to avoid using build or new on the association. Instead if you need to display a new comment (such as in a form) prefer Comment.new over #user.comments.build or #user.comments.new.
Well the correct way to solve the problem is to modify the routes.rb, changing action upvote to be on: :member
resources :comments do
post :upvote, on: :member
end
OR
post 'comments/:id/upvote' => 'comments#upvote', as: 'upvote_comment'
What may be happening is that you have created a static rather than a resourceful route. In this case you may have to actually specify the id explicitly in your link_to
<%= link_to "upvote", upvote_comment_path(:id => c.id), method: :post, :class => "btn btn-small" %>
You can avoid this extra markup by creating a resourceful route as in you attempted in your example
resources :comments do
member do
post 'upvote'
end
end
Not sure why the "on: :member" syntax didn't work for you though as it is functionally equivalent to the block syntax. I believe the issue may be that you are specifying the action (upvote) with a symbol instead of string.
Good luck
Related
I search a solution on stackoverflow but there are different issues each time, so I decided to ask the question.
On my application, I have Travel, which has many posts. One user can create many travels, with many posts on one travel.
But when I try to create a post, I have this error :
ActiveRecord::RecordNotFound in PostsController#create
Couldn't find Travel with 'id'=
I don't understand why, so if someone could help me ..
Here is my Posts_controller.rb (create action):
def create
#travel = Travel.find(params[:id])
#post = #travel.posts.new(posts_params)
#post.user = current_user
if #post.save
flash[:success] = "Your post is published"
redirect_to user_path(current_user)
else
render 'new'
end
end
Here is my the models :
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :travel
geocoded_by :country
after_validation :geocode
end
class Travel < ApplicationRecord
has_many :posts
belongs_to :user
end
My routes :
# Travel
resources :travels, :shallow => true do
# Posts
resources :posts
end
And a ligne for form :
<%= form_for(#post, :html => {class: "form-horizontal", role: "form"}, :url => travel_posts_path(#travel)) do |f| %>
<div class="form-group">
<%= f.text_field :travel_id, placeholder: #travel.id %>
</div>
params[:id] will give you the post's id, and you're using it to find a travel. This could potentially not error and give you the wrong travel, but that's not what you're looking for. You specified travel_id in your form, so what you are looking for is params[:travel_id].
In the future you may want to use the debugger right at the top of the controller function and puts the params object to see what's inside. This will give you insight to if you're calling the wrong keys and what the structure looks like as things get complicated.
How can I set a helper method in my app that when I call it, recognizes a current_patient from a certain medic (can be multiple medics, and multiple patients for a medic) and access to patient :id, this would help me to associate:
Medic with a Patient with a Consultations for current_patient
I need to access on a patient and set id (patient) on a Consultation table foreign key :patient_id
in my create action in the controller I have:
def create
#consultation = Consultation.new(consultation_params.merge({:patient_id => current_patient}))
respond_to ...
end
Is a helper method a good way to do this?
How can I do this?
my models:
class Medic < ActiveRecord::Base
has_many :patients
end
class Patient < ActiveRecord::Base
belongs_to :medic, :foreign_key => :medic_id
has_many :consultations
accepts_nested_attributes_for :consultations
end
class Consultation < ActiveRecord::Base
belongs_to :patient, :foreign_key => :patient_id
end
Thanks for help
Lets start with the routes:
resources :patients do
resources :consultations
end
This will give us the routes for consultations nested under the patient:
Prefix Verb URI Pattern Controller#Action
patient_consultations GET /patients/:patient_id/consultations(.:format) consultations#index
POST /patients/:patient_id/consultations(.:format) consultations#create
new_patient_consultation GET /patients/:patient_id/consultations/new(.:format) consultations#new
edit_patient_consultation GET /patients/:patient_id/consultations/:id/edit(.:format) consultations#edit
patient_consultation GET /patients/:patient_id/consultations/:id(.:format) consultations#show
PATCH /patients/:patient_id/consultations/:id(.:format) consultations#update
PUT /patients/:patient_id/consultations/:id(.:format) consultations#update
DELETE /patients/:patient_id/consultations/:id(.:format) consultations#destroy
So lets say we have a PatientsController#index method which shows all the patients. And in our view we have something like this:
<% #patients.each do |patient| %>
<li>
<p><%= patient.name %></p>
</li>
<% end %>
So lets add a link to create the consultation:
<% #patients.each do |patient| %>
<p><%= patient.name %></p>
<ul class="actions">
<li>
<%= link_to "New consultation",
new_patient_consultation_path(patient) %>
</li>
</ul>
<% end %>
Clicking the link would take us to /patients/6/consultations/new.
So in our ConsultationsController we can access the patient id from the params:
class ConsultationsController < ApplicationController
# We use a callback so that we don't need to do
# #patient = Patient.find(params[:id]) in every action
before_action :set_patient
# GET /patients/:patient_id/consultations/new
def new
#consultation = #patient.consultations.new
end
# POST /patients/:patient_id/consultations
def create
#consultation = #patient.consultations.new(consultation_params)
# ...
end
# ...
private
def set_patient
#patient = Patient.find(params[:patient_id])
end
# ...
end
set_patient is just a private method belonging to the controller. This is not really a case where you would use a helper method.
Its often done when dealing with dealing with authentication since you are getting current_user from the session - independently from the params. If you are creating a helper method it should work everywhere.
There is one final thing you need to to get this to work, the form needs to point to /patients/:patient_id/consultations.
# app/views/consultations/_form.html.erb
<% form_for [#patient, #consultation] do |f| %>
# .. rails does all the magic figuring out the url.
<% end %>
http://guides.rubyonrails.org/routing.html#nested-resources
I am trying to user a rails method called polymorphic_path but I am getting the wrong url.
My polymorphic association is with Students and Landlords who are both Users through userable.
Here are my models:
class User < ActiveRecord::Base
belongs_to :userable, polymorphic: true
end
class Student < ActiveRecord::Base
has_one :user, :as => :userable
end
class Landlord < ActiveRecord::Base
has_one :user, :as => :userable
end
I have a variable called current_user holding the User object. The following line:
<%= link_to "Profile", polymorphic_path(current_user) %>
gives me the url "users/22" instead of returning the Student/Landlord url.
Here is my routes.rb file if that helps..
resources :users
resources :students
resources :landlords
Where am I going wrong?
Thanks!
Ok, I got it! And the solution was painfully obvious...
<%= link_to "Profile", polymorphic_path(current_user.userable) %>
<%= link_to "Edit", edit_polymorphic_path(current_user.userable) %>
Hmm, not sure if polymorphic_path should work they way You using it, home brewed alternative
# application_controller.rb
helper_method :current_profile, :path_to_profile
def current_profile
#current_profile ||= current_user.userable
end
def path_to_profile
send("#{current_profile.class.downcase}_path", current_profile)
end
With few additional lines You can extend it to work with other methods too, not only show.
I can implement reverse relationships, so if UserA adds UserB, then it shows UserA in B's profile, and visa versa.
But I cannot figure out how to let UserB remove UserA as a friend, if UserA added UserB.
I've tried so many different ways, but everytime I change something it moves the problem elsewhere! I can't tell if the fundamental issue is:
a. how the FriendshipsController destroy method is defined
b. whether I need another controller specifically just to handle
InverseFriendships destroy
c. if I need to customize the routes
d. if all the above are ok, but the code I have in my views (specifically
the _suggested_connections partial) is calling the wrong controller
and/or route
e. or none of the above.
Code snippets below:
class FriendshipsController < ApplicationController
def destroy
#friendship = current_user.friendships.find(params[:id])
#friendship.destroy
flash[:notice] = "Removed friendship."
redirect_to current_user
end
In the view
<% #user.inverse_friends.each do |inverse_friendship| %>
<li>
<%= inverse_friendship.name %>
<%= link_to "remove", #user.inverse_friendships, :method => :delete, :class => "btn-small btn-danger" %><br />
<%= image_tag inverse_friendship.avatar(:thumb) %>
My models:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
attr_accessible :friend_id, :user_id
end
class User < ActiveRecord::Base
has_many :friendships, dependent: :destroy
has_many :friends, through: :friendships
has_many :inverse_friendships, dependent: :destroy, class_name: "Friendship", foreign_key: "friend_id"
has_many :inverse_friends, through: :inverse_friendships, source: :user
And routes:
resources :friendships
authenticated :user do
root :to => 'home#index'
end
root :to => "home#index"
devise_for :users, :controllers => { :registrations => :registrations }
resources :users
Your main problem is a:
a. how the FriendshipsController destroy method is defined
You're looking for the friendship in the current_user.friendships, but it's not there. It's in inverse_friendships.
You'd need to either check both associations, or let the controller know which one you're looking for. The latter is probably preferable since although they are the same class, they are different resources. Something like this maybe:
# In routes, route inverse friendships to the same controller, but with a
# different path (I'm routing everything here, you may not need that.)
resources :friendships
resources :inverse_friendships, :controller => 'friendships'
# Then in your friendships controller, use the path to determine which
# collection you're working with:
#
def destroy
#friendship = collection.find(params[:id])
# ...
end
# the other collection methods would use the same collection, if you needed them,
# for example:
def create
#friendship = collection.build(params[:friendship])
# ..
end
protected
# simple case statement here, but you get the idea
def collection
case request.path
when /\/inverse_friendships/ then current_user.inverse_friendships
else current_user.friendships
end
end
Finally in your view you'd route to an inverse friendship like:
<%= link_to "remove", inverse_friendship_path(friendship), :method => :delete %>
A normal friendship could use the shorter form, or the full named route:
<%= link_to "remove", friendship, :method => :delete %>
OR
<%= link_to "remove", friendship_path(friendship), :method => :delete %>
EDIT: Searching both associations.
Of course if you wanted to keep it simple, and had no other use for inverse_friends being a separate resource, you could always just...
def destroy
id, cid = params[:id], current_user.id
# search both associations (two queries)
#friendship = current_user.friendships.find_by_id(id) ||
current_user.inverse_friendships.find(id)
# or query friendship looking for both types
#friendship = Friendship.
where("user_id = ? OR friend_id = ?", cid, cid).find(id)
# ...
end
I have the following models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :queue
end
class Queue < ActiveRecord::Base
has_many :subscriptions
end
I want to have some meta-data in the Subscription class and allow users to maintain the details of each of their subscriptions with each subscriptions meta-data. Queues produce messages, and these will be sent to users who have Subscriptions to the Queue.
As I see it the resource I want to have is a list of subscriptions, ie the user will fill in a form that has all the Queues they can subscribe to and set some metadata for each one. How can I create a RESTful Rails resource to achieve this? Have I designed my Subscription class wrong?
I presently have this in my routes.rb:
map.resources :users do |user|
user.resources :subscriptions
end
But this makes each subscription a resource and not the list of subscriptions a single resource.
Thanks.
This can be done quite easily using accepts_nested_attributes_for and fields_for:
First in the User model you do the following:
class User < ActiveRecord::Base
has_many :subscriptions
accepts_nested_attributes_for :subscriptions, :reject_if => proc { |attributes| attributes['queue_id'].to_i.zero? }
# if you hit scaling issues, optimized the following two methods
# at the moment this code is suffering from the N+1 problem
def subscription_for(queue)
subscriptions.find_or_initialize_by_queue_id queue.id
end
def subscribed_to?(queue)
subscriptions.find_by_queue_id queue.id
end
end
That will allow you to create and update child records using the subscriptions_attributes setter. For more details on the possibilities see accepts_nested_attributes_for
Now you need to set up the routes and controller to do the following:
map.resources :users do |user|
user.resource :subscriptions # notice the singular resource
end
class SubscriptionsController < ActionController::Base
def edit
#user = User.find params[:user_id]
end
def update
#user = User.find params[:user_id]
if #user.update_attributes(params[:user])
flash[:notice] = "updated subscriptions"
redirect_to account_path
else
render :action => "edit"
end
end
end
So far this is bog standard, the magic happens in the views and how you set up the params:
app/views/subscriptions/edit.html.erb
<% form_for #user, :url => user_subscription_path(#user), :method => :put do |f| %>
<% for queue in #queues %>
<% f.fields_for "subscriptions[]", #user.subscription_for(queue) do |sf| %>
<div>
<%= sf.check_box :queue_id, :value => queue.id, :checked => #user.subscribed_to?(queue) %>
<%= queue.name %>
<%= sf.text_field :random_other_data %>
</div>
<% end %>
<% end %>
<% end %>
I found this tutorial very useful, as I was trying to relate Users to Users via a Follows join table: http://railstutorial.org/chapters/following-users