7. Specification
1. Save Entity Type as string in Entity Table (STI pattern)
2. Keep attributes directly in the model
3. Use Polymorphic Association between Entity and Value
8. Migration
class CreateEntityAndValues < ActiveRecord::Migration
def change
create_table :products do |t|
t.string :type
t.string :name
t.timestamps
end
%w(string integer float boolean).each do |type|
create_table "#{type}_attributes" do |t|
t.references :entity, polymorphic: true
t.string :name
t.send type, :value
t.timestamps
end
end
end
end
9. Attribute Models
class Attribute < ActiveRecord::Base
self.abstract_class = true
attr_accessible :name, :value
belongs_to :entity, polymorphic: true, touch: true, autosave: true
end
class BooleanAttribute < Attribute
end
class FloatAttribute < Attribute
end
class IntegerAttribute < Attribute
end
class StringAttribute < Attribute
end
10. Product
class Product < ActiveRecord::Base
%w(string integer float boolean).each do |type|
has_many :"#{type}_attributes", as: :entity, autosave: true, dependent: :delete_all
end
def eav_attr_model(name, type)
attributes = send("#{type}_attributes")
attributes.detect { |attr| attr.name == name } || attributes.build(name: name)
end
class << self
def eav(name, type)
class_eval <<-EOS, __FILE__, __LINE__ + 1
attr_accessible :#{name}
def #{name}; eav_attr_model('#{name}', '#{type}').value end
def #{name}=(value) eav_attr_model('#{name}', '#{type}').value = value end
def #{name}?; eav_attr_model('#{name}', '#{type}').value? end
EOS
end
end
end
12. Advanced Attribute Methods
class Product < ActiveRecord::Base
def self.eav(name, type)
attr_accessor name
attribute_method_matchers.each do |matcher|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{matcher.method_name(name)}(*args)
eav_attr_model('#{name}', '#{type}').send :#{matcher.method_name('value')}, *args
end
EOS
end
end
end
14. What about query methods?
class Product < ActiveRecord::Base
def self.scoped(options = nil)
super(options).extend(QueryMethods)
end
module QueryMethods
def select(*args, &block)
super(*args, &block)
end
def order(*args)
super(*args)
end
def where(*args)
super(*args)
end
end
end
16. Installation
class Product < ActiveRecord::Base
attr_accessor :title, :code, :quantity, :price, :active, :description
define_hydra_attributes do
string :title, :code
integer :quantity
float :price
boolean :active
text :description
end
end
class GenerateAttributes < ActiveRecord::Migration
def up
HydraAttribute::Migration.new(self).migrate
end
def down
HydraAttribute::Migration.new(self).rollback
end
end