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

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).

Related

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

belongs_to and has_many of the same items

I'm modeling a lessons table, the lesson belongs to a user, the teacher and creator of the lesson, and also, the lesson can have many students, which are also users.
So it would be something like this
class Lesson < ActiveRecord::Base
belongs_to :user
has_many :users
end
I'd like to call the first user teacher, and the collection of users students, I've read the documentation at http://guides.rubyonrails.org/association_basics.html but I can't quite find what I want.
This should have what you want: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to
I think you want the class_name option:
class Lesson < ActiveRecord::Base
belongs_to :teacher, class_name: "User"
has_many :students, class_name: "User"
end
In your current code, all users could be the "owner" (teacher) of a lesson, instead you should have two additional classes "student" and "teacher" both having a 1:1 relation to the "user" class.
This would fit better:
class Teacher < ActiveRecord::Base
has_one :user
end
class Student < ActiveRecord::Base
has_one :user
end
class Lesson < ActiveRecord::Base
belongs_to :teacher
has_many :students
end

Rails 3.1 has_many, :through => not working (joined model returns nil)

Update: This was all due to a stupid error: previously, I had defined a method with the same name as one of the methods ActiveRecord creates, which was masking the proper behaviour and breaking everything. I can't answer/close the question for a few more hours, apologies to anyone who looked into this!
I have an infuriating problem with a has_many, :through => relationship in my Rails 3.1 app.
It is infuriating because as far as I can see it is identical to two similar relationships which both work.
The owner of these relationships declares them like this:
has_many :user_skills, :dependent => :destroy
has_many :skills, :through => :user_skills
has_many :user_roles, :dependent => :destroy
has_many :roles, :through => :user_roles
has_many :conversation_users
has_many :conversations, :through => :conversation_users
(I am aware I have not followed standard nomenclature for join tables here - I only read about the convention of both-plural, names-alphabetical after setting this up, and I will refactor later)
The first two pairs of relationships (skills and roles) work just fine.
The final relationship (conversations) does not work fully. user.conversation_users returns the expected array, but user.conversations returns nil. Not an empty array, nil.
I may well be doing something stupid here, so I would be very grateful to anyone who can spot something wrong with the ConversationUser or Conversation models below.
conversation_user.rb
class ConversationUser < ActiveRecord::Base
belongs_to :user, :inverse_of => :conversation_users
belongs_to :conversation, :inverse_of => :conversation_users
validates_presence_of :user
validates_presence_of :conversation
end
conversation.rb
class Conversation < ActiveRecord::Base
has_many :messages, :dependent => :destroy
has_many :conversation_users, :dependent => :destroy
has_many :users, :through => :conversation_users
validates_presence_of :unique_id
end
(I am also aware that these are not really complex enough to justify has_many, :through => over has_and_belongs_to_many, but planned additional functionality will require join models.)
Answering to close question:
This was all due to a stupid error: previously, I had defined a method with the same name as one of the methods ActiveRecord creates, which was masking the proper behaviour and breaking everything.

ActiveRecord: Has many through (twice)

There are Things in Places which I'm looking to find. One Thing could be in many different Places, and many Things can be in one Place.
class Thing < ActiveRecord::Base
has_and_belongs_to_many :places
end
class Place < ActiveRecord::Base
has_and_belongs_to_many :things
end
I want to record the Finds of my Users so that I know where they found what.
class Find < ActiveRecord::Base
belongs_to :user
belongs_to :places_thing # Is this depluralization correct?
end
class User < ActiveRecord::Base
has_many :finds
# Now, how can I link in the Things the user has found? Like this?
has_many :found_things_in_places, :class_name => :places_things, :through => :finds
has_many :things, :through => :thought_things_in_places
end
Does this seem right? is it efficient? Thanks.
I think you were on the right track, the big change I'd make is that rather than having a join table (places_things) you should make it a proper model. I decided to call this an existence.
The data only exists in one place, so it's properly normalized. These relationships are clear and will be easy to manage. I think it's efficient.
class Place < ActiveRecord::Base
has_many :existences
has_many :things, :through => :existences
end
class Thing < ActiveRecord::Base
has_many :existences
has_many :places, :through => :existences
end
class Existence < ActiveRecord::Base
belongs_to :place
belongs_to :thing
end
class Find < ActiveRecord::Base
belongs_to :user
belongs_to :existence
end
class User < ActiveRecord::Base
has_many :finds
has_many :existences, :through => :finds
has_many :things, :through => :existences
end
You'll need rails 3.1 to do the nested has many through's like we did in User.
BTW the correct association declaration should be: belongs_to :places_things

Ruby on Rails: Associations when a user likes a song

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

Resources