Many-to-one bidirectional relation in Rails - ruby-on-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

Related

ActiveRecord Association - Design Question

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.

Should I use `has_and_belongs_to_many` or `has_many :through` in my model?

I have read the Choosing Between has_many :through and has_and_belongs_to_many on the Rails website, however I am a bit confused since I have a different case to the ones given on the website.
I have two models: Prop and CharacterCostume, and the character's costume can have multiple props associated to it, but a prop doesn't belong to that character and it can be used by any number of characters in the scene, too.
Right now I have has_and_belongs_to_many :props inside my CharacterCostume model, which does exactly what I want it to do: it fetches all the props associated with the costume using a table named character_costumes_props when I call CharacterCostume#props
However the association name is putting me off because of the "belongs to many" part. The costume does not belong to any of the props, so there's no has_and_belongs_to_many :character_costumes inside the Prop model.
I know that it can all function fine without it, but it got me thinking that maybe I should use a has_many :through association, but that requires me to create a superfluous model (it is superfluous, right?) and the model would look like this:
class CharacterCostumeProp < ActiveRecord::Base
belongs_to :character_costume
has_one :prop
end
Also, would has_one instead of belongs_to work here?
I want the code to be as semantic as possible, but I am not sure if this will increase the requirement for resources or decrease performance in some way, since there's an intermediate model.
Are there certain quirks/benefits attached to either approach? Is mine good enough? Or is my thinking completely wrong from what I need to do?
Thanks!
I think you want to use a :has_many, :through because you're going to want to work directly with the relation model - what scene(s), consumed or damaged, etc.
But, the reason it reads funny to you is that, for the most part, has_many and belongs_to don't really mean what they mean in English. What they really mean is "They have the foreign keys" and "I have the foreign key", respectively; the exception being the :dependent => :destroy behavior.
That still doesn't really help with has_and_belongs_to_many, since you're then saying, "They have the foreign keys and I have the foreign keys` - except that you can think of it sort of adding a new part both to "I" and "They" that happens to be the same part for each, and has those keys.
Does that help?
The single most important question you must ask yourself when deciding between HABTM and has_many :through is this:
Do I want to store any information specific to the association?
Example 1: magazine subscriptions
A many-to-many relationship between readers and magazines might conceivably be structured as a HABTM or a has_many :through. However, the latter makes far more sense in this case because we can easily think of information specific to the association that we might want to store.
A reader is related to a magazine through a subscription, and every subscription can be described by fields such as price, starting date, issue frequency and whether it's active or not.
Example 2: tags
The relationship between an existing Tag model and, say, an Article model is clearly of the many-to-many kind. The fact that one particular tag has been associated to any particular article must have no influence on whether the same tag will be able to be similarly associated to other articles in the future.
But, differently from the previous example, here the association itself is all the information we need. We just need to know which tags are associated to any given article. It doesn't matter when the association was formed. It doesn't matter how long it lasted.
It may matter to us how many articles a tag is associated with. But that information is stored in the Tag model since it's not specific to an association. There is even a Rails feature that takes care of that called counter_cache.
has_one wouldn't work, you'd need belongs_to
it is not superfluous if you have logic in your association model
has_and_belongs_to_many is good enough for you
See example below
class Student
has_and_belongs_to_many :courses
has_many :teachers, through: :courses
end
class Teacher
has_many :courses
has_many :students, through: :courses
end
class Course
has_and_belongs_to_many :students
belongs_to :teacher
def boring?
teacher.name == 'Boris Boring'
end
end
In the example above, I make use of both versions. See how Course would have its own logic? See how a class for CourseStudent might not? That's what it all comes down to. Well, to me it is. I use has_many through for as long as I can't give a proper name to my association model and/or the model doesn't need extra logic or behavior.

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

rails model relationship and migration

I have some problem trying to understand when building a rails app with several models and relation ships between them...
If I take a basic example like a model Group, a model User and a model Car
class Group < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :group
has_many :cars
end
class Car < ActiveRecord::Base
belongs_to :user
end
Will those relation ship statements automatically create the following functions:
group.users
user.group
user.cars
car.user
It seems that we sometimes need to have to create "references" in migration (like adding a reference toward User in Car table) but is this always required ?
In this case, what is the difference of creating the migration and of adding the relationship statement in the models ? I sometimes have the feeling this is used for the same purpose.
Thanks a lot for your help,
Regards,
Luc
The association declarations are there for Rails only. You have to define the foreign keys (references) in the database, so that Rails can properly save the data.
Remember, despite all the magic, it's still backed by a relational database, so good practices there will pay off in the long run.

Ruby On Rails Relationships - One to Many

I'm a beginning to ROR, but here's what I'm trying to achieve. I have two items I want to associate: matters and people. Each matter can have many people. That is, I want to create people and matters separately and later be able to link them.
For example, I may create:
Bill Clinton
Barack Obama
I may create the matters:
Global warming
War on terror
I want to be able to associate the users Bill Clinton AND Barack Obama to BOTH matters. Can someone point me to a tutorial that can show me how to do this?
I think has_and_belongs_to_many is used less and less by the RoR community now. While still supported, I think it is now more common to have an intermediate model (in your case something like PoliticianMatter) to join your Politician and Matter models.
Then your politician_matter table will have a PK, a politician_id and a matter_id.
Then you have
class PoliticanMatter < ActiveRecord::Base
belongs_to :politician
belongs_to :matter
end
The advantage of this approach is that if there ever need to be future properties of the politician -> matter relationship (e.g importance, date of last occurrence) you have a model which affords this - has_and_belongs_to_many would not support the addition of these extra properties.
You can also access the many to many relationship directly from the Politician and Matter models like this
class Politician < ActiveRecord::Base
has_many :politician_matters
has_many :matters, :through => :politician_matters
end
class Matter < ActiveRecord::Base
has_many :politician_matters
has_many :politicians, :through => :politician_matters
end
You need a many2many relationship between these two entities.
A matter can be studied by many people
A person can studie several matters
Rails uses the has_and_belongs_to_many helper to do that. You'll find more about that in the documentation and many many blog posts!
has_and_belongs_to_many helper
class Politician < ActiveRecord::Base
has_and_belongs_to_many :tasks
end
class Task < ActiveRecord::Base
has_and_belongs_to_many :politicians
end
What you need are 3 tables:
politicians, tasks and politicians_tasks (having the two columns politician_id and task_id, no primary key)
Hope this helps
Seb

Resources