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
Related
I've made my own app basing on the railstutorial.org website, and i'm now on chapter 11. Everything's fine, i've learned a lot from this tutorial and now i'm continuing work on my app and i'm actually on model "Artists" where every user can create new artist ex.Michael Hartl ;) and add their most popular quotations. The problem is to allow users to follow their favourite artists and see quotations in feed, just like Microposts feed from railstutorial. Artist and User are two different models, and railstutorial dosn't explaing how to make the "follow system" for that. It's like subscribing channels on YouTube etc.
Can someone explain me how to get this working? What must i change in code?
Answer:
The button:
<%= form_for(current_user.userartists.build(followed_id: #artist.id)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
Controller
class UserartistsController < ApplicationController
def create
#artist = Artist.find(params[:userartist][:followed_id])
current_user.follow!(#artist)
respond_to do |format|
format.html { redirect_to #artist }
format.js
end
end
end
You should set up a Artist model and a intermediate model called UserArtist (or UserFollowsArtist) where you will store all the matches between users and artists.
class User < ActiveRecord::Base
has_many :user_artists
has_many :artists, :through => :user_artists
end
class Artist < ActiveRecord::Base
has_many :user_artists
has_many :users, :through => :user_artists
end
class UserArtist < ActiveRecord::Base
belongs_to :user
belongs_to :artist
end
Now you can call #user = User.first to get the first user, and #user.artists to get the list of artists the #user is following.
You will have to create a separate controller called UserArtistsController where you will have actions create and possibly destroy (if the user wishes to unfollow the artist).
In your routes.rb:
resources :user_artists, :only => [:create, :destroy]
I guess the follow button will be on the Artists show page so you should have something like this in your view:
<%= button_to "Follow artist", {:controller => :user_artists,
:action => 'create', :artist_id => params[:id] }, :method => :post %>
And in your controller:
class UserArtistsController < ActionController
def create
#user_artist = UserArtist.create(:user_id => current_user.id, :artist_id => params[:artist_id])
#artist = Artist.find(params[:artist_id])
if #user_artist.save
redirect_to #artist
else
flash[:alert] = "Something went wrong, please try again"
redirect_to root_path
end
end
end
Don't forget to create a migration for the Artist and UserArtist. UserArtist table should contain a user_id and a artist_id.
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 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.
I have the following route definition:
resources :documents do
collection do
post :filter
end
end
and the following model structure:
class Document < ActiveRecord::Base
belongs_to :documentable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :documents, :as => :documentable
end
and controller structure:
class DocumentsController < ApplicationController
def index
# not important
end
def filter
# not important
end
end
I can easily in a view say:
polymorphic_path([#user, Document])
to get the path /users/1/documents, but I want to be able to say:
filter_polymorphic_path([#user, Document])
to get the path /users/1/documents/filter, unfortunately, this doesn't work.
Anyone know how I can pull this off without adding the following to my routes, for each of my documentable models:
resources :users do
resources :documents do
collection do
post :filter
end
end
end
polymorphic_path([#user, Document], :action => 'filter') gives you /users/:user_id/documents/filter.
Also, polymorphic_path([#user, Document], :action => 'filter', :sort_order => 'this-order') gives you /users/:user_id/documents/filter?sort_order=this-order.
I ran into the same problem thinking you can replace the edit in edit_polymorphic_path to whatever method you want.
See: http://api.rubyonrails.org/classes/ActionDispatch/Routing/PolymorphicRoutes.html
This would do it, and it reads nicely.
polymorphic_path([:filter, #user, Document])
Or these
polymorphic_path([:filter, #user, :documents])
polymorphic_path([:filter, #user, Document.new])
And with a query param
polymorphic_path([:filter, #user, Document], :q => 'keyword')
And, in a view you can also do this:
= link_to "Documents", [[:filter, #user, :documents], :q => 'keyword']
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.