Following artists by users - few questions - ruby-on-rails

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.

Related

ActiveRecord Couldn't find Travel with 'id'=

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.

Not showing user during iteration if user already belongs to "group" relationship

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.

Rails has_and_belongs_to_many association

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

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.

In Rails, how do I use RESTful actions for a resource that is the join in a many to many relationship?

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

Resources