Object that belongs to multiple other objects - ruby-on-rails

Take the following associations based on a Feedback model:
feedback.rb
belongs_to :buyer, :foreign_key => :buyer_id, :class_name => "User"
belongs_to :seller, :foreign_key => :seller_id, :class_name => "User"
belongs_to :listing
The reverse associations are has_many
When creating the feedback object it could be 'anchored' to any of the above other objects (e.g. buyer.feedbacks, seller.feedbacks, listing.feedbacks)
Say in this example we want to primarily link it with the buyer and so nest the feedback routes within user and then build a create action in the Feedbacks controller that looks something like:
current_user.feedbacks.new(feedback_params)
or is it more appropriate/correct to reference?
buyer.feedbacks.new(feedback_params)
Is this even valid without an explicit buyer model?
What is the Rails way to then incorporate the other belongs_to relationships?
It's not a nested attribute (as the other objects already exist).
Should I be merging in the other params with something like?
params.require(:feedback).permit(:rating, :comment).merge(seller_id: #seller.id, listing_id: #listing.id)
One approach I have seen is the use of a before_validation filter in this sort of manner (from a different domain) like this:
class Feedback < ActiveRecord::Base
belongs_to :user
belongs_to :host, :class_name => "User"
belongs_to :inquiry
belongs_to :room
before_validation :set_room_and_guest_id
def set_room_and_guest_id
self.room ||= self.inquiry.room if self.inquiry
self.user ||= self.inquiry.user if self.inquiry
end
I have spent a long time reading related posts about this today as well as the Rails documentation and have not been able to find a conclusion.

There's no "right" way here - just build the system you want. It sounds like a Buyer has Feedback about a Listing sold by a Seller, so it's completely reasonable to link all of those things together (or at least Amazon hasn't discovered a reason not to yet, and that's a pretty good sign).
The simplest "Rails way" you're probably looking for is to include all the relevant IDs in the feedback form. That does make them accessible to people who might want to fiddle, eg. by setting the seller/listing IDs to their own and leaving fraudulent positive feedback. You can combat that by merging #seller and #listing in the controller like you've mentioned, or with validations that the reviewer has bought the item, can't leave more than one, etc.
It also sounds like there might be room for a Transaction object here, representing the link between Buyer, Seller and Listing. Maybe the Feedback belongs on the Transaction.
Overall, I'd say you've been paralyzed by all the options available to you, and the best thing to do is pick one and run with it. When it's not clear which direction is best, that's often because you don't have all the information you need yet, but once you try something out you'll quickly learn more. Once you realize "Oh, I should have done this this other way", the beauty of code is you can change it.

I assume every listing has exactly one buyer and one seller, and there can be exactly zero or one feedbacks per listing. Then you might be confusing things a bit by linking the feedback directoy to buyer and seller. The normalized way would be to relate the feedback to the listing and nothing else. Then all questions go away.
(Note, you can then still get the buyer (or seller) directly from the feedback by using a :through rails relation.)

Maybe you are looking for Polymorfic Associations.
A slightly more advanced twist on associations is the polymorphic association. With polymorphic associations, a model can belong to more than one other model, on a single association.

Related

Rails association has_many and has_many through on same STI model

I have a model Note.
Notes are very useful and are used for lots of purposes throughout the application.
We have another model User, and these are STI split between
User1s
and
User2s
We have a third model Thing, which are things belonging to Users... I'll leave it there for the moment as it gets a touch more complicated, (there are other models a bit like Things which one can likewise make notes on).
Note.rb
belongs_to :user #This signifies that the user wrote the note
belongs_to :notable, polymorphic: :true #This is what the note is referring to
User.rb
has_many :user_notes, class_name: "Note", foreign_key: "user_id"
#signifies the note was written by the User
User1.rb < User
has_many :notes, as: :notable
#Signifies the note was about the User1
has_many :things
User2.rb < User
has_many :notes, as: :notable
#Signifies the note was about the User2
Thing.rb
belongs_to :user1
has_many :notes, as: :notable
#The note is about the thing
These can be, but are not generally, self-referential (I write a note about myself).
Often User1s will write notes about User2s, or about User2s Things so that User2 can see them.
I think I've taken the correct route in renaming the user_notes.
I'm struggling with how one collects together all the notes that might be related to me.
i.e. Let's say I'm a user1.
I'd like to see the notes I've written, plus notes other people have written about me, plus the notes that have been written about my things.
Is there an easy way to do that?
I'm struggling with two points.
How can I connect the notes about my things?
has_many :thing_notes, through: :things, source: :notes
works, but I wonder if it's the correct way of approaching it.
Also, I'm interested in the reverse (which is also sort of useful), in this case ignoring who might have written the note, to return the relevant user1, if there is one. But...
delegate :user1, to: :notable
doesn't make sense if user1 is already a potential notable_type.
How can I collate all my notes (of whatever type) and return them?
i.e.
#user1.notes
will return just the notes about me
#user1.user_notes
will return notes I've written
#user1.thing_notes
will return notes about my things
I could merge them together, but I'll risk there being annoying duplicates, and it seems a shame to make multiple database calls and lose db ordering when they're all of one type...
Is there an obvious way to get all my notes...?
Note.where("(user_id = :user1_id) OR (notable_id = :user1_id AND notable_type = 'User') OR (notable_type = 'Thing' AND notable_id IN (:things))", user1_id: user1.id, things:user1.thing_ids)
is a touch on the ugly side and will only get uglier...
Hope that makes sense to someone who loves to solve such puzzles...
For the collation, if you're using Rails 5, you could do something like this to simplify the raw SQL:
Note.where(user: user1).or(Note.where(notable: user1)).or(Note.where(notable: user1.things))
If you're not using Rails 5, you can include this gem for the same functionality: https://github.com/Eric-Guo/where-or

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

Making Model Associations to make Queries easier

I've came up with a Database Schema, but I feel it's more influenced by my old SQL habits than a Rails ORM. Here is the schema I developed.
Currently, I have made models for all the tables in the image above, however, after researching associations, I believe that I can just use Rails Associations to produce some of the many-to-many relational tables.
In my PlayerStats#index I find myself doing things like <%= Year.find(TeamYear.find(TeamUser.find(player_stat.team_user_id).team_year_id).year_id).year %> which I know is not good. So, I'm looking for guidance on how to build model associations based on the normalized schema above. Here is my guess:
User.rb
has_many :team_users
Team.rb
belongs_to :team_year
Year.rb
belongs_to :team_year
Team_Year.rb
has_many :teams
has_many :years
Team_User.rb
belongs_to :user
belongs_to :team_year
has_one :role
Game.rb
has_many :team_years
Player_stats.rb
belongs_to :team_user
belongs_to :game
I'm not sure if I'm on the right track or not. Furthermore, I'm not sure how to take advantage of declaring these associations. I'd definitely like to make the Team.name (given Player_Stat.game_id) a lot easier than Team.find(TeamYear.find(Game.find(player_stat.game_id).away_team_year_id).team_id).name.
Am I trying too hard to have a normalized DB? Should I be thinking about this problem differently? I appreciate any help or guidance.
Look on has_many through association.
Your DB Design is fine...
Consider for e.g. the common one to many relationships:
User model, and a Subject model having userid with it..
Now in sql terms you would put a user id in Subject. Subject[ id, userid, marks]
so in subject model you will define userid belongs_to User..
Read http://guides.rubyonrails.org/association_basics.html this in detail atleast once..and maybe try it a little in rails console with one two tables... and you will be good to go...
Also http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html has nice and clear explanation with example look from heading 'Cardinality and associations' in it...
The DB Design seems OK, except the Game-TeamYear relation, witch is unclear, but the direction of the query is not just what is easier to do.
Try starting the building not from the top, but from the base, in your case your query
<%= Year.find(TeamYear.find(TeamUser.find(player_stat.team_user_id).team_year_id).year_id).year %>
transforms into
<%= player_stat.team_user.team_year.year %>
and the query
Team.find(TeamYear.find(Game.find(player_stat.game_id).away_team_year_id).team_id).name
transforms into
TeamYear.find(player_stat.game.away_team_year_id).team.name
So you just go from inside query to the outside query, hope you got the idea.

not sure how to model in ActiveRecord

I am making a simple event system (like physical events, not software events). It has the following structure. Each event will have a set of slots (think a music event, opening act, healiner etc...). Each event_slot will be a reference to a tag. Right now I have the following but I think this is not going to work:
class Event < ActiveRecord::Base
#id primary key
has_many :event_slots, :order => "sort desc"
has_many :tags, :through => :event_slots
end
# event_slots will be populuated with tag_id
class EventSlot < ActiveRecord::Base
# event_id, tag_id; will also have a sort value to sort these
belongs_to :event
belongs_to :tag
end
the issue is that event_slots will have a tag_id. In other words we'll be adding the tags and associating them in place (like physically in place in a web form).
class Tag < ActiveRecord::Base
has_many :event_slots
end
I am not sure if this modeling will work. Any ideas on how to implement / improve this this? The has_many :through seems not be done correctly.
thx
That should work fine. I would perhaps try use a better name than EventSlots because its not intuitive what it represents. If its aimed at the music market something like Acts would perhaps make more sense.
Using something like accepts_nested_attributes_for on the Event model can help eliminate the need for a controller for the second nested model (Act/EventSlot). Take a look at http://railscasts.com/episodes/196-nested-model-form-part-1 if you havent already.
Lastly I would consider not rolling your own tagging system. There are already plenty of well tested gems you can use to provide the functionality you need. Check out https://www.ruby-toolbox.com/categories/rails_tagging.html to find one that might suit your needs.

Resources