Get associated objects via DB access in ActiveRecord - ruby-on-rails

I have 3 models: Guardian, Student and Organization. Guardian is connected to Student through a linking model and similarly Student is connected to Organization through a linking model. I need to get for every guardian, a list of (distinct) organizations and am wondering what the best way to do so is.
Currently I do it at the application level in the Guardian class
def organizations
orgs = []
students.each do |s|
s.organizations.each do |o|
orgs << o if !orgs.include?(o)
end
end
orgs
end
I wonder if there's a better way to do this, preferably at the database level. Any help will be appreciated.
Edit: here's a more detailed description of my models
class Person < ActiveRecord::Base
end
class Guardian < Person
has_many :student_guardian_links, inverse_of: :guardian, dependent: :destroy
has_many :students, through: :student_guardian_links, inverse_of: :guardians
end
class Student < Person
has_many :student_guardian_links, inverse_of: :student, dependent: :destroy
has_many :guardians, through: :student_guardian_links, inverse_of: :students
has_many :student_organization_links, inverse_of: :student, dependent: :destroy
has_many :organizations, through: :student_organization_links
end
class Organization < ActiveRecord::Base
has_many :student_organization_links, inverse_of: :organization, dependent: :destroy
has_many :students, through: :student_organization_links, inverse_of: :organizations
end

With relational databases is often better to think from the target set. So you want organisations with specific conditions (they have students connected to a specific guardian)
In ActiveRecord we have joins for this. It is a misnamer, as you still get only Organisation objects, it only uses a SQL-join to get them from the database, and you can specify conditions on the joined object.
Look at http://guides.rubyonrails.org/active_record_querying.html at "Joining Nested Associations" and "Specifying Conditions on the Joined Tables"
So depending on your exact model (remember you need the backward connections) it may look like this:
Organinisation.joins(:students).where('student.guardian_id' => mygivenguardian.id).distinct()
distinct is important when the join can lead to multiplications of the rows, as when a guardian is connected with more then one student to the organisation.

This should work:
students.map { |student| student.orgs }.flatten.uniq
Really the same approach you are taking. Just using a more functional approach.

Try this:
class Guardian < ActiveRecord::Base
has_many :organizations,
:through => :students
# Your existing relationships go here
end
This way you can simply call guardian.organizations.uniq to get a list of distinct organizations associated with a specific Guardian object.

Related

has_many, has_one association with composite foreign keys

I am trying to wrap my head around how I am going to be able to have a has_many (or has_one in my case) association with a compound foreign_key.
For my example Assume this:
Imagine I am managing relationships between sellers and buyers, there is an initial contract between them and after that any amount of invoices. So the models look like this:
class Invoice
belongs_to :seller
belongs_to :buyer
end
class Seller
has_many :contracts
has_many :invoices
end
class Buyer
has_many :contracts
has_many :invoices
end
class Contract
belongs_to :seller
belongs_to :buyer
end
For every invoice there is one initial contract (defined by the seller and the buyer). I'm aware that there is all kinds of solutions to build a query that achieves that. Ideally though I want to take care of preloading/joining the contract for a list of invoices to avoid N+1 problems:
class Invoice
belongs_to :seller
belongs_to :buyer
has_one :contract # options go here
end
EDIT: The schema is what it is. Solutions that require a schema change unfortunately is not an option.
Just make the invoice belong to the contract and get the buyer and seller from that.
class Invoice
belongs_to :contract
end
Then get the invoices through contacts...
class Buyer
has_many :contracts
has_many :invoices, through: :contracts
end
class Seller
has_many :contracts
has_many :invoices, through: :contracts
end

multiple has_many through with the same model rails

I am trying to set up the model structure that has a User model Project model along with two join tables setup as has_many through to manage two specific aspects of the Project, ProjectManagers and ProjectMembers.
I can setup two has_and_belongs_to_many but it doesn't feel very railsy.
Right now, this is what I have and I'm unsure of how to proceed to use multiple has_many through (Project Manager, Project Member) both referencing User model.
Would a nested through be the way to go even if a Project Manager will not always be part of the Project User table?
project.rb
class Project < ApplicationRecord
has_many :project_members
has_many :users, through: :project_manager
end
user.rb
class User < ApplicationRecord
has_many :project_managers
has_many :users, through: :project_managers
end
project_manager.rb
class ProjectManager < ApplicationRecord
belongs_to :project
belongs_to :user
end
project_member.rb
class ProjectMember < ApplicationRecord
belongs_to :project
belongs_to :user
end
I don't see any problems with what you're doing. There are other options, but this approach should work as you want. Have you tried it? I'd do something like this.
class Project < ApplicationRecord
has_many :project_members
has_many :project_managers
has_many :members, through: :project_members, :class_name => User.to_s
has_many :managers, through: :project_manager, :class_name => User.to_s
end
Another approach, since the join tables are similar is to subclass them and add a type column to the join table. Not necessarily better than what you're doing.
You could also create a project_users table (don't separate members and managers) and include a "role" column. A scope on project_user.rb would bring back managers or members.
Personally, I would go with your approach. Managers will likely have different auth and have relationships with other objects. It's simpler to query and less likely to make a mistake.
And, I wouldn't recommend a has_and_belongs_to_many, you're likely to add other columns to the join table and you'll be glad you have the model.

Does Rails have a built-in way to create triple-join objects? Do I create AR models for them?

My database stores data about a TV show. I want to store information about who worked on what episodes, and in what role.
Each crew member works on many episodes; each episode has many crew members; and crew members can work on the same episode in different roles (they can be writer and director on the same episode, for example).
I'm new to thinking about ActiveRecord, so I'm a bit confused about how to properly represent these relationships in Rails. I would really love it if someone could tip me off as to the 'Rails way' to do this.
My instinct is to create a triple-join (or double-join; three fields, anyway) table. I have three models: CrewMember (has a name), Episode (has a title, airdate, etc), and Role (has a job title, like Director, Writer or Actor). I could create a table with three fields: CrewMember_ID, Episode_ID, and Role_ID. Each row would then describe someone working on a specific episode in a specific capacity.
So the relationship would appear to be:
CrewMember has_many :episodes
Episode has_many :crew_members
CrewMember has_many :roles
Role has_many :crew_members.
Here's where I get confused. Do I create a new model for that table? And if I do, how exactly do I create instances of that object? I understand relationships like Author has_many :books where I can create a book with Author.books.new, but this kind of has-many has-many has-many thing stumps me a bit.
I can do what I want with SQL, I'm just having trouble approaching it from an ORM/ActiveRecord angle.
you can work around this issue by adding a model that collects the three models
rails g model big_model model1:references model2:references model3:references
Using "Jobs" as GreenTriangle suggested
class CrewMember < ActiveRecord::Base
has_many :jobs, dependent: :destroy, inverse_of: :crew_member
has_many :episodes, through: :jobs
has_many :roles, through: :jobs
end
class Episode < ActiveRecord::Base
has_many :jobs, dependent: :destroy, invserse_of: :episode
has_many :crew_members, through: :jobs
has_many :roles, through: :jobs
end
class Role < ActiveRecord::Base
has_many :jobs, dependent: :destroy, inverse_of: :role
end
class Job < ActiveRecord::Base
belongs_to :episode
belongs_to :role
belongs_to :crew_member
validates_presence_of :episode, :role, :crew_member
end

Rails associations between clients and sellers - has_many :through or has_and_belongs_to_many

I'm beginning a project with rails where there are products, clients and sellers. Each seller has_many products. Each Client has_many products. (And in my case, each client only buys one product at a time).
I want to know who are my clients' seller and my Seller's clients, knowing that, they'll be linked by, the purchase, of one product.
Should I use a has_and_belongs_to_many association between clients and sellers ? Or a double has_many through :products, like :
Seller :
has_many :clients through :products
Belongs_to :products
Client :
has_many :sellers through :products
Belongs_to :products
In order to avoid two belongs_to in the product class, could this work ?
class Client < ActiveRecord::Base
has_many :products, as: :productable
has_many :sellers, through: :products
end
class Seller < ActiveRecord::Base
has_many :products, as: :productable
has_many :clients, through: :products
end
class Product < ActiveRecord::Base
belongs_to :productable, polymorphic: true
end
Thanks in advance for your answer.
I would go with has_many :through here.
class Client < ActiveRecord::Base
has_many :products
has_many :sellers, through: :products
end
class Seller < ActiveRecord::Base
has_many :prodcuts
has_many :clients, through: :products
end
class Product < ActiveRecord::Base
belongs_to :client
belongs_to :seller
end
The simplest rule of thumb is that you should set up a has_many
:through relationship if you need to work with the relationship model
as an independent entity. If you don't need to do anything with the
relationship model, it may be simpler to set up a
has_and_belongs_to_many relationship (though you'll need to remember
to create the joining table in the database).
You should use has_many :through if you need validations, callbacks,
or extra attributes on the join model.
And also see these Guides for choosing between HABTM and a has_many :through
I want to approach your question from the other end: let us start from the product. I think this will clarify a lot of things.
So you have three models: Seller, Client and Product.
A Product has a seller and client. In your model that would like this:
class Product
belongs_to :seller
belongs_to :client
end
This means that in the products table we have a column seller_id and client_id.
Afaik a product needs to have both, always. So this also means you cannot use a polymorphic association here. At least not the way you proposed it. If you write
belongs_to :productable, polymorphic: true
you will add the fields productable_id and productable_typeto yourProduct` model. But that is only 1 link (so either a seller or a client, but never both). You could introduce a link table here, so a product could be linked to many "productables" but in your case i think it is besides the point. You know a product has one seller and one client.
Secondly, now this is established, your Product is exactly the link-table between clients and sellers. So you do not have to introduce a new link-table, just use the one already there.
class Seller
has_many :products
has_many :clients, through: :products
end
class Client
has_many :products
has_many :sellers, through: :products
end
So in conclusion:
use the has_many :through because you already have the link table as a model. Only use a habtm if you do not care about the join-table (link-table).
you can't use a polymorphic association here, as you need two links (without introducing a link-table, which seems overkill imho). I like the explicitness, clarity, readability of having an explicit seller_id and client_id, and it is also easier to manage.

has_many :through relationships explained

I'm new to Rails and have some doubts about the kind of relationship do I need to use. Here is the case.
I have two models Offer and User, a user could belong to to many offers and offers can have many user. Also the users create the offers.
I think I have to use a has_many :through ralationship. For example I've created another model "Applicant". Applicant belongs_to user and belongs_to offer. But how is the relationship from the user and offer model? For example:
User Model
has_many :offer, :through => :applicant
Offer Model
has_many :user, :through => :applicant
My doubt is because I already have this two relationship
User Model
has_many :offers, :dependent => :destroy
Offer Model
belongs_to :user
After solve this, I guest I have to save the record in the applicant model from the applicanst_controller, right?
Thanks in advance
What you have described is a many-to-many relationship using a join table. You're actually pretty close but you just need to remove the has_many :offers, :dependent => :destroy from your user model and the blongs_to :user in your offer model. It should look something like this:
class User < ActiveRecord::Base
has_many :offers, :through => :applicants
end
class Applicant < ActiveRecord::Base
belongs_to :users
belongs_to :offers
end
class Offer < ActiveRecord::Base
has_many :users, :through => :applicants
end
You don't have to worry about the dependent destroy part as associations are automatically removed as the corresponding objects are removed. With a many to many association it doesn't really matter how you go about building the relationship. Either of the following will work:
#user.offers << #offer
#offers.users << #user
If you don't need to store any information specific to your applicant join table (e.g., time stamps, descriptions) you might instead want to look at a has_and_belongs_to_many relationship. Check out choosing between has_many_through and has_and_belongs_to_many for reference.
Edit
Heres the code for a HABTM relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :offers
end
class Offer < ActiveRecord::Base
has_and_belongs_to_many :users
end

Resources