I'm having trouble figuring out the right way to set this association up.
I have 3 models: Musicians, Bands and Managers. Each of those can have a certain "level" associated with them (stress, happiness, etc).
But I'm not sure how to correctly associated my Levels model with the other 3.
Do I need some sort of has_many :through that's polymorphic? And how on earth do I set that up? Is there some other type of associated I need?
If this is the case where you're defining attributes that can be assigned to a particular class of model, then you probably want to use a more conventional approach. Instead of kind use something like record_type and build a scope on that instead.
That way what you'd do to fetch them is create an accessor method, not a relationship, between any of your entities and this column. Something like this:
def levels
Level.for_record_type(self.class)
end
The for_record_type is a scope:
scope :for_record_type, lambda { |record_type|
where(:record_type => record_type.to_s)
}
There's no convention for associating models with classes instead of instances of other models.
Yep, this would be a polymorphic association:
class Level < ActiveRecord::Base
belongs_to :leveled, :polymorphic => true # maybe there's a better word than "leveled"
end
class Musician < ActiveRecord::Base
has_many :levels, :as => :leveled
end
You'll need to add a leveled_type and leveled_id to your Levels table.
Related
Is it possible to make a scope for a polymorphic model in the following case?
I have a polymorphic model named Mutations.
class Mutation < ApplicationRecord
belongs_to :mutationable, :polymorphic => true
end
A Mutation belongs to both models TimeRegistration and SickRegistration. A TimeRegistration and a SickRegistration (Mutationable) belongs_to a User.
class SickRegistration < ApplicationRecord
belongs_to :user
has_many :mutations, as: :mutationable
end
class TimeRegistration < ApplicationRecord
belongs_to :user
has_many :mutations, as: :mutationable
end
I want to create a scope for the Mutation whereby i can retrieve a collection of Mutations by a given user name. I have more scopes already on the Mutation model, so this one must be joined with the other used scopes (used for filtering).
So something like this on the Mutation Model:
scope :with_name, -> (name) { joins(mutationable: :user).where('users.name = ?', name) }
This won't work. I've also tried to make a delegate on the Mutation model and to make a custom SQL Query with multiple joins on the mutationable models, but without success. I think there must be an (more easy) way to do this, but i can't find any good examples or ansewers for this problem.
Please help. Thanks in advance!
******TRY THIS*****
Mutation.rb
belongs_to :sick_registation, ->{where(mutations: {mutationable_type: 'SickRegistation'})},
foreign_key: 'mutationable_id'
belongs_to :time_registation, ->{where(mutations: {mutationable_type: 'TimeRegistation'})},
foreign_key: 'mutationable_id'
scope :with_name, ->(name){
joins(sick_registation: :user).where(user:{name: name}) +
joins(time_registation: :user).where(user:{name: name})
}
Explanation:
There is no direct relation between your polymorphic relation and user.
So, I am joining the results of individual associated i.e sick and time.
Try
scope :with_name, -> (name) { where(mutationable: User.where(name: name)) }
edited. added )
Edit2:
My bad for my "wrong quick solution" due to not reading carefully enough, but I see the question has been edited and it is more clear now.
Knowing what you want to achieve I would suggest a different approach. Mutable should not be hardcoded looking at specific models because it will limit the flexibility provided by the polymorphic associations and will break 'Law of Demeter'
To get a collection of mutations of a particular set of XXXRegistrations I would do:
a. Use STI so the queriable XXXRegistrations, extend a Registration model. Add a scope to filter by user.
SickRegistration < Registration
b. Query Registration for user and then get the mutations
Registration.for_user(user).joins(:mutations)
Using Rails 4 (and Postgres), I'm trying to work out the best way in which to structure my ActiveRecord associations and corresponding database tables/relationships for a diet tracking app.
I want my app to be structured as follows:
I have Document and FoodEntry models. Each Document has a number of FoodEntries. I want to be able to iterate over them like document.food_entries.each ... (which is easy with your typical has_many association).
However all the FoodEntries for each Document need to be able (potentially but not necessarily) to be subdivided by day, as this is a natural division for which logic and calculations must be able to be performed, in addition to doing them for the whole document. For instance I'd be using something like document.day(1).food_entries.each ....
Furthermore, each day should be able to be subdivided (again, optionally) into meals in a similar manner, e.g. document.day(1).meal(1).food_entries.each ...
Lastly, there must be a way to record the user-specified order that the FoodEntries, meals, and days are in for each document. Presumably using number sequence(s)?
I was thinking there are a few ways I could do this:
Use a simple has_many relationship. Have day, meal and sort columns in the food_entries table, where the value for day and meal is left blank or given a default value if a day/meal isn't provided. Use a logic-based approach to get and sort the entries for a day or meal.
Outline:
class Document
has_many :food_entries
class FoodEntry
belongs_to :document
Potential issues:
This might leave things a bit messy in general in the table?
All the logic for subdividing things would have to be hand-coded.
Storing/using the user-defined (i.e. arbitrary) sort order might get a bit complicated? The order for entries AND days AND meals would have to stored in and inferred from one sequence (unless more columns were added).
Use has_many :through to set up associations through days and meals (naming?) tables. Entries where a day/meal isn't specified get given a default. Both these tables have their own individual sort column, along with the food_entries table.
Outline:
class Document
has_many :days
has_many :meals, through: :days
has_many :food_entries, through: :days (AND :meals???)
class Day
belongs_to :document
has_many :meals
has_many :food_entries, through: :meals
class Meal
belongs_to :day
has_many :food_entries
class FoodEntry
belongs_to :meal
Potential issues:
Adds unnecessary relational complexity? (consider that days or at the very least meals are meant to be optional)
Can I even use has_many :food_entries through: ... in my Document model if it would have to go through both tables?
A compromise between the two approaches above: have a days table but keep meal in a column in the food_entries table.
Something else? Polymorphic association(s)?
This is getting a bit complicated to wrap my head around, and so I'm really having a hard time working out what I should use. What is the correct way to go about things?
A couple of final questions which are related but completely optional:
Ideally the day value could be either a datetime value or an arbitrary string, depending on what the user sets. Is this possible?
Could anyone point me to a resource that can inform me about sorting/ordering strategies? Like I said I assume the simplest way is to use a sequence of numbers, but I'm not exactly sure how I would work with such a sequence.
has_many :through
You'd only use a has_many :through relationship if you wanted to attribute multiple FoodEntries to Document, like this:
#app/models/document.rb
Class Document < ActiveRecord::Base
has_many :food_entries_types
has_many :food_entries, through: :food_entries_types
end
#app/models/food_entry_type.rb
Class FoodEntryType < ActiveRecord::Base
belongs_to :document
belongs_to :food_entry
end
#app/models/food_entry.rb
Class FoodEntry < ActiveRecord::Base
has_many :food_entries_types
has_many :documents, through: :food_entries_types
end
This would only allow you to associate many food_entries with a similar number of documents. Although you could add specific days & meals attributes to the join model, allowing you to call them as required
Scopes
I believe a much better option for you is to use ActiveRecord scopes:
#app/models/document.rb
Class Document < ActiveRecord::Base
has_many :food_entries
#uses [wday][3]
#needs to return dates for specific day
scope :day, ->(day = 1) { where(created_at: Date::DAYNAMES[day]) }
scope :meal, ->(meal = 1) { where(meal: meal) }
end
Because scopes can be chained, I believe you'd be able to do this:
food = Document.day(1).meal(2).food_entries
Class Method
You could also create a class_method to achieve something similar:
#app/models/document.rb
Class Document < ActiveRecord::Base
has_many :food_entries
def self.sorted(day = 1, meal = 1)
document = self.where("created_at = ? AND meal = ?", Date::DAYNAMES[day], meal)
end
end
#app/controllers/documents_controller.rb
def show
#document = Document.sorted
end
I ended up implementing my #2 option. This has given me the most flexibility and worked out well. I think it has been the most elegant approach for my use case.
Imagine the following situation:
I have a dog model and a house model. A dog can belong to a house, and a house can have many dogs, so:
Class Dog < ActiveRecord::Base
belongs_to :house
end
Class House < ActiveRecord::Base
has_many :dogs
end
Now, imagine that I also want to create dogs that don't have a house. They don't belong to house. Can I still use that relationship structure and simply don't inform a :house_id when creating it?
Is there a better practice?
Obs.: I used this analogy to simplify my problem, but my real situation is: I have a model a user can generate instances of it. He can also create collections of those instances, but he can leave an instance outside a collection.
Be careful with this in Rails 5...
#belongs_to is required by default
From now on every Rails application will have a new configuration
option config.active_record.belongs_to_required_by_default = true, it
will trigger a validation error when trying to save a model where
belongs_to associations are not present.
config.active_record.belongs_to_required_by_default can be changed to
false and with this keep old Rails behavior or we can disable this
validation on each belongs_to definition, just passing an additional
option optional: true as follows:
class Book < ActiveRecord::Base
belongs_to :author, optional: true
end
from: https://sipsandbits.com/2015/09/21/whats-new-in-rails-5/#belongs_toisrequiredbydefault
I think it is absolutely normal approach.
You can just leave house_id with null value in database for the models which don't belong to other.
I'm looking for some suggestions on how to deal with "Regions" in my system.
Almost all other models in the system (news, events, projects, and others) need to have a region that they can be sorted on.
So far, I've considered a Region model with has_many :through on a RegionLink table. I've never had a model joined to so many others and wonder if this route has any negatives.
I've also considered using the acts_as_taggable_on gem and just tag regions to models. This seems ok but I'll have to write more cleanup type code to handle the customer renaming or removing a region.
Whatever I choose I need to handle renaming and, more importantly, deleting regions. If a region gets deleted I will probably just give the user a choice on another region to replace the association.
Any advice on handling this is greatly appreciated.
If each News, Event, etc. will belong to only 1 Region, tags don't seem the most natural fit IMO. This leaves you with 2 options:
Add a region_id field to each model
This is simplest, but has the drawback that you will not be able to look at all the "regioned" items at once - you'll have to query the news, events, etc. tables separately (or use a UNION, which ActiveRecord doesn't support).
Use RegionLink model with polymorphic associations
This is only slightly more complicated, and is in fact similar to how acts_as_taggable_on works. Look at the Rails docs on *belongs_to* for a fuller description of polymorphic relationships if you are unfamiliar
class Region < ActiveRecord::Base
has_many :region_links
has_many :things, :through => :region_links
end
# This table with have region_id, thing_id and thing_type
class RegionLink < ActiveRecord::Base
belongs_to :region
belongs_to :thing, :polymorphic => true
end
class Event < ActiveRecord::Base
has_one :region_link, :as => :thing
has_one :region, :through => :region_link
end
# Get all "things" (Events, Projects, etc.) from Region #1
things = Region.find(1).things
Renaming is quite simple - just rename the Region. Deleting/reassigning regions is also simple - just delete the RegionLink record, or replace it's region_id.
If you find yourself duplicating a lot of region-related code in your Event, etc. models, you may want to put it into a module in lib or app/models:
module Regioned
def self.inluded(base)
base.class_eval do
has_one :region_link, :as => :thing
has_one :region, :through => :region_link
...
end
end
end
class Event < ActiveRecord::Base
include Regioned
end
class Project < ActiveRecord::Base
include Regioned
end
Checkout the cast about polymorphic associations. They did change a bit in rails 3 though: http://railscasts.com/episodes/154-polymorphic-association?view=asciicast
I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end