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
Related
I'm working on a simple project management tool in Rails 4, and the part which gives me headaches has three main models: Projects, Users and Memberships.
Users can have many projects and projects can have many users. I implemented a has_many through membership relationship between Projects and Users in the following manner:
Project:
class Project < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
User:
class User < ActiveRecord::Base
has_many :memberships
has_many :projects, through: :memberships
end
Membership:
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
So far I haven’t seen it on Stack Overflow, but I created a seperate controller for the Memberships, with a :create and :destroy only.
So far, :create seems to work just fine.
The main problem lies in the destroy function of the Memberships.
The destroy function I implemented is:
def destroy
Membership.find(:id).destroy
redirect_to current_project || request.referer
end
rake routes says that the membership path exists, but the following tries give me:
I tried to use a link_to helper to delete the memberships:
<%= link_to "delete", membership, method: :delete %>
EDIT: error: undefined local variable or method `membership'
<%= link_to "delete", #membership, method: :delete %>
EDIT: error: Sorry something went wrong --> goes to /memberships
<%= link_to "delete, membership_path(#membership), method: :delete %>
EDIT: error No route matches {:action=>"destroy", :controller=>"memberships", :id=>nil} missing required keys: [:id]
which all give errors.
EDIT: on request also the projects_controller #show function
def show
#user = current_user
#project = current_user.projects.find(params[:id])
#members = #project.users
#projects = #user.projects
#membership = #project.memberships.build if logged_in?
#memberships = #project.memberships
end
How can I make sure a membership is removed with the associated id in #project.membership_ids? Should I include certain extra parameters?
resources :memberships, only: [:create, :destroy]
Update
Don't know how I missed this earlier, in your destroy action you have Membership.find(:id).destroy. It should utilize params and be more along the lines of this:
# MembershipsController
def destroy
#membership = Membership.find(params[:id])
if #membership.destroy
redirect_to current_project || request.referer
else
#
end
end
Your ProjectsController's Show action isn't defining #membership as a Membership object.
def show
#user = current_user
#project = #user.projects.find(params[:id])
#membership = Membership.find_by user: #user, project: #project
# build is used for nested attributes, not sure why you'd have this in a show action...
# #project.memberships.build if logged_in?
# the following are redundant.
#projects = #user.projects
#members = #project.users
#memberships = #project.memberships
end
In the view: <%= link_to "delete", #membership, method: :delete %>
If you want all memberships of a project or user to be destroyed upon parent deletion, make the following changes to your User & Project models:
class Project < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :users, through: :memberships
end
class User < ActiveRecord::Base
has_many :memberships, dependent: :destroy
has_many :projects, through: :memberships
end
The reason the your code did not work is because you did not specify the path to link_to. I do not believe you need to have #membership so I edited that out.
<%= link_to "delete", membership_path(#membership), method: :delete %>
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
I have this relationship where User can create a document(trip) and invite other users to a group that belongs to that document. My relationship indicates that "Group" has a user_id and trip_id column, so for every user I invite, a new Group record will be created in the database.
When I am inviting other users, I only want users who are NOT in the group to appear. Users who are already in the group should not show up, but my view still shows the users.
I've been playing around with <% if !friend.trips.include?(#trip)%>, but I can't seem to get the correct view. The record is being created in the database correctly.
Also, when I am viewing groups/new.html.erb, this is the url http://localhost:3000/groups/new?id=2, where the id is the trip_id.
My question:
Am I using restful convention? That is, should I be using the new method here (as is) or should I be using the index method instead?
How do I iterate through each friend's groups to make sure that none of the group's trip_id is equivalent to #trip.id?
Thanks!
view (/groups/new.html.erb)
<% if !#friends.blank? %>
<% #friends.each do |friend| %>
<% if !friend.trips.include?(#trip)%>
<%= link_to groups_path(:user_id => friend.id, :trip_id => #trip.id),
:method => :post, :action => 'create' do %>
<div id="addfriend_totrip_button_groupsnew">add friend to trip</div>
<% end %>
<% end %>
<% end %>
<% end %>
groups_controller.rb
class GroupsController < ApplicationController
before_filter :authenticate, :only => [:update, :create, :destroy]
def new
#trip = Trip.find(params[:id])
#user = User.find(current_user)
#group = Group.new
#friends = #user.friends.all
end
def create
#trip = Trip.find(params[:trip_id])
#user = User.find(params[:user_id])
#group = Group.create(:user_id => #user.id, :trip_id => #trip.id)
if #group.save
flash[:success] = "Friend added to group."
redirect_to groups_path(:id => #trip.id)
else
flash[:error] = "Could not add friend."
redirect_to root_path
end
end
end
user.rb
class User < ActiveRecord::Base
has_many :trips, :through => :groups
has_many :trips, :dependent => :destroy
has_many :groups
end
trip.rb
class Trip < ActiveRecord::Base
belongs_to :user
belongs_to :traveldeal
has_many :groups
has_many :users, :through => :groups
end
group.rb
class Group < ActiveRecord::Base
belongs_to :trip
belongs_to :user
end
First of all, you have has_many :trips called twice in your User model. I understand you have two different types of User-Trip relationships (one directly, and one through Group), but you can't give both the same name, otherwise one will hide the other. Try defining your User model like this:
class User < ActiveRecord::Base
has_many :group_trips, :through => :groups,
:class_name => "Trip"
has_many :trips, :dependent => :destroy
has_many :groups
def all_trips
Trip.joins(:groups).where({:user_id => self.id} | {:groups => {:user_id => self.id}})
end
end
There's also the problem that you're searching the friend's list of groups for a Trip object. Try changing that line to:
<% if !friend.all_trips.include?(#trip) %>
Or without the new method, something like this should work:
<% if !friend.groups.where(:trip_id => #trip.id).first %>
I don't see anything un-RESTful about your approach. RESTful in general means stateless. I.e. the only thing a response depends on is the HTTP method and the address. So as long as your not keeping state information in, say, the session, you should be following REST.
so I have two models:
class User < ActiveRecord::Base
has_and_belongs_to_many :followed_courses, :class_name => "Course"
end
class Course < ActiveRecord::Base
has_and_belongs_to_many :followers, :class_name => "User"
end
in User.rb, I also have:
def following_course?(course)
followed_courses.include?(course)
end
def follow_course!(course)
followed_courses<<course
end
def unfollow_course!(course)
followed_courses.delete(course)
end
I don't have a courses_users model, just a join table(courses_users). I guess I'll have to follow/unfollow a course in CoursesController. Do I create a new action in the controller?
Right now, I have a follow form in the courses/show page when the course is not followed
= form_for #course, :url => { :action => "follow" }, :remote => true do |f|
%div= f.hidden_field :id
.actions= f.submit "Follow"
And I have in CoursesController:
def follow
#course = Course.find(params[:id])
current_user.follow_course!(#course)
respond_to do |format|
format.html { redirect_to #course }
format.js
end
end
But looks like it was never activated. Do I need to modify the route so the action will be activated? How to modify it? Or is there a better way to do this? Can I replace the form with just a link? Thanks in advance!
This is a follow-up of a related question rails polymorphic model implementation
THe routing system invokes CoursesController#follow? If not you have to write in the routes.rb file the following line:
map.resources :courses, :member => {:follow => :post}
#You'll have the map.resources :courses, just add the second argument.
After that the routing system could redirect to that action, and it will you give the follow_course_url(#course) helper
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.