polymorphic_path not generating correct path - ruby-on-rails

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.

Related

No route matches error: Difference between #model.association vs querying?

Technically, I could get this working, but why my current code doesn't work confuses me. I have a many-to-many relationship in my Events <> Users. This is where my view farts out, saying No route matches: missing required key [:id] ...
<% #event_users.each do |event_user| %>
<%= link_to event_user.user.try(:full_name), user_path(event_user.user) %>
<% end %>
This is my code in my controller. One way that works, one way that doesn't work.
#event = Event.find(params[:id])
#event_users = #event.event_users # This does NOT work
# #event_users = EventUser.where(event_id: 14) # This does work
This is my controller relationships
class User < ActiveRecord::Base
has_many :event_users, dependent: :destroy
has_many :events, through: :event_users
end
class EventUser < ActiveRecord::Base
belongs_to :user
belongs_to :event
validates :user, presence: true
validates :event, presence: true
end
class Event < ActiveRecord::Base
has_many :event_users
has_many :users, through: :event_users
end
What exactly is going on here that I have to query and can't use the association to create a link? When I print out the text, I get the relevant data (the ID), so it should work. I've also tried this below and it still doesn't work.
<%= link_to event_user.user.try(:full_name), user_path(event_user.user_id) %>
You don't need to query #event.event_users, thats what the has_many :through association is for, you can simply use the #event object to get its users:
<% #event.users.each do |user| %>
<%= link_to user.full_name, user_path(user) %>
<% end %>
Note, you can also just pass an object to link_to that rails will automatically use a helper to create a route to users#show, like this:
<%= link_to user.full_name, user %>

Rails: c.id submits nil in comment.each do |c.id| block

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

Deleting a 'friend' that added you using self-referential association

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

Has_many through and path helper - accessing resources through the application

I have an app in which users can follow law firms
I have 3 models
- User
- Firm
- Follow
class Firm < ActiveRecord::Base
has_many :follows, :dependent => :destroy
has_many :users, :through => :follows
class User < ActiveRecord::Base
has_many :follows, :dependent => :destroy
has_many :firms, :through => :follows
class Follow < ActiveRecord::Base
belongs_to :firm
belongs_to :user
In a table in my firms index view, I would like to take the current signed and create an association between that user and the law firm - through the follow table.
In effect doing this -
firm.users << User(current)
This is the code that I have at present, how would you suggest that I structure the path, and the corresponding controller?
<% #firms.each do |firm| %>
<tr id = "firm_<%= firm.id %>">
<td><%= link_to image_tag(firm.logo_url, :size => "80x120"), firm.url %></td>
<td><%= link_to firm.name, firm_path(firm) %></td>
<% if user_signed_in? %><td>
<%= button_to 'Follow', ? , method: :post %>
</td>
<% end %>
I am using devise for the User authentication and have put the following helpers into application helper to allow my login partial to function in a different models view.
def resource_name
:user
end
def resource_id
:user_id
end
def resource
#resource ||= User.new
end
The simplest way would be to have a follow action on a FirmsController.
In config/routes.rb:
resources :firms do
post :follow, on: :member
end
In your FirmsController:
def follow
#firm.users << current_user
end
In your view:
<%= link_to "Follow", follow_firm_path(#firm), method: :post %>
Another way would be to represent a follow relationship as a singular resource. You'd follow a firm by POSTing to /firms/1234/follow and you'd unfollow a firm by sending a DELETE request to /firms/1234/follow.
If you wanted to take that approach, you'd stick this in your config/routes.rb:
resources :firms do
resource :follow, on: :member
end
And you'd create a FollowsController like this:
class FollowsController < ApplicationController
def create
#firm = Firm.find params[:firm_id]
#firm.users << current_user
# respond with success
end
def destroy
#firm = Firm.find params[:firm_id]
#firm.users.delete current_user
# respond with success
end
end

link_to create and destroy a resource from a different resource

The code below is working but I want to know if there is a better way to do it. Is this the correct RESTful way to do this? Any suggestions would be helpful. The basic requirement is that I need a way to create and destroy a membership from places/show.html.erb
class Place < ActiveRecord::Base
has_many :memberships
has_many :members, :through => :memberships, :source => :user
def membership_for_user(user)
self.memberships.select{|m| m.user_id == user.id}
end
end
class User < ActiveRecord::Base
has_many :memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :place
end
class MembershipsController < ApplicationController
def create
#membership = Membership.new({:user_id => current_user.id, :place_id => params[:place_id]})
unless #membership.save
flash[:notice] = "Unable to become member."
end
redirect_to place_path(params[:place_id])
end
def destroy
#membership = Membership.find(params[:id])
place_id = #membership.place_id
#membership.destroy
redirect_to place_path(place_id)
end
end
places/show.html.erb
<%= link_to 'Join', memberships_path(:place_id => #place.id), :method => :post %>
<%= link_to 'Cancel', #place.membership_for_user(current_user), :method => :delete %>
This looks odd - how can you both create and delete a membership on the same page?
Are you selecting one or the other of the link_to statements, or can a user have multiple memberships to the same place?
I'd consider:
using a form_for(#membership) instead of the first link_to, with hidden_field :place_id
loading the membership in the controller, which would simplify the 2nd link_to.
#klochner is right; this is wrong. You need to create the membership beforehand and have a status attribute on the Membership, which you either update to "accepted" or just delete the membership, if you want to do it this way. Otherwise you need a form to create the membership first.

Resources