Empty Eye
ActiveRecord based MTI gem powered by database views
MySQL, Postgresql and SQLite are supported and tested
add to your Gemfile
gem 'empty_eye'
and bundle or
gem install empty_eye
when using rails run the optional migration generator and the migration
this migration tracks view versions and its usage is highly recommended
rails generate empty_eye
=> create db/migrate/20120313042059_create_empty_eye_views_table.rb
rake db:migrate
Issues
- No known issues major issues; has been successful within data structures of high complexity (MTI to MTI, MTI to STI to MTI relationships)
- Not sure why but new mti instances have a id of zero; this has caused no problems so far however.
- No mechanism to change mti class table name but that is minor
- More complex testing needed to ensure reliability
- Uses ARel so should be compatible with ARel supported database that support view; there is support for Oracle and Sql Server adapters but these are not tested
Create MTI classes by renaming your base table with the core suffix and wrapping your associations in a mti_class block
Test example from http://techspry.com/ruby_and_rails/multiple-table-inheritance-in-rails-3/ which uses mixins to accomplish MTI:
ActiveRecord::Migration.create_table :restaurants, :force => true do |t|
t.string :type
t.boolean :kids_area
t.boolean :wifi
t.integer :eating_venue_id
t.string :food_genre
t.datetime :created_at
t.datetime :updated_at
t.datetime :deleted_at
end
ActiveRecord::Migration.create_table :bars_core, :force => true do |t|
t.string :music_genre
t.string :best_nights
t.string :dress_code
t.datetime :created_at
t.datetime :updated_at
t.datetime :deleted_at
end
ActiveRecord::Migration.create_table :businesses, :force => true do |t|
t.integer :biz_id
t.string :biz_type
t.string :name
t.string :address
t.string :phone
end
class Business < ActiveRecord::Base
belongs_to :biz, :polymorphic => true
end
class Restaurant < ActiveRecord::Base
mti_class do
has_one :business, :as => :biz
end
end
class Bar < ActiveRecord::Base
mti_class(:bars_core) do
has_one :business, :as => :biz
end
end
For now the convention is to name the base tables with the suffix core as the view will use the rails table name
In the background the following association options are used :autosave => true, :validate => true, :dependent => :destroy
MTI associations take the :only and :except options to limit the inherited columns.
class SmallMechanic < ActiveRecord::Base
mti_class :mechanics_core do |t|
has_one :garage, :foreign_key => :mechanic_id, :except => 'specialty'
end
end
class TinyMechanic < ActiveRecord::Base
mti_class :mechanics_core do |t|
has_one :garage, :foreign_key => :mechanic_id, :only => 'specialty'
end
end
Views are automatically created during class definition.
They are managed through Empty Eye View Manager.
The View Manager only rebuilds views when necessary and prevents another app instance from mistakenly rebuilding views during runtime.
Validations are also inherited but only for validations for attributes/columns that are inherited.
Only has one associations are supported and always have :autosave => true, :validate => true, :dependent => :destroy options applied.
Changing or adding these options will have no effect but the MTI would be senseless without them.
If the class does not descend active record the correct table will be used.
If you dont want to use the core suffix convention a table can be specified (see Bar class mti implementation).
1.9.3p0 :005 > Bar
=> Bar(id: integer, music_genre: string, best_nights: string, dress_code: string, created_at: datetime, updated_at: datetime, deleted_at: datetime, name: string, address: string, phone: string)
1.9.3p0 :006 > bar = Bar.create(:music_genre => "Latin", :best_nights => "Tuesdays", :dress_code => "casual", :address => "1904 Easy Kaley Orlando, FL 32806", :name => 'Chicos', :phone => '123456789')
=> #<Bar id: 2, music_genre: "Latin", best_nights: "Tuesdays", dress_code: "casual", created_at: "2012-03-09 18:41:17", updated_at: "2012-03-09 18:41:17", deleted_at: nil, name: "Chicos", address: "1904 Easy Kaley Orlando, FL 32806", phone: "123456789">
1.9.3p0 :008 > bar.phone = '987654321'
=> "987654321"
1.9.3p0 :009 > bar.save
=> true
1.9.3p0 :010 > bar.reload
=> #<Bar id: 2, music_genre: "Latin", best_nights: "Tuesdays", dress_code: "casual", created_at: "2012-03-09 18:41:17", updated_at: "2012-03-09 18:41:17", deleted_at: nil, name: "Chicos", address: "1904 Easy Kaley Orlando, FL 32806", phone: "987654321">
1.9.3p0 :011 > bar.destroy
=> #<Bar id: 2, music_genre: "Latin", best_nights: "Tuesdays", dress_code: "casual", created_at: "2012-03-09 18:41:17", updated_at: "2012-03-09 18:41:17", deleted_at: nil, name: "Chicos", address: "1904 Easy Kaley Orlando, FL 32806", phone: "987654321">
1.9.3p0 :013 > Bar.find_by_id(2)
=> nil
Other more complex structures examples:
ActiveRecord::Migration.create_table :restaurants, :force => true do |t|
t.string :type
t.boolean :kids_area
t.boolean :wifi
t.integer :eating_venue_id
t.string :food_genre
t.datetime :created_at
t.datetime :updated_at
t.datetime :deleted_at
end
ActiveRecord::Migration.create_table :eating_venues_core, :force => true do |t|
t.string :api_venue_id
t.string :latitude
t.string :longitude
end
ActiveRecord::Migration.create_table :businesses, :force => true do |t|
t.integer :biz_id
t.string :biz_type
t.string :name
t.string :address
t.string :phone
end
class Business < ActiveRecord::Base
belongs_to :biz, :polymorphic => true
end
class Restaurant < ActiveRecord::Base
belongs_to :foursquare_venue
end
class MexicanRestaurant < Restaurant
mti_class do |t|
has_one :business, :as => :biz
end
end
class EatingVenue < ActiveRecord::Base
mti_class do |t|
has_one :mexican_restaurant
end
end
Here Restaurant is not an mti class but instead a sti base class. The table name here is just restaurants.
MexicanRestaurant inherits from Restaurant therefore no specified primary table is needed; the primary table for MexicanRestaurant is restaurants.
MexicanRestaurant inherits the attributes of the Business table.
EatingVenue has not specified primary table so Empty Eye uses 'eating_venues_core'.
Eating Venue is declared as an mti class which inherits the table structure of MexicanRestaurant, an mti class, which in turn is a sti class.
Empty Eye only handles multiple table inheritance and not multiple class inheritance.
Classes like MexicanRestaurant will inherit methods from Restaurant but not from Business.