implementation of eav pattern for activerecord models
DESCRIPTION
Describe what EAV is and how to use it with ActiveRecord.TRANSCRIPT
![Page 1: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/1.jpg)
Implementation of EAV pattern for ActiveRecord
models
Kostyantyn Stepanyuk [email protected] https://github.com/kostyantyn
![Page 2: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/2.jpg)
Entity - Attribute - Value
![Page 3: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/3.jpg)
Schema
![Page 4: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/4.jpg)
Entity Type
![Page 5: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/5.jpg)
Attribute Set
![Page 6: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/6.jpg)
ActiveRecord and EAVhttps://github.com/kostyantyn/example_active_record_as_eav
![Page 7: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/7.jpg)
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
Specification
![Page 8: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/8.jpg)
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 endend
Migration
![Page 9: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/9.jpg)
class Attribute < ActiveRecord::Base self.abstract_class = true attr_accessible :name, :value belongs_to :entity, polymorphic: true, touch: true, autosave: trueend
class BooleanAttribute < Attributeend
class FloatAttribute < Attributeend
class IntegerAttribute < Attributeend
class StringAttribute < Attributeend
Attribute Models
![Page 10: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/10.jpg)
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 endend
Product
![Page 11: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/11.jpg)
class SimpleProduct < Product attr_accessible :name
eav :code, :string eav :price, :float eav :quantity, :integer eav :active, :booleanend
Simple Product
![Page 12: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/12.jpg)
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 endend
Advanced Attribute Methods
![Page 13: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/13.jpg)
SimpleProduct.create(code: '#1', price: 2.75, quantity: 5, active: true).id # 1
product = SimpleProduct.find(1)product.code # "#1" product.price # 2.75product.quantity # 5product.active? # true
product.code_changed? # falseproduct.code = 3.50product.code_changed? # trueproduct.code_was # 2.75
SimpleProduct.instance_methods.first(10)# [:code, :code=, :code_before_type_cast, :code?, :code_changed?, :code_change, :code_will_change!, :code_was, :reset_code!, :_code]
Usage
![Page 14: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/14.jpg)
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 endend
What about query methods?
![Page 15: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/15.jpg)
hydra_attributehttps://github.com/kostyantyn/hydra_attribute
![Page 16: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/16.jpg)
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 endend
class GenerateAttributes < ActiveRecord::Migration def up HydraAttribute::Migration.new(self).migrate end
def down HydraAttribute::Migration.new(self).rollback endend
Installation
![Page 17: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/17.jpg)
Product.hydra_attributes# [{'code' => :string, 'price' => :float, 'quantity' => :integer, 'active' => :boolean}]
Product.hydra_attribute_names# ['code', 'price', 'quantity', 'active']
Product.hydra_attribute_types# [:string, :float, :integer, :boolean]
Product.new.attributes# [{'name' => nil, 'code' => nil, 'price' => nil, 'quantity' => nil, 'active' => nil}]
Product.new.hydra_attributes# [{'code' => nil, 'price' => nil, 'quantity' => nil, 'active' => nil}]
Helper Methods
![Page 18: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/18.jpg)
Product.create(price: 2.50) # id: 1Product.create(price: nil) # id: 2Product.create # id: 3
Product.where(price: 2.50).map(&:id) # [1]Product.where(price: nil).map(&:id) # [2, 3]
Where Condition
![Page 19: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/19.jpg)
Product.create(price: 2.50) # id: 1Product.create(price: nil) # id: 2Product.create # id: 3 Product.select(:price).map(&:attributes)# [{'price' => 2.50}, {'price => nil}, {'price' => nil}]
Product.select(:price).map(&:code)# ActiveModel::MissingAttributeError: missing attribute: code
Select Attributes
![Page 20: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/20.jpg)
Product.create(title: 'a') # id: 1Product.create(title: 'b') # id: 2Product.create(title: 'c') # id: 3
Product.order(:title).first.id # 1Product.order(:title).reverse_order.first.id # 3
Order and Reverse Order
![Page 21: Implementation of EAV pattern for ActiveRecord models](https://reader034.vdocument.in/reader034/viewer/2022042614/554f6814b4c905bb178b4c19/html5/thumbnails/21.jpg)
Questions?