I am trying to use Single Table Inheritance to query for all records within a class hierarchy from a class that is not the base class for the single table inheritance. For example, given the following class heirarchy.
class Animal < ActiveRecord::Base; end
class Dog < Animal; end
class Mutt < Dog; end
class PureBred < Dog; end
I want to be able to query for all of the dogs
dogs = Dog.all
and dogs be a list of instances of Mutt and PureBred.
Can this be done? I tried a proof of concept by setting the default_scope in the Dog class to
default_scope { where(:type => ['Mutt', 'PureBred']) }
but ActiveRecord is still appending the more restrictive condition of WHERE type IN ('Dog')
No; you're running up against a technical limitation of Rails' implementation of STI.
You'll have to query the base class:
Animal.where(type: %w(Dog Mutt PureBred))
Related
Say I have models using STI like so:
class MyBase < ApplicationRecord ; end
class MyBase::MySubclass1 < MyBase ; end
class MyBase::MySubclass2 < MyBase ; end
When I lookup records using the base class, the records all load with the class in the type column.
MyBase.all.to_a.map { |record| record.class.name }
# => [MyBase::MySubclass1, MyBase::MySubclass2]
99 times out of 100 this is a good thing, but is it possible to have these records load into their base class instead of the class in the type column? eg
MyBase.first.class
# => MyBase
I'm hoping there's a way to turn it off in the AR query, something like MyBase.where(condition: :something, use_base: true)...
My use case is that I'm using a gem which expects me to pass it an AR relation, looks at class.name, and breaks when it gets an STI subclass. To avoid patching the gem, I'd like to abide by its limitations and pass it a relation whose records' classes will automatically be coerced into the STI base class when loaded.
Myclass.all.map{|e| e.becomes(Myclass)}
will give objects of class Myclass regardless the type attribute.
I have rails 4 application and this models
class Product < AR::Base
default_scope -> { where(product_type: self.to_s) }
after_initialize { self.product_type = self.class.to_s }
end
and many others like this
class Orange < Product #Apple, Carrot, ...
end
if i call in console >> Orange.new, the callback after_initialize works like expected: it sets instance attribute product_type to Orange, so self.class is determined as Orange class like i need
but
if i call >> Orange.all, the method self.to_s inside default_scope is applied to Product class =(
So, here is the question: how can i use Orange class name inside default_scope in parent class (Product) in case i do not want to write any methods inside Orange class (because there are many subclasses like Orange and i want to leave everything DRY).
And so on, if i have Apple class, it name has to be used for filtering all Product with default_scope if i call >> Apple.all (some kind of polymorphic association)
thank you.
Just use STI here:
class Product < ActiveRecord::Base
self.inheritance_column = :product_type
end
class Orange < Product
end
# then...
> Orange.all
Orange Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."product_type" IN ('Orange')
Ie. don't do the default_scope or the after_initialize, because Rails already has you covered as soon as you inherit from another model Rails will assume you're using STI and it will add the right thing in the query.
I want to have two layers of STI. The first layer is working but am unsure how to have the first model lookup to another table also using STI. Example:
class Instrument < ActiveRecord::Base
end
class Guitar < Instrument
end
class Piano < Instrument
# and so on...
Ok. But I want to track categories of instruments so I can tell their type:
class InstrumentType < ActiveRecord::Base
end
class StringInstrumentType < InstrumentType
end
# etc.
So... our Guitar model becomes:
class Guitar < Instrument
belongs_to :string_intrument_type
end
class StringInstrumentType < InstrumentType
has_many :guitars
end
How do I implement this? What do I name the foreign-key column and how do I tell Rails/AR what to do?
Am I way off base here?
This scenario is invented but we are finding some real-world refactoring wants us to move in this direction.
Your first STI makes sense, the other one does not. On the abstract level, strings are rather an instance of an InstrumentType, not its subclass. It seems to me that you might rather use sth like:
class Instrument < ActiveRecord::Base
# abstract
end
class StringInstrument < Instrument
# abstract
end
module Instrument::Keyboard
end
class Guitar < StringInstrument
end
class Piano < StringInstrument
include Instrument::Keyboard
end
or similar. Obviously it depends A LOT on your actual code. In example you gave instrument types are rather constant in time, hence it is possible to use inheritance.
My guess is however, that your types are dynamic and can be added by clients. What you are looking for then is probably a tree structure. So every type has its parent and each parent type might have couple of children. You can then simply do Instrument belongs_to instrument_type and point to some InstrumentType tree leaf.
So here we go. I've got an Activerecord::Base model, let it be called a human.
class human < ActiveRecord::Base
has_one :Animal
end
Animal is an abstract class -
class animal < ActiveRecord::Base
self.abstract_class = true;
end
And I have a subclass of animal, let it be dog
class dog < Animal
in case I don't use abstract class, I can't add instance variables to 'Dog' (because it stores in 'Animal' table). In case I use abstract class, I can't add an 'Animal' to 'Human' - because rails doesn't know, how to store, for example, 'Dog'(ActiveRecord error: couldn't find table ''). This situation drives me crazy, and I just can't get over it.
Am I missing something or just doin' it completely wrong?
By convention in Ruby, Animal would refer to a class (actually, it's a bit more involved - this link has some more detail). In your original post, "class dog" should be "class Dog" b/c the class name is a constant, and if you had a has_one association between human and animal, you could say human.animal = (some instance of animal), but human.Animal is likely to have strange effects if it doesn't just immediately crash. The STI approach that others are recommending will do exactly what you want, though you would set the 'type' value, not 'Animal' (please don't actually do this directly).
You should read up on the meaning of capitalization in Ruby and RoR, STI, active record associations, and polymorphic associations. Something like this should work (not tested, and it's bad normalization - you can use has_one associations and a pattern called delegation to set up a situation where generic animal traits are in one table, and 'human specific' traits are in another to avoid a bunch of NULL columns in your database):
# remember to set up your migrations to add a 'type' column to your Animal table
# if animals can own other animals who own other animals, you may want to look at
# acts_as_tree, which does trees in relational databases efficiently
class Animal < ActiveRecord::Base
self.abstract_class = true
end
class Dog < Animal
# this is bad normalization - but you can keep this simple by adding
# a human_id field in your animal table (don't forget to index)
# look into the 'belongs_to' / 'references' type available for DB migrations
belongs_to :human
end
class Human < Animal
has_one :dog, :autosave => true # or you could use 'has_many :dogs'
end
human = Human.new # => adds record to Animal table, with type = 'human'
dog = Dog.new
human.dog = dog
human.save
ActiveRecord has built-in support for polymorphic associations, so you could do that:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
ActiveRecord by default gets the table name from the name of a model. You can override that, however. If you want Dogs in one table, Cats in other, etc. then you can do (in Rails 3.2):
class Dog < Animal
self.table_name = 'dogs'
end
class Cat < Animal
self.table_name = 'cats'
end
(You'll have to add migrations to create those tables.)
However, if you want all animals to exist in one table, you should look at Single-Table-Inheritance. See the ActiveRecord docs for more on that.
class CashOrderStatus < ActiveRecord::Base
belongs_to:cash_order
end
usually the db need a table cash_order_statuses to mapping this model,but now i want to
mapping this model to a specific sql view like
select * from order_statues where cash_order_id is not null <=> CashOrderStatus
does rails provide some way to achieve this
There are multiple ways to fulfill your requirement:
In your CashOrderStatus model, you can set table name to override default ORM mapping:
class CashOrderStatus > ActiveRecord::Base
set_table_name "order_statuses"
belongs_to:cash_order
end
You can implement STI(Single Table Inheritance) functionality where in your database table "order_statuses" one more column will be there: type which will hold the derived model class name(In this case, CashOrderStatus).
So your model will look like this:
class CashOrderStatus > OrderStatus
set_table_name "order_statuses"
belongs_to:cash_order
end
And OrderStaus model will be derived from AR::Base class. Try it.
NOTE: Sorry for the class inheritance notation. It should be < instead of >. There is formatting issue in my stackoverflow account, so I put like this :-)