How to define a nested has_one association? - ruby-on-rails

Suppose we have this contrived model structure
class Apple < ActiveRecord::Base
belongs_to :fruit
has_one :tree, through: :fruit
has_one :organism, through: :tree
end
class Fruit < ActiveRecord::Base
belongs_to :tree
has_many :apples
end
class Tree < ActiveRecord::Base
belongs_to :organism
has_many :fruits
end
class Organism < ActiveRecord::Base
has_many :trees
end
To avoid having to call #apple.fruit.tree.organism, I have definded the two has_one-through directives in Apple, and expect #apple.organism to work, but it does not. #apple.tree.organism does work.
Am I doing something wrong? Should I just define a getter method for :organism on Apple instances and be done with it?

Your has_one technically performs the characteristic of belongs_to: think Organism has_many apples through trees and not the other way around. "belongs_to :through" does not work in Rails, so I suggest using delegate in your models.
class Apple < ActiveRecord::Base
delegate :organism, to: :fruit
end
class Fruit < ActiveRecord::Base
delegate :organism, to :tree
end

Related

Guarantee that model is associated to same parent

Assuming i have 3 models associated to each other:
class Farm < ApplicationRecord
has_many :horses
has_many :events
end
class Horse < ApplicationRecord
belongs_to :farm
has_many :events_horses, class_name: 'Event::EventsHorse'
has_many :events, through: :events_horses, source: :event, dependent: :destroy
end
class Event
belongs_to :farm
has_many :events_horses, class_name: 'Event::EventsHorse'
has_many :horses, through: :events_horses, source: :horse, dependent: :destroy
end
class Event::EventsHorse < ApplicationRecord
self.table_name = "events_horses"
belongs_to :horse
belongs_to :event
audited associated_with: :event, except: [:id, :event_id]
end
How to guarantee that each of the Horse belongs to same Farm as event? Possible solution is using custom validation, but i was wondering if there is some other way. I have few other models like Horse, so it force me to do custom validation method to each of them.
class Event
...
validate :horses_belongs_to_farm
private
def horses_belongs_to_farm
horses.all? {|h| h.farm_id == farm_id}
end
end
I think the model you are using is setting up too many id's between the tables that require consistency checking.
If you set the model up this way, then you don't need to validate that a horse's farm and event are consistent since the data ensures it:
class Farm < ApplicationRecord
has_many :horses
has_many :events
end
class Horse < ApplicationRecord
belongs_to :farm
has_many :events, through: :farm
end
class Event < ApplicationRecord
belongs_to :farm
has_many :horses, through: :farm
end
If you need efficient access to horses from events or events from horses, you can use joins. This gives some simplicity, clarity, and consistency.
You should also have a look at Choosing Between has_many_through and has_and_belongs_to_many.
[Edit based upon updated question and comments] Now that your model and question are a little more clear, my hunch is that putting the validation in the Event model causes redundant validations. Since your intent is to make sure that, in a given event, the horse and farm are consistent, I would put the validation in EventsHorses:
class Event::EventsHorse < ApplicationRecord
...
validate :horse_belongs_to_farm
private
def horse_belongs_to_farm
horse.farm_id == event.farm_id
end
end
As an aside, thy do you have Event::EventsHorse rather than simply have a separate model for EventsHorse?

How to traverse a nested association without typing every model in the association in rails

Let's say I have
ModelA < ActiveRecord::Base
has_many :modelb
end
ModelB < ActiveRecord::Base
has_many :modelc
belongs_to :modela
end
Modelc < ActiveRecord::Base
has_many :modeld
belongs_to :modelb
end
Modeld < ActiveRecord::Base
belongs_to :modelc
end
I want to be able to write
modeld.modela
What's the best way of doing it?
You can set up a chain using has_one :through (you could do implement modela.modelds similarly using has_many :through)
Modeld < ActiveRecord::Base
belongs_to :modelc
has_one :modelb, through: :modelc
has_one :modela, through: :modelb
end
Have you tried delegation?
delegate :modelb, to: :modelc
delegate :modela, to: :modelb
The easiest way to do that is define method modela in model Modeld.
Modeld < ActiveRecord::Base
belongs_to :modelc
def modela
self.modelc.try(:modelb).try(:modela)
end
end

Accessing one class from another using id as parameter

The association between class are as below
class Level < ActiveRecord::Base
has_many :levels_users
end
class LevelsUser < ActiveRecord::Base
belongs_to :level
has_many :schedules
end
class Schedule < ActiveRecord::Base
belongs_to :levels_user
end
I know the value of level.id. How do I find all schedules belonging to that level.id. level.id is the id of one of the level. I tried something like this:
Level.find(level.id).levels_users.schedule
But it won't work.
You could use through property of has_many association.
class Level < ActiveRecord::Base
has_many :levels_users
has_many :schedules, through: :levels_users
end
Now you can use
level.schedules

has_many nested to many models in Rails

I have 4 different models in rails and there is a has_many and belongs_to association between them. For example:
class Apple < AR::Base
has_many :bananas
end
class Banana < AR::Base
belongs_to :apple
has_many :oranges
end
class Orange < AR::Base
belongs_to :banana
has_many :berries
end
class Berry < AR::Base
belongs_to :orange
end
Now, if I write, like #apple.berries, I should get the list of berries belongs to that Apple. So how should I define this association? One of my friend told me to use inverse polymorphic association. I don't know whether its related to the above issue or not.
What you actually need here is has_many + through relationship. Right now, an Apple object does contain Berry objects, but Apple object doesn't contain them directly. So we need to tell it explicitly like:
class Apple < ActiveRecord::Base
has_many :bananas
has_many :berries, through: :bananas
end
class Banana < ActiveRecord::Base
belongs_to :apple
has_many :oranges
has_many :berries, through: :oranges
end
Now, you can berries directly on an Apple object as well as on a Banana object.

Dynamic has_many class_name using polymorphic reference

I am trying to associate a polymorphic model (in this case Product) to a dynamic class name (either StoreOnePurchase or StoreTwoPurchase) based on the store_type polymorphic reference column on the products table.
class Product < ActiveRecord::Base
belongs_to :store, polymorphic: true
has_many :purchases, class_name: (StoreOnePurchase|StoreTwoPurchase)
end
class StoreOne < ActiveRecord::Base
has_many :products, as: :store
has_many :purchases, through: :products
end
class StoreOnePurchase < ActiveRecord::Base
belongs_to :product
end
class StoreTwo < ActiveRecord::Base
has_many :products, as: :store
has_many :purchases, through: :products
end
class StoreTwoPurchase < ActiveRecord::Base
belongs_to :product
end
StoreOnePurchase and StoreTwoPurchase have to be separate models because they contain very different table structure, as does StoreOne and StoreTwo.
I am aware that introducing a HABTM relationship could solve this like this:
class ProductPurchase < ActiveRecord::Base
belongs_to :product
belongs_to :purchase, polymorphic: true
end
class Product < ActiveRecord::Base
belongs_to :store, polymorphic: true
has_many :product_purchases
end
class StoreOnePurchase < ActiveRecord::Base
has_one :product_purchase, as: :purchase
delegate :product, to: :product_purchase
end
However I am interested to see if it is possible without an extra table?
Very interesting question. But, unfortunately, it is impossible without an extra table, because there is no polymorphic has_many association. Rails won't be able to determine type of the Product.purchases (has_many) dynamically the same way it does it for Product.store (belongs_to). Because there's no purchases_type column in Product and no support of any dynamically-resolved association types in has_many. You can do some trick like the following:
class Product < ActiveRecord::Base
class DynamicStoreClass
def to_s
#return 'StoreOnePurchase' or 'StoreTwoPurchase'
end
end
belongs_to :store, polymorphic: true
has_many :purchases, class_name: DynamicStoreClass
end
It will not throw an error, but it is useless, since it will call DynamicStoreClass.to_s only once, before instantiating the products.
You can also override ActiveRecord::Associations::association to support polymorphic types in your class, but it is reinventing the Rails.
I would rather change the database schema.

Resources