Rails transparent child relationship - ruby-on-rails

I have a polymorphic relationship, and I would like the child (polymorph?) to be completely transparent. The setup is generic:
class ScheduledEvent < ActiveRecord::Base
belongs_to :scheduleable, polymorphic:true
#has column names like #starts_at, #ends_at
end
class AppointmentTypeOne < ActiveRecord::Base
has_one :scheduled_event, :as=>:scheduleable, :dependent=>:destroy
end
class AppointmentTypeTwo < ActiveRecord::Base
has_one :scheduled_event, :as=>:scheduleable, :dependent=>:destroy
end
I would like to be able to treat AppointmentTypeOne and AppointmentTypeTwo as if THEY had the #starts_at and #ends_at table columns.
Method-wise it's very easy to add #starts_at, #starts_at=, etc to my AppointmentX classes, and refere back to ScheduledEvent. But how can I setup so that the relationship is transparent to ActiveRelation also? Letting me do something like:
AppointmentTypeOne.where('starts_at IS NOT NULL')
(not having to join or include :scheduled_event)

It sounds like you want to use Single Table Inheritance, not a has_one association. That will allow you to create subclasses of ScheduledEvent for each appointment type:
class ScheduledEvent < ActiveRecord::Base
end
class AppointmentTypeOne < ScheduledEvent
end
class AppointmentTypeTwo < ScheduledEvent
end
Basically, you add a type column to your scheduled_events table, and rails takes care of the rest.
This forum post covers all of the details: http://railsforum.com/viewtopic.php?id=3815

Related

Create Rails scope comparing fields on two tables

I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }

Overriding a has_many association getter

A user can have several cars -
User: has_many :cars
Car: belongs_to :user
Every time I call #user.cars it returns the list of cars in default search order.
If I wanted the association sorted on some arbitrary field, I could do
class User < ActiveRecord::Base
has_many :cars, -> { order :num_wheels }
end
But let's say my ordering logic is complex and I want to just override the association getter to implement my own logic
I try something something like -
class User < ActiveRecord::Base
has_many :cars
def cars
# Pretend this is complex logic
cars.order(:num_wheels)
end
end
However that obviously fails because you can't reference the original cars from inside the overridden cars method without it looping infinitely.
Is there a way to reference the "original" getter from inside my overridden getter?
Thanks!
Use super:
class User < ActiveRecord::Base
has_many :cars
def cars
# Pretend this is complex logic
super.order(:num_wheels)
end
end
when you use a macro like has_many, Rails dynamically creates a module(which could be accessed by User.generated_association_methods).In your case, define the accessors and readers(such as "cars" in your case, which could be accessed by User.generated_association_methods.instance_methods). This module becomes the ancestor of your User class, so you can access the reader method(cars) by "super" in your own cars method.
With my understanding I believe what has_many is essentially doing is:
Class User < ActiveRecord::Base
has_many :cars
# is essentially
def cars
Car.where(user_id: self.id)
end
end
So when a user wants to list all the cars it would still be User.cars. When using ActiveRecord the has_many is assuming both the method name of cars and the foreign keys associated.
Try this:
class User < ActiveRecord::Base
has_many :cars
def cars
Car.where(user_id: id).order(:num_wheels)
end
end

What type of relations to choose if I want to link 2 models with one

I have model Message. It may be posted by either Person or Organization from my DB.
I want to call update.contact, where contact is Organization or Person.
class Update < ActiveRecord::Base
has_one :contact
The decision I like to use is like
class Organization < ActiveRecord::Base
belongs_to :update, as: :contact
But this approach not available in Rails.
Should I use polymorphic association? How to organize the architecture for this case?
It sounds like Organization and Person might be two different variants of a same entity (a customer, user, whatever). So, why not create a common parent model for the two of them? Such a parent wouldn't necessarily need a table, and you might just define common methods inside of it. Update is more of an action rather than an object, which could be applied to a Contact object (typically in its controller). For the Contact class, polymorphic association can be used. So you might have:
class Parent < ActiveRecord::Base
# stuff Person and Organization share
end
class Person < Parent
has_one :contact, as: :owner
# Person methods etc.
end
class Organization < Parent
has_one :contact, as: :owner
# Organization stuff
end
class Contact
belongs_to :owner, polymorphic: true
def update
#...
end
# other stuff for Contact
end
Then you can write lines like:
Person.first.contact.update
or whatever you need to do with your objects.
In case your Organization and Person don't differ too much, you could just create a table for the parent class, and add the has_one etc. in there.

rails how to save a model with has_many through association

I am having models like
// Contains the details of Parties (Users)
class Party < ActiveRecord::Base
has_many :party_races
has_many :races, :through=>:party_races
end
// Contains the party_id and race_id mappings
class PartyRace < ActiveRecord::Base
belongs_to :party
belongs_to :race
end
// Contains list of races like Asian,American,etc..
class Race < ActiveRecord::Base
has_many :party_races
has_many :parties, :through => :party_races
end
Now, lets say I'm creating an instance of Party
party_instance = Party.new
How am I supposed to add multiple Races to party_instance and save to database ?
You could use nested attributes to make one form that allows children. There are many examples on this site. Read up on the following first:
Accepts nested attributes
Fields for
A railscast about your use case
You also can create new PartyRace for each Races that you can add:
def addRace( party_instance, new_race )
party_race = PartyRace.new( party: party_instance, race: new_race )
party_race.save
end

How do I model this multi-inheritance relationship w/ Ruby ActiveRecord?

Assuming I have 5 tables. Can ActiveRecord handle this? How would you set it up?
The hierarchy:
Account (Abstract)
CorporateCustomer (Abstract)
PrivateCustomer
PublicCustomer
GovernmentCustomer
Edit: In nhibernate and castle activerecord the method needed to enable this scenario is called "joined-subclasses".
You could try something along the following lines.
class Account < ActiveRecord::Base
belongs_to :corp_or_gov_customer, :polymorphic => true
def account_id
self.id
end
end
class GovernmentCustomer < ActiveRecord::Base
has_one :account, :as => :corp_or_gov_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.account.send( symbol, *args )
end
end
class CorporateCustomer < ActiveRecord::Base
has_one :account, :as => :corp_or_gov_customer, :dependent => :destroy
belongs_to :priv_or_pub_customer, :polymorphic => true
def method_missing( symbol, *args )
self.account.send( symbol, *args )
end
end
class PrivateCustomer < ActiveRecord::Base
has_one :corporate_customer, :as => :priv_or_pub_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.corporate_customer.send( symbol, *args )
end
end
class PublicCustomer < ActiveRecord::Base
has_one :corporate_customer, :as => :priv_or_pub_customer, :dependent => :destroy
def method_missing( symbol, *args )
self.corporate_customer.send( symbol, *args )
end
end
I've not tested this code (or even checked it for syntax). Rather it's intended just to point you in the direction of polymorphic relations.
Overriding method_missing to call nested objects saves writing code like
my_public_customer.corporate_customer.account.some_attribute
instead you can just write
my_public_customer.some_attribute
In response to the comment:
The problem is that concepts like "is a", "has many" and "belongs to" are all implemented by foreign key relationships in the relational model. The concept of inheritance is completely alien to RDB systems. The semantics of those relationships has to be mapped onto the relational model by your chosen ORM technology.
But Rails' ActiveRecord library doesn't implement "is_a" as a relationship between models.
There are several ways to model your class hierarchy in an RDB.
A single table for all accounts but with redundant attributes - this is supported by ActiveRecord simply by adding a "type" column to your table. and then creating your class hierarchy like this:
class Account < ActiveRecord::Base
class GovernmentCustomer < Account
class CorporateCustomer < Account
class PublicCustomer < CorporateCustomer
class PrivateCustomer < CorporateCustomer
Then if you call PrivateCustomer.new the type field will automatically be set to "PrivateCustomer" and when you call Account.find the returned objects will be of the correct class.
This is the approach I would recommend because it's by far the simplest way to do what you want.
One table for each concrete class - As far as I know there is no mapping provided for this in ActiveRecord. The main problem with this method is that to get a list of all accounts you have to join three tables. What is needed is some kind of master index, which leads to the next model.
One table for each class - You can think of tables that represent the abstract classes as a kind of uniform index, or catalogue of objects that are stored in the tables for the concrete classes. By thinking about it this way you are changing the is_a relationship to a has_a relationship e.g. the object has_a index_entry and the index_entry belongs_to the object. This can be mapped by ActiveRecord using polymorphic relationships.
There is a very good discussion of this problem in the book "Agile Web Development with Rails" (starting on page 341 in the 2nd edition)
Search for ActiveRecord Single Table Inheritance feature.
Unfortunately I can't find a more detailed reference online to link to. The most detailed explanation I read was from "The Rails Way" book.
This is one way (the simplest) of doing it:
class Account < ActiveRecord::Base
self.abstract_class = true
has_many :corporate_customers
has_many :government_customers
end
class CorporateCustomer < ActiveRecord::Base
self.abstract_class = true
belongs_to :account
has_many :private_customers
has_many :public_customers
end
class PrivateCustomer < ActiveRecord::Base
belongs_to :corporate_customer
end
class PublicCustomer < ActiveRecord::Base
belongs_to :corporate_customer
end
class GovernmentCustomer < ActiveRecord::Base
belongs_to :account
end
NOTE: Abstract models are the models which cannot have objects ( cannot be instantiated ) and hence they don’t have associated table as well. If you want to have tables, then I fail to understand why it needs to an abstract class.
Assuming most of the data is shared, you only need one table: accounts. This will just work, assuming accounts has a string type column.
class Account < ActiveRecord::Base
self.abstract_class = true
end
class CorporateCustomer < Account
self.abstract_class = true
has_many financial_statements
end
class PrivateCustomer < CorporateCustomer
end
class PublicCustomer < CorporateCustomer
end
class GovernmentCustomer < Account
end
Google for Rails STI, and in particular Rails STI abstract, to get some more useful info.

Resources