ActiveRecord Association - Design Question - ruby-on-rails

Overview
I have a set of data models for a Sailing club example, where there are Members of the club, who have a relationship with a Boat (eg Owner, Skipper, Crew). Currently wrestling with the best way to design the ActiveRecord associations to answer some questions on getting data. This would include such queries as:
A Boat has many Members,
A Member can belong to one or more Boats
A Member has one or more roles associated to a Boat (Skipper, Crew, Owner)
First Design Attempt
So my first design was to have a Boat <- many to many -> Member structure using the has_many_through association type. The join table would carry the attribute for the type of relationship with the Boat This looks like:
class Boat < ApplicationRecord
has_many :boat_crews
has_many :members, through: :boat_crews
end
class Member < ApplicationRecord
has_many :boat_crews
has_many :boats, through: :boat_crews
end
class BoatCrew < ApplicationRecord
belongs_to :member
belongs_to :boat
end
The table BoatCrews has the usual attributes id, member_id, boat_id and also includes another attribute called role:string to represent the relationship to the boat. So when a Member is associated to a boat and entry is placed in the join table to hold the join information with the role. If a Member is both and owner and say a skipper of the boat then there are two roles in the BoatCrews table.
Now what this enables is the expected ActiveRecord Association queries such as
b = Boat.find(1)
b.members # show all the members associated with the first Boat
The only downside is that if a Member has two roles (Owner, Skipper) then the b.members query will return an array which contains one duplicate (ie. the Member with two roles). This can be removed with a b.members.uniq
Is there a better design where I model the role/relationship to the Boat differently, hence Design attempt number 2
Second Design Attempt
Thinking on it some more I thought that I could seperate the idea of the member and the relationship to the concept of a boat and a generic crew. So new model attempt is
class Member < ApplicationRecord
has_one :crew
end
class Crew < ApplicationRecord
has_many :boat_crews
has_many :boats, through: :boat_crews
belongs_to :member, optional: true
end
class Boat < ApplicationRecord
has_many :boat_crews
has_many :crews, through: :boat_crews
end
class BoatCrew < ApplicationRecord
belongs_to :boat
belongs_to :crew
end
The idea is that a Boat has a Crew collection and vice versa with the Crew model identifying the type of Crew role (eg crew, skipper, owner). The association of the Member to Crew models is to then take the Member information (eg name, etc) and associate to the role they have with the Boat crew model.
The flaw in this design is that the has-one relationship defined between the Member and Crew models means that foreign_key faults occur if you try to do a member#crew.build type creation.
Is there a better option
At this point neither approach seems correct. The first design works, but requires use of an additional constraint (needing to use uniq method). The second design doesn't work and on reflection is probably correct in that the semantics I've defined is 'has-one' but I'm really got more than one (eg Member has two roles as part of Boat).
Some ideas I've thought about are
Revert to the Boat has_many Member with a join table and then use a new model from that join table to model the type of relationship
Does introducing a model off a join table introduce too many levels of indirection ?
So seeking any suggestions or comments on how to tackle this one ?

Unless I'm misunderstanding something, I don't think your design attempt 2 solves for the 'problem' that arises with design attempt 1. If a member can only have one 'crew', then they would still be doubly counted when being considered a part of a boatcrew, now you have a duplicate crew (that points to the same member).
Though this isn't a perfect rule, when doing initial architecture, it's a decent rule-of-thumb to model things as close to the real world as you can. Optimizations can come later. In this case, I would go with attempt 1, but don't use uniq (because that would require loading everything into ruby, and then getting distinct record, which is slow). Rely on rails Active Record Query Interface instead. You can use something like,
b.members.distinct
which generates a SELECT DISTINCT ... query for when you need unique members. Realistically though, it's highly likely you want to query for knowledge of the members and their roles. I'm not sure of the easiest way to do this in pure rails active query interface, but, you can always use array_agg if you want to get the member and all of their roles.
For example
SELECT members.name, array_agg(boat_crews.role) AS roles FROM members
INNER JOIN boat_crews on members.id = boat_crews.member_id
INNER JOIN boats on boat_crews.boat_id = boats.id
WHERE boats.id = 1
GROUP BY members.name
would return something like
names | roles
joe | ["rower"]
john | ["skipper","captain"]
Will get you the names of all members, and all of their respective roles as an array. I leave it to you to convert this to an active record query.
A rather long-winded way of saying go with option 1, and ask specific questions here when you need help with certain queries.

Related

Rails field on join entity in has_many through relationship

I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.

How do I define many to many associations and the related properties in Rails (Active record)?

I am wondering how do I add additional properties to a many to many relationship.
I have two models that share a many to many relationship, Company and Profession.
Many professionals could belong to a Company
So my Company model looks like below
class Company < ActiveRecord::Base
has_and_belongs_to_many :professions
end
The same people in the same profession could belong to multiple companies as well
so
class Profession < ActiveRecord::Base
has_and_belongs_to_many :companies
end
Now I need to associate an hourly rate which could be different for each of the companies for the same profession. I am not very sure where to introduce the hourly rate property? Even if I were to add that to the joining table, how do I access that rate using active record?
This is a typical scenario where you select has_many through over habtm. As a rule, if you only need to associate two models, no other info needed to be stored in the association, use habtm. For most cases, you have to use has_many through. You case falls under this scenario.
You want to save the hourly rate in the table that associates a Profession and a Company. If you have existing data that you want to migrate, you may want to look at this post How to migrate has_and_belongs_to_many to has_many through?. If you can drop the joins table you use for the habtm association, just drop it and create a new table.

Modelling an application in Rails - mixing STI and polymorphic associations

Please forgive me if this has been answered already but I've searched a lot and still require some clarification.
I'm building a Property Management tool using Rails 3.2. I already have a User model and authorisation/authentication code in place with full tests coverage.
I've just started drawing out the models/classes/tables and have gotten myself a bit confused.
Let's start with Users.
Modelling Users
I plan to have allow multiple companies to use this system. Each will have employees (users). These users will have different roles e.g. Manager, Agent, Accountant, Secretary etc. For the most part the data I plan to store for each of these users will be similar (or so I think at the moment) so I am leaning towards Single Table Inheritance and using the type to define the level of access each employee has.
Secondly, I plan to allow Landlord and Tenants to also log in to the system. Upon logging in they'll be able to view information about the property they are owning or renting - maybe keep their contact details up to date too.
I was thinking of using polymorphic associations to represent these users.
So the plan I have at the moment and would like some feedback on is to have
User < ActiveRecord::BASE
Employee < User (this will have a STI type column and allow for the different employee roles)
Landlord < User
Tenant < User
Is this the best way of approaching this problem or am I shooting myself in the foot?
I've had some people advise me I should have a 'roles' table and assign roles to the users - but I have a feeling this isn't the most elegant way to do this in Rails.
Properties
My next issue is with Properties. Right now I have a model for properties and when I add them they belong_to a User (i.e. they have a user_id foreign key). I then started thinking "what happens if the employee (user) that added the Property leaves the company or has their account deleted for some reason?"
So in this scenario is it best to forgo the User/Employee to Property association and just link the Property to the Company that the employee belongs to? This way I can all employee.company.properties to list out all the properties?
Landlord and Tenant associations
Let's presume we make Properties belong to a Company.
In terms of associations this is what I have in my head. Looking at it now I see that everything belongs to a company because one company using the system shouldn't be able to see the landlords/tenants/employees/properties of another company.
class Landlord < User
belongs_to :company
has_many :properties, :through => :ownerships
end
class Tenant < User
belongs_to :company
has_one :property, :through => tenancies #you can only live at one place at a time right?
end
class Property < ActiveRecord::Base
has_many :tenants, :through => :tenancies
has_many :landlords, :through => :ownerships
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :properties
has_many :employees
has_many :landlords :through => :ownerships #not sure if through is required/works here
has_many :tenants :through => :tenancies #not sure if through is required/works here
end
class Employees < User
belongs_to :company
end
Properties
Also I'm guessing we'll have different types of Properties (Commercial/Residential) and of those there will be whole buildings, apartments within a building (single address) etc.
Like with users I'm planning on using Polymorphic Associations to define two subclasses CommercialProperty and ResidentialProperty and then use sti to define a type. If the type is "multi unit" then have a new model for units and an association whereby Property has_many Units and a Unit belongs_to a property.
Aim
I'm trying to make sure that my code follows best practice as much as possible so that when I come to add new features and expand I'm not left having to re-write large chunks of the app.
I would really appreciate your feedback.
Reference
Some of the posts I've read. Hopefully to help others trying to solve the same problem.
Designing a Rails application: single table inheritance?
Ruby on rails with different user types
Ruby On Rails User Model for multiple types
It's probably too late but you could also use has_and_belongs_to_many on User and Company and thus avoid STI altogether by using gems cancan and rolify.
It allows you to define finely grained access rights (abilities).
I know that it seems more elegant having different classes instead of roles, but it is not viable long-term strategy, it can become messy when logic becomes complex.
Other then that, the rest seems pretty solid, hope that helps :)

Many-to-one bidirectional relation in Rails

I'm begininng with Rails and I'm getting kind of confused with the relations.
The problem is quite simple, I have a Station class representing a train station, and a Shop class. I'd simply want the shop to have a station, representing the closest train station, so I guess it would be a many-to-one relation.
Without any ORM I'd just add a foreign key of the station in the shop table. After looking up about rails relations, I ended up with
class Shop < ActiveRecord::Base
belongs_to :station
end
class Station < ActiveRecord::Base
has_many :shop
end
As properly speaking, the shop does not really BELONG to a station I'm finding this kind of strange, so I'd like to know if this is the right way to proceed or if I'm getting confused.
Thank you in advance.
This is the right way to proceed. "Belongs to" simply means "has a foreign key to" - it doesn't necessarily mean that this is a sensible way of describing the relationship in real terms.
As posted, the code won't quite work - you need to pluralize the has_many side; i.e.:
class Station < ActiveRecord::Base
has_many :shops
end
You can test the relationship actually works by firing up the rails console ('rails c') from your application folder and the experimenting with creating objects. Assuming you've created the corresponding tables, you should be able to do things like:
station = Station.create
shop = Shop.create
shop.station = station
station.shops
station.shops.build
...etc
belongs_to and has_many do not describe either the ownership or the scope or the life cycles for the objects they relate. They just describe the references (the keys) between the objects.
Such references can have their life cycle tied with :dependent, :autosave, etc. options.
Other options such as :read_only reduce privileges of edition from reference to another.
Ownership is a concept that you have to define yourself. For instance: a Post in a forum can "belong" to different users with different privileges. Who is the owner? The admin? The last editor? The one that created the post? For such behaviors, extra definition and mechanics are needed. I recommand you take a look at the CanCan gem for this ( https://github.com/ryanb/cancan ).
class Station < ActiveRecord::Base
has_many :shops, :dependent => "nullify"
end
if your station gets deleted still the shops will be their

Rails has_one :through association

Rails has a has_one :through association that helps set up a one-to-one association with a third model by going through a second model. What is the real use of that besides making a shortcut association, that would otherwise be an extra step away.
Taking this example from the Rails guide:
class Supplier < ActiveRecord::Base
has_one :account
has_one :account_history, :through => :account
end
class Account < ActiveRecord::Base
belongs_to :supplier
has_one :account_history
end
class AccountHistory < ActiveRecord::Base
belongs_to :account
end
might allow us to do something like:
supplier.account_history
which would otherwise be reached as:
supplier.account.history
If it's only for simpler access then technically there could be a one-to-one association that connects a model with some nth model going through n-1 models for easier access. Is there anything else to it that I am missing besides the shortcut?
Logic, OK it might sound a bit weak for this but it would be logical to say that "I have a supplier who has an account with me, I want to see the entire account history of this supplier", so it makes sense for me to be able to access account history from supplier directly.
Efficiency, this for me is the main reason I would use :through, simply because this issues a join statement rather than calling supplier, and then account, and then account_history. noticed the number of database calls?
using :through, 1 call to get the supplier, 1 call to get account_history (rails automatically uses :join to retrieve through account)
using normal association, 1 call to get supplier, 1 call to get account, and 1 call to get account_history
That's what I think =) hope it helps!
I'm surprised no one has touched on Association Objects.
A has_many (or has_one) :through relationship facilitates the use of the association object pattern which is when you have two things related to each other, and that relation itself has attributes (ie a date when the association was made or when it expires).
This is considered by some to be a good alternative to the has_and_belongs_to_many ActiveRecord helper. The reasoning behind this is that it is very likely that you will need to change the nature of the association or add to it, and when you are a couple months into a project, this can be very painful if the relationship were initially set up as a has_and_belongs_to_many (the second link goes into some detail). If it is set up initially using a has_many :through relationship, then a couple months into the project it's easy to rename the join model or add attributes to it, making it easier for devs to respond to changing requirements. Plan for change.
Inverse association: consider the classic situation user-membership-group. If a user can be a member in many groups, then a group has many members or users, and a user has many groups. But if the user can only be a member in one group, the group still has many members: class User has_one :group, :through => :membership but class Group has_many :members, :through => memberships. The intermediate model membership is useful to keep track of the inverse relationship.
Expandability: a has_one :through relationship can easy be expanded and extended to a has_many :through relationship

Resources