SlideShare a Scribd company logo
1 of 21
Download to read offline
Implementation of EAV
        pattern for ActiveRecord
                 models


Kostyantyn Stepanyuk   kostya.stepanyuk@gmail.com   https://github.com/kostyantyn
Entity - Attribute - Value
Schema
Entity Type
Attribute Set
ActiveRecord and EAV
https://github.com/kostyantyn/example_active_record_as_eav
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
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
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
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
Simple Product

class SimpleProduct < Product
  attr_accessible :name

  eav   :code,       :string
  eav   :price,      :float
  eav   :quantity,   :integer
  eav   :active,     :boolean
end
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
Usage

SimpleProduct.create(code: '#1', price: 2.75, quantity: 5, active: true).id
# 1

product = SimpleProduct.find(1)
product.code     # "#1"
product.price    # 2.75
product.quantity # 5
product.active? # true

product.code_changed? # false
product.code = 3.50
product.code_changed? # true
product.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]
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
hydra_attribute
https://github.com/kostyantyn/hydra_attribute
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
Helper Methods

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}]
Where Condition

Product.create(price: 2.50) # id: 1
Product.create(price: nil) # id: 2
Product.create              # id: 3

Product.where(price: 2.50).map(&:id) # [1]
Product.where(price: nil).map(&:id) # [2, 3]
Select Attributes

Product.create(price: 2.50) # id: 1
Product.create(price: nil) # id: 2
Product.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
Order and Reverse Order

Product.create(title: 'a') # id: 1
Product.create(title: 'b') # id: 2
Product.create(title: 'c') # id: 3

Product.order(:title).first.id                 # 1
Product.order(:title).reverse_order.first.id   # 3
Questions?

More Related Content

What's hot

Content extraction with apache tika
Content extraction with apache tikaContent extraction with apache tika
Content extraction with apache tika
Jukka Zitting
 

What's hot (20)

Practical Object Oriented Models In Sql
Practical Object Oriented Models In SqlPractical Object Oriented Models In Sql
Practical Object Oriented Models In Sql
 
Introduction to SQL Antipatterns
Introduction to SQL AntipatternsIntroduction to SQL Antipatterns
Introduction to SQL Antipatterns
 
Python set
Python setPython set
Python set
 
What is Dictionary In Python? Python Dictionary Tutorial | Edureka
What is Dictionary In Python? Python Dictionary Tutorial | EdurekaWhat is Dictionary In Python? Python Dictionary Tutorial | Edureka
What is Dictionary In Python? Python Dictionary Tutorial | Edureka
 
HTML Tables
HTML TablesHTML Tables
HTML Tables
 
Object Oriented Javascript
Object Oriented JavascriptObject Oriented Javascript
Object Oriented Javascript
 
Top java script frameworks ppt
Top java script frameworks pptTop java script frameworks ppt
Top java script frameworks ppt
 
Writer Monad for logging execution of functions
Writer Monad for logging execution of functionsWriter Monad for logging execution of functions
Writer Monad for logging execution of functions
 
Monoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and CatsMonoids - Part 1 - with examples using Scalaz and Cats
Monoids - Part 1 - with examples using Scalaz and Cats
 
Introduction to Qt programming
Introduction to Qt programmingIntroduction to Qt programming
Introduction to Qt programming
 
PHP Workshop Notes
PHP Workshop NotesPHP Workshop Notes
PHP Workshop Notes
 
Python data type
Python data typePython data type
Python data type
 
Content extraction with apache tika
Content extraction with apache tikaContent extraction with apache tika
Content extraction with apache tika
 
Tutorial: Building a GraphQL API in PHP
Tutorial: Building a GraphQL API in PHPTutorial: Building a GraphQL API in PHP
Tutorial: Building a GraphQL API in PHP
 
JavaScript Control Statements II
JavaScript Control Statements IIJavaScript Control Statements II
JavaScript Control Statements II
 
React Development with the MERN Stack
React Development with the MERN StackReact Development with the MERN Stack
React Development with the MERN Stack
 
Python Dictionaries and Sets
Python Dictionaries and SetsPython Dictionaries and Sets
Python Dictionaries and Sets
 
Jetpack Compose a new way to implement UI on Android
Jetpack Compose a new way to implement UI on AndroidJetpack Compose a new way to implement UI on Android
Jetpack Compose a new way to implement UI on Android
 
Extensible Data Modeling
Extensible Data ModelingExtensible Data Modeling
Extensible Data Modeling
 
REST APIs with Spring
REST APIs with SpringREST APIs with Spring
REST APIs with Spring
 

Similar to Implementation of EAV pattern for ActiveRecord models

Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
pauldix
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
pauldix
 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1
Jano Suchal
 
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2KZepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Thomas Fuchs
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutos
Edgar Suarez
 

Similar to Implementation of EAV pattern for ActiveRecord models (20)

Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModel
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Rails
 
Why ruby
Why rubyWhy ruby
Why ruby
 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1
 
Say Goodbye to Procedural Programming - Nick Sutterer
Say Goodbye to Procedural Programming - Nick SuttererSay Goodbye to Procedural Programming - Nick Sutterer
Say Goodbye to Procedural Programming - Nick Sutterer
 
Prototype Framework
Prototype FrameworkPrototype Framework
Prototype Framework
 
Ajax nested form and ajax upload in rails
Ajax nested form and ajax upload in railsAjax nested form and ajax upload in rails
Ajax nested form and ajax upload in rails
 
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2KZepto.js, a jQuery-compatible mobile JavaScript framework in 2K
Zepto.js, a jQuery-compatible mobile JavaScript framework in 2K
 
Rails is not just Ruby
Rails is not just RubyRails is not just Ruby
Rails is not just Ruby
 
Rails for Beginners - Le Wagon
Rails for Beginners - Le WagonRails for Beginners - Le Wagon
Rails for Beginners - Le Wagon
 
WTF Oriented Programming, com Fabio Akita
WTF Oriented Programming, com Fabio AkitaWTF Oriented Programming, com Fabio Akita
WTF Oriented Programming, com Fabio Akita
 
jQuery
jQueryjQuery
jQuery
 
Rails vs Web2py
Rails vs Web2pyRails vs Web2py
Rails vs Web2py
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutos
 
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares DornellesA linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
A linguagem de programação Ruby - Robson "Duda" Sejan Soares Dornelles
 
Ruby Programming Language
Ruby Programming LanguageRuby Programming Language
Ruby Programming Language
 
Type safe embedded domain-specific languages
Type safe embedded domain-specific languagesType safe embedded domain-specific languages
Type safe embedded domain-specific languages
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weibo
 

Recently uploaded

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 

Recently uploaded (20)

ICT role in 21st century education and its challenges
ICT role in 21st century education and its challengesICT role in 21st century education and its challenges
ICT role in 21st century education and its challenges
 
FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024FWD Group - Insurer Innovation Award 2024
FWD Group - Insurer Innovation Award 2024
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
CNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In PakistanCNIC Information System with Pakdata Cf In Pakistan
CNIC Information System with Pakdata Cf In Pakistan
 
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
Biography Of Angeliki Cooney | Senior Vice President Life Sciences | Albany, ...
 
Artificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : UncertaintyArtificial Intelligence Chap.5 : Uncertainty
Artificial Intelligence Chap.5 : Uncertainty
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data DiscoveryTrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
TrustArc Webinar - Unlock the Power of AI-Driven Data Discovery
 
MS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectorsMS Copilot expands with MS Graph connectors
MS Copilot expands with MS Graph connectors
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..Understanding the FAA Part 107 License ..
Understanding the FAA Part 107 License ..
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
Web Form Automation for Bonterra Impact Management (fka Social Solutions Apri...
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWEREMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
EMPOWERMENT TECHNOLOGY GRADE 11 QUARTER 2 REVIEWER
 

Implementation of EAV pattern for ActiveRecord models

  • 1. Implementation of EAV pattern for ActiveRecord models Kostyantyn Stepanyuk kostya.stepanyuk@gmail.com https://github.com/kostyantyn
  • 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
  • 11. Simple Product class SimpleProduct < Product attr_accessible :name eav :code, :string eav :price, :float eav :quantity, :integer eav :active, :boolean 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
  • 13. Usage SimpleProduct.create(code: '#1', price: 2.75, quantity: 5, active: true).id # 1 product = SimpleProduct.find(1) product.code # "#1" product.price # 2.75 product.quantity # 5 product.active? # true product.code_changed? # false product.code = 3.50 product.code_changed? # true product.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]
  • 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
  • 17. Helper Methods 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}]
  • 18. Where Condition Product.create(price: 2.50) # id: 1 Product.create(price: nil) # id: 2 Product.create # id: 3 Product.where(price: 2.50).map(&:id) # [1] Product.where(price: nil).map(&:id) # [2, 3]
  • 19. Select Attributes Product.create(price: 2.50) # id: 1 Product.create(price: nil) # id: 2 Product.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
  • 20. Order and Reverse Order Product.create(title: 'a') # id: 1 Product.create(title: 'b') # id: 2 Product.create(title: 'c') # id: 3 Product.order(:title).first.id # 1 Product.order(:title).reverse_order.first.id # 3