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

What's hot (20)

Angular Directives
Angular DirectivesAngular Directives
Angular Directives
 
Modern JS with ES6
Modern JS with ES6Modern JS with ES6
Modern JS with ES6
 
Advanced Javascript
Advanced JavascriptAdvanced Javascript
Advanced Javascript
 
An Introduction To REST API
An Introduction To REST APIAn Introduction To REST API
An Introduction To REST API
 
ES2015 / ES6: Basics of modern Javascript
ES2015 / ES6: Basics of modern JavascriptES2015 / ES6: Basics of modern Javascript
ES2015 / ES6: Basics of modern Javascript
 
php
phpphp
php
 
Css pseudo-classes
Css pseudo-classesCss pseudo-classes
Css pseudo-classes
 
Working with Databases and MySQL
Working with Databases and MySQLWorking with Databases and MySQL
Working with Databases and MySQL
 
Intro to html 5
Intro to html 5Intro to html 5
Intro to html 5
 
JavaScript - Chapter 7 - Advanced Functions
 JavaScript - Chapter 7 - Advanced Functions JavaScript - Chapter 7 - Advanced Functions
JavaScript - Chapter 7 - Advanced Functions
 
JavaScript - Chapter 8 - Objects
 JavaScript - Chapter 8 - Objects JavaScript - Chapter 8 - Objects
JavaScript - Chapter 8 - Objects
 
TypeScript Best Practices
TypeScript Best PracticesTypeScript Best Practices
TypeScript Best Practices
 
Arquitetura Node com NestJS
Arquitetura Node com NestJSArquitetura Node com NestJS
Arquitetura Node com NestJS
 
Javascript arrays
Javascript arraysJavascript arrays
Javascript arrays
 
jQuery
jQueryjQuery
jQuery
 
01 Php Introduction
01 Php Introduction01 Php Introduction
01 Php Introduction
 
JavaScript - Chapter 5 - Operators
 JavaScript - Chapter 5 - Operators JavaScript - Chapter 5 - Operators
JavaScript - Chapter 5 - Operators
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training
 
CSS
CSSCSS
CSS
 
Introduction to HTML5
Introduction to HTML5Introduction to HTML5
Introduction to HTML5
 

Viewers also liked (7)

Entity Attribute Value (Eav)
Entity   Attribute   Value (Eav)Entity   Attribute   Value (Eav)
Entity Attribute Value (Eav)
 
Eav Data Model Concepts
Eav Data Model ConceptsEav Data Model Concepts
Eav Data Model Concepts
 
Extensible Data Modeling
Extensible Data ModelingExtensible Data Modeling
Extensible Data Modeling
 
Sql Antipatterns Strike Back
Sql Antipatterns Strike BackSql Antipatterns Strike Back
Sql Antipatterns Strike Back
 
Was ist eigentlich EAV?
Was ist eigentlich EAV?Was ist eigentlich EAV?
Was ist eigentlich EAV?
 
SQL Outer Joins for Fun and Profit
SQL Outer Joins for Fun and ProfitSQL Outer Joins for Fun and Profit
SQL Outer Joins for Fun and Profit
 
Practical Object Oriented Models In Sql
Practical Object Oriented Models In SqlPractical Object Oriented Models In Sql
Practical Object Oriented Models In Sql
 

Similar to Implement EAV pattern for ActiveRecord models

Ruby on rails
Ruby on rails Ruby on rails
Ruby on rails Mohit Jain
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Building Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelBuilding Web Service Clients with ActiveModel
Building Web Service Clients with ActiveModelpauldix
 
Ruby/Rails
Ruby/RailsRuby/Rails
Ruby/Railsrstankov
 
Metaprogramovanie #1
Metaprogramovanie #1Metaprogramovanie #1
Metaprogramovanie #1Jano Suchal
 
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 SuttererRuby Meditation
 
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 railsTse-Ching Ho
 
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 2KThomas Fuchs
 
Rails for Beginners - Le Wagon
Rails for Beginners - Le WagonRails for Beginners - Le Wagon
Rails for Beginners - Le WagonAlex Benoit
 
WTF Oriented Programming, com Fabio Akita
WTF Oriented Programming, com Fabio AkitaWTF Oriented Programming, com Fabio Akita
WTF Oriented Programming, com Fabio AkitaiMasters
 
Rails vs Web2py
Rails vs Web2pyRails vs Web2py
Rails vs Web2pyjonromero
 
Desarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosDesarrollando aplicaciones web en minutos
Desarrollando aplicaciones web en minutosEdgar Suarez
 
Ruby Programming Language
Ruby Programming LanguageRuby Programming Language
Ruby Programming LanguageDuda Dornelles
 
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 DornellesTchelinux
 
Type safe embedded domain-specific languages
Type safe embedded domain-specific languagesType safe embedded domain-specific languages
Type safe embedded domain-specific languagesArthur Xavier
 
More to RoC weibo
More to RoC weiboMore to RoC weibo
More to RoC weiboshaokun
 

Similar to Implement 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
 
Ruby Programming Language
Ruby Programming LanguageRuby Programming Language
Ruby Programming Language
 
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
 
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

Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostZilliz
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticscarlostorres15106
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Manik S Magar
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupFlorian Wilhelm
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024BookNet Canada
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxhariprasad279825
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenHervé Boutemy
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationRidwan Fadjar
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLScyllaDB
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubKalema Edgar
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyAlfredo García Lavilla
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024Lorenzo Miniero
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek SchlawackFwdays
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxNavinnSomaal
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr BaganFwdays
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsMemoori
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clashcharlottematthew16
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsMiki Katsuragi
 

Recently uploaded (20)

Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage CostLeverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
Leverage Zilliz Serverless - Up to 50X Saving for Your Vector Storage Cost
 
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmaticsKotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
Kotlin Multiplatform & Compose Multiplatform - Starter kit for pragmatics
 
Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!Anypoint Exchange: It’s Not Just a Repo!
Anypoint Exchange: It’s Not Just a Repo!
 
Streamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project SetupStreamlining Python Development: A Guide to a Modern Project Setup
Streamlining Python Development: A Guide to a Modern Project Setup
 
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
Transcript: New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
New from BookNet Canada for 2024: BNC CataList - Tech Forum 2024
 
Artificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptxArtificial intelligence in cctv survelliance.pptx
Artificial intelligence in cctv survelliance.pptx
 
DevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache MavenDevoxxFR 2024 Reproducible Builds with Apache Maven
DevoxxFR 2024 Reproducible Builds with Apache Maven
 
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptxE-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
E-Vehicle_Hacking_by_Parul Sharma_null_owasp.pptx
 
My Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 PresentationMy Hashitalk Indonesia April 2024 Presentation
My Hashitalk Indonesia April 2024 Presentation
 
Developer Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQLDeveloper Data Modeling Mistakes: From Postgres to NoSQL
Developer Data Modeling Mistakes: From Postgres to NoSQL
 
Unleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding ClubUnleash Your Potential - Namagunga Girls Coding Club
Unleash Your Potential - Namagunga Girls Coding Club
 
Commit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easyCommit 2024 - Secret Management made easy
Commit 2024 - Secret Management made easy
 
SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024SIP trunking in Janus @ Kamailio World 2024
SIP trunking in Janus @ Kamailio World 2024
 
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
"Subclassing and Composition – A Pythonic Tour of Trade-Offs", Hynek Schlawack
 
SAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptxSAP Build Work Zone - Overview L2-L3.pptx
SAP Build Work Zone - Overview L2-L3.pptx
 
"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan"ML in Production",Oleksandr Bagan
"ML in Production",Oleksandr Bagan
 
AI as an Interface for Commercial Buildings
AI as an Interface for Commercial BuildingsAI as an Interface for Commercial Buildings
AI as an Interface for Commercial Buildings
 
Powerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time ClashPowerpoint exploring the locations used in television show Time Clash
Powerpoint exploring the locations used in television show Time Clash
 
Vertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering TipsVertex AI Gemini Prompt Engineering Tips
Vertex AI Gemini Prompt Engineering Tips
 

Implement 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