SlideShare a Scribd company logo
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

PDF
Microservices avec Spring Cloud
PDF
Towards Functional Programming through Hexagonal Architecture
PDF
인프런 - 스타트업 인프랩 시작 사례
PDF
[MOPCON 2022] 以 Kotlin Multiplatform 制霸全平台
PPTX
Best Practices in Handling Performance Issues
PDF
Microservices with Apache Camel, DDD, and Kubernetes
ODP
Monitoramento de Filas com Laravel Horizon
PDF
임태현, 게임 서버 디자인 가이드, NDC2013
Microservices avec Spring Cloud
Towards Functional Programming through Hexagonal Architecture
인프런 - 스타트업 인프랩 시작 사례
[MOPCON 2022] 以 Kotlin Multiplatform 制霸全平台
Best Practices in Handling Performance Issues
Microservices with Apache Camel, DDD, and Kubernetes
Monitoramento de Filas com Laravel Horizon
임태현, 게임 서버 디자인 가이드, NDC2013

What's hot (20)

PPT
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
PDF
Odoo Performance Limits
PDF
객체지향 개념 (쫌 아는체 하기)
PPT
Entity Attribute Value (Eav)
PPTX
Why TypeScript?
PPTX
Hyperledger Besu 빨리 따라하기 (Private Networks)
PPTX
Building event-driven Serverless Apps with Azure Functions and Azure Cosmos DB
PDF
Windows Registered I/O (RIO) vs IOCP
PDF
CQRS and Event Sourcing in a Symfony application
PPTX
GPGPU(CUDA)를 이용한 MMOG 캐릭터 충돌처리
PDF
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
PDF
Oracle APEX Cheat Sheet
PDF
MMOG Server-Side 충돌 및 이동처리 설계와 구현
PDF
Clean architecture
PDF
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
PPTX
[0410 박민근] 기술 면접시 자주 나오는 문제들
PDF
Tdc2013 선배들에게 배우는 server scalability
PPTX
영화 예매 프로그램 (DB 설계, 프로그램 연동)
 
PDF
#살아있다 #자프링외길12년차 #코프링2개월생존기
PPTX
게임 분산 서버 구조
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
Odoo Performance Limits
객체지향 개념 (쫌 아는체 하기)
Entity Attribute Value (Eav)
Why TypeScript?
Hyperledger Besu 빨리 따라하기 (Private Networks)
Building event-driven Serverless Apps with Azure Functions and Azure Cosmos DB
Windows Registered I/O (RIO) vs IOCP
CQRS and Event Sourcing in a Symfony application
GPGPU(CUDA)를 이용한 MMOG 캐릭터 충돌처리
[2B7]시즌2 멀티쓰레드프로그래밍이 왜 이리 힘드나요
Oracle APEX Cheat Sheet
MMOG Server-Side 충돌 및 이동처리 설계와 구현
Clean architecture
NDC 2017 하재승 NEXON ZERO (넥슨 제로) 점검없이 실시간으로 코드 수정 및 게임 정보 수집하기
[0410 박민근] 기술 면접시 자주 나오는 문제들
Tdc2013 선배들에게 배우는 server scalability
영화 예매 프로그램 (DB 설계, 프로그램 연동)
 
#살아있다 #자프링외길12년차 #코프링2개월생존기
게임 분산 서버 구조
Ad

Viewers also liked (6)

PDF
Eav Data Model Concepts
PDF
Extensible Data Modeling
PDF
Sql Antipatterns Strike Back
PDF
Was ist eigentlich EAV?
PDF
SQL Outer Joins for Fun and Profit
PDF
Practical Object Oriented Models In Sql
Eav Data Model Concepts
Extensible Data Modeling
Sql Antipatterns Strike Back
Was ist eigentlich EAV?
SQL Outer Joins for Fun and Profit
Practical Object Oriented Models In Sql
Ad

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

PDF
Active Record Inheritance in Rails
KEY
ActiveRecord Association (1), Season 2
PDF
Contagion的Ruby/Rails投影片
 
ODP
Ruby on rails
PPTX
Active record in rails 5
PDF
Deprecating ActiveRecord Attributes without making Zombies
DOCX
Database design guide
PPT
Intro to Rails ActiveRecord
KEY
Learn Ruby 2011 - Session 4 - Objects, Oh My!
PDF
Design Patterns the Ruby way - ConFoo 2015
PDF
model.search: customize your own search logic
PPTX
Active record(1)
PDF
Refactoring @ Mindvalley: Smells, Techniques and Patterns
PDF
Presentation technico-commercial-ruby-on-rails
PDF
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
PDF
Domain Driven Design and Hexagonal Architecture with Rails
KEY
Rails Model Basics
PDF
Rails Models
KEY
Desarrollando aplicaciones web en minutos
PDF
Introduction to Domain-Driven Design in Ruby on Rails
Active Record Inheritance in Rails
ActiveRecord Association (1), Season 2
Contagion的Ruby/Rails投影片
 
Ruby on rails
Active record in rails 5
Deprecating ActiveRecord Attributes without making Zombies
Database design guide
Intro to Rails ActiveRecord
Learn Ruby 2011 - Session 4 - Objects, Oh My!
Design Patterns the Ruby way - ConFoo 2015
model.search: customize your own search logic
Active record(1)
Refactoring @ Mindvalley: Smells, Techniques and Patterns
Presentation technico-commercial-ruby-on-rails
Staying railsy - while scaling complexity or Ruby on Rails in Enterprise Soft...
Domain Driven Design and Hexagonal Architecture with Rails
Rails Model Basics
Rails Models
Desarrollando aplicaciones web en minutos
Introduction to Domain-Driven Design in Ruby on Rails

Recently uploaded (20)

PDF
MIND Revenue Release Quarter 2 2025 Press Release
PPTX
Cloud computing and distributed systems.
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Empathic Computing: Creating Shared Understanding
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
Review of recent advances in non-invasive hemoglobin estimation
PDF
Encapsulation_ Review paper, used for researhc scholars
PDF
KodekX | Application Modernization Development
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Approach and Philosophy of On baking technology
PPTX
Big Data Technologies - Introduction.pptx
PPTX
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
MIND Revenue Release Quarter 2 2025 Press Release
Cloud computing and distributed systems.
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Dropbox Q2 2025 Financial Results & Investor Presentation
20250228 LYD VKU AI Blended-Learning.pptx
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Empathic Computing: Creating Shared Understanding
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Unlocking AI with Model Context Protocol (MCP)
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Per capita expenditure prediction using model stacking based on satellite ima...
Network Security Unit 5.pdf for BCA BBA.
Review of recent advances in non-invasive hemoglobin estimation
Encapsulation_ Review paper, used for researhc scholars
KodekX | Application Modernization Development
Advanced methodologies resolving dimensionality complications for autism neur...
Approach and Philosophy of On baking technology
Big Data Technologies - Introduction.pptx
ACSFv1EN-58255 AWS Academy Cloud Security Foundations.pptx
Reach Out and Touch Someone: Haptics and Empathic Computing

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