Ruby on Rails: Associations when a user likes a song - ruby

I'm trying to figure out the best way to setup my database and models for the following scenario.
A user can like an infinite number of songs.
A song can be liked once by an infinite number of users.
I have these tables:
songs, users, likes etc... Following RoR conventions.
The table named likes has these foreign keys: user_id, song_id. And also a field named 'time' to save a timestamp when the song was liked.
I'm not sure of how to do this, I would like to be able to use code like this in my controllers:
User.find(1).likes.all
This should not return from the likes table, but join the songs table and output all the songs that the user likes.
What are the best practises to achieve this in Ruby on Rails following their conventions?

Unless you need to act specifically on the likes table data, the model itself is probably not necessary. The relationship is easy:
class User < ActiveRecord::Base
has_and_belongs_to_many :songs
end
class Song < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will join through the currently non-existent song_users table. But since you want it to join through likes you can change each one to this:
has_and_belongs_to_many :songs, :join_table => 'likes'
If you want to be able to call User.find(1).likes and get songs, then change the user's version to this:
class User < ActiveRecord::Base
has_and_belongs_to_many :likes, :join_table => 'likes', :class_name => 'Song'
end
And you could change the songs version to something like this:
class Song < ActiveRecord::Base
has_and_belongs_to_many :liked_by, :join_table => 'likes', :class_name => 'User'
end
This will give you Song.find(1).liked_by.all and give you the users (You could keep it to users if you wanted using the first version)
More details on habtm relationships can be found here: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Edit to add
If you want to act on the join table for whatever reason (you find yourself needing methods specifically on the join), you can include the model by doing it this way:
class User < ActiveRecord::Base
has_many :songs, :through => :likes
has_many :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :users, :through => :likes
has_many :likes
end
This will let you do User.find(1).songs.all, but User.find(1).likes.all will give you the join data

Related

database schema for like entities that can be combined pairwise (ActiveRecord)

I am designing a database of woodwind instrument sounds, and would like to create a table that joins pairs of sounds that a performer can combine into, for example, a trill. Such relations are transitive: if sound A has a 'Sound Relation' with sound B, then sound B has that same 'Sound Relation' with sound A.
I am familiar with join tables, but I've never seen them used to join 'like' objects, only to join 'unlike' objects, such as tags and posts, so I'm wary of going that direction.
I realize the example below looks extremely dubious, but it gives an idea of what I'm after. What is a better way of doing it? (Using ActiveRecord syntax)
Models
class Sound < ActiveRecord::Base
has_many :linked_sounds, through: :sound_relations, class_name: "Sound", foreign_key: ???
end
class Sound_Relation < ActiveRecord::Base
has_many :sounds
end
Migration
class CreateSoundRelations < ActiveRecord::Migration
def change
create_table :sound_relations do |t|
t.integer first_sound_id # This cannot possibly be right.
t.integer second_sound_id # Surely the transitivity of the
# relationship should be more evident?
end
end
end
You might try something like:
class Set < ActiveRecord::Base
has_many :sound_sets
has_many :sounds, :through => :sound_sets
end
class SoundSet < ActiveRecord::Base
belongs_to :sound
belongs_to :set
end
class Sound < ActiveRecord::Base
has_many :sound_sets
has_many :sets , :through => :sound_sets
has_many :set_sound_sets, :through => :sets , :source => :sound_sets
has_many :set_sounds , :through => :set_sound_sets, :source => :sound
end
So, no more "sound_1" and "sound_2" ... they are both just sounds. For every sound you can also use the set_sounds method to retrieve all of the sounds associated with it.
This would also allow more than two sounds in a relation, and you might like to put a "type" on the sets model.
Edit: If you look at the query generated, you'll find that sound_sets is mentioned in there twice, once with a different alias. The key to eliminating "self" joins is to include a clause in the association along the lines of:
has_many :set_sounds ,
{where("sound_sets.sound_id != set_sound_sets_sound_set_sounds.sound_id")},
:through => :set_sound_sets,
:source => :sound
... where "sound_set_sounds" is the table alias. If you can post the query in the comments I can update this with the actual alias.

How to properly handle multiple model associations to different keys in Rails 4.0.8

I'm having the following problem while trying to model my application, it's basically a functionality to handle bills with multiple people, like sharing the rent or any other similar stuff, the deal is:
I have a user model and a billing model but can't find out how to build the associations.
A user has_many billings and billings belongs_to user but, also the billing has_many users, e.g. Tom registers a bill that is meant to be shared by Tom himself, Betty and Bob.
So that makes Tom the creditor and the other two become debtors in the billing.
I'm kinda lost at this point, how to consolidate these associations, dunno if any more information is needed or if it's clear enough, but will appreciate any help and update with any other information needed.
Thanks in advance,
---EDIT---
I've come to the following approach:
Class User < ActiveRecord::Base
has_many :billings, foreign_key: "creditor_id", dependent: :destroy
end
Class Billing < ActiveRecord::Base
belongs_to :creditor, class_name: "User"
has_many :debts, dependent: :destroy
end
Class Debt < ActiveRecord::Base
belongs_to :billing
has_one :user
end
I also tried to graphically model it for better understanding here: imgur
Would that all be correct?
Don't be afraid to use the options available to you in ActiveRecord. If your model naming is getting cluttered (Billing belongs to a User and has_many Users) then be more specific with the association labels and sort the links with options. The Rails Associations Guide is easy to Google and covers pretty much everything.
class User < ActiveRecord::Base
has_many :billings, :foreign_key => 'creditor_id'
has_many :debts, :through => :debtor_users, :source => :billing
end
class Billing < ActiveRecord::Base
belongs_to :creditor, :class_name => 'User'
has_many :debtors, :through => :debtor_users, :source => :user
end
class DebtorUser < ActiveRecord::Base
belongs_to :billing
belongs_to :user
end
You can extend the Billing model to have multiple creditors also simply by converting the belongs_to association into a has_many :through association following the same pattern as debtors just with a different join model (say CreditorUser).

Active Record has_many through more than one model

Is it possible to access objects more than one model away?
For example let's say I have
class Contact <ActiveRecord:Base
has_many :interactions
end
class Interaction <ActiveRecord:Base
belongs_to :contact
belongs_to :course_presentation
end
class CoursePresentation <ActiveRecord:Base
has_many: interactions
belongs_to :course
end
class Course <ActiveRecord:Base
has_many :course_presentations
end
Right now I know I could write a through relationship via contacts to course presentations and then get all the course related to all the course presentations or I could do
contact.interactions.map{ |i| i.course_presentation.course }
I would like to be able to pull courses related to a contact directly so ... e.g.
contact.courses
Is this possible?
Yes, I believe so. Just add the following:
class Contact < ActiveRecord::Base
has_many :interactions
has_many :course_presentations, through: :interactions
has_many :courses, through: :course_presentations
end

Rails Associations - Multiple has_one relationships to the same class

An example of my issue is a sports game. A sports game has two teams, a home team and an away team. My active record models are as follows:
class Team < ActiveRecord::Base
belongs_to :game
end
class Game < ActiveRecord::Base
has_one :home_team, :class_name => "Team"
has_one :away_team, :class_name => "Team"
end
I want to be able to access a team through the game, for example: Game.find(1).home_team
But I am getting an unitialized constant error: Game::team. Can anyone tell me what I'm doing wrong? Thanks,
If Game has_one :team then Rails assumes your teams table has a game_id column. What you want though is for the games table to have a team_id column, in which case you'd use Game belongs_to :team. As English it does sound backwards in this case, but as Ruby, it's correct.
I did simplify a little. You'd want something like:
class Team < ActiveRecord::Base
has_many :home_games, :class_name => "Game", :foreign_key => 'home_team_id'
has_many :away_games, :class_name => "Game", :foreign_key => 'away_team_id'
end
class Game < ActiveRecord::Base
belongs_to :home_team, :class_name => "Team"
belongs_to :away_team, :class_name => "Team"
end
I just tested your code and it should work.
What I suspect is that your file name is wrong. Make sure that your filenames in app/models/ are:
game.rb
team.rb
and not:
games.rb
or
teams.rb
I think that it may be a mistake of your architecture.
Game can't distinguish two Team with this architecture.
So, please run like that
rails g migration add_stadium_to_game stadium:integer
rails g migration add_home_to_team home:integer
rake db:migrate
and, edit "game.rb" like that
class Game < ActiveRecord::Base
has_many :teams
def home_team
teams.select { |team| team.home == self.stadium }.first
end
def away_team
teams.select { |team| team.home != self.stadium }.first
end
end
Of cource this is one example, so there are many ways to realize your purpose.
Sounds like a namespacing problem. Try explicitly declaring the class (with namespace) for team. E.g.:
has_one :home_team, :class_name => "::Team"
http://guides.rubyonrails.org/association_basics.html#the-has_one-association

Rails get related items through two different relationships

I have a "two middleman" model setup as shown below:
User
has_many :comments
has_many :ratings
Comment
belongs_to :user
belongs_to :movie
Rating
belongs_to :user
belongs_to :movie
Movie
has_many :comments
has_many :ratings
Whats the best way to get all Movies that a User is associated with (either commented on or rated)?
I'd like to be able to call User.get_movies(user_id) and get back an ActiveRecord::Relation object so that it's chainable (i.e. User.get_movies(user_id).limit(3).order(...)). This returns a regular old array, and I suspect I'm hitting the database way more than I need to be.
def self.get_movies(user_id)
user = self.where(:id => user_id).includes({:comments => :movie}, {:ratings => :movie})
movies = []
user.comments.each do |comment|
movies.push(comment.movie)
end
user.ratings.each do |rating|
movies.push(rating.movie)
end
movies.uniq!
end
def movies
Movie.includes(:ratings, :comments).where("`ratings`.user_id = ? OR `comments`.user_id = ?", self.id, self.id)
end
Untested, but I'm pretty sure using a joins instead of includes also works.

Resources