AKA "screw 1NF"
Since Rails 0.x
class Shop < ActiveRecord::Base
serialize :settings
end
>> shop.settings = {language: 'en'}
Serialize anything to YAML by default
class Shop < ActiveRecord::Base
serialize :settings, Settings
end
A class can be specified to ensure that only expected types are serialized
class Settings
def self.dump(settings)
MultiJson.dump(settings.to_hash)
end
def self.load(string)
new(MultiJson.load(string))
end
end
If both load and dump are implemented, the class is responsible of his own serialization
Since Rails 0.x (killed and then resurrected in 4.0)
class Shop < ActiveRecord::Base
composed_of :address, mapping: [%w(address_street street), %w(address_city city)]
end
class Address
def intialize(street, city)
@street, @city = street, city
end
end
>> shop.address_street
=> '126 York Street'
>> shop.address
=> #<Address:0x007fc584725c08 @street="126 York Street", @city="Ottawa">
Build a plain ruby object from a mapping of attributes
Since Rails 3.2
class Shop < ActiveRecord::Base
store :settings
end
>> shop.settings[:language] = 'en'
Always serialize a Hash (in YAML by default)
class Shop < ActiveRecord::Base
store :settings, accessors: [:language]
end
>> shop.language = 'en'
Allow to define accessors
Since Rails 4.0
class Shop < ActiveRecord::Base
store :settings, coder: MultiJson
end
Can change the serialization method
Compatible with Rails >= 3.2
class Shop < ActiveRecord::Base
typed_store :settings do |s|
s.string :language
end
end
>> shop.language = 'en'
Same behavior than store
class Shop < ActiveRecord::Base
typed_store :settings do |s|
s.integer :drinking_age
end
end
>> shop.drinking_age = '18'
>> shop.drinking_age
=> 18
But type can be specified
class Shop < ActiveRecord::Base
typed_store :settings do |s|
s.integer :drinking_age, default: 21
end
end
>> Shop.new.drinking_age
=> 21
Just like default value
class Shop < ActiveRecord::Base
typed_store :settings do |s|
s.integer :drinking_age, default: 21, null: false
end
end
>> shop.drinking_age = nil
>> shop.drinking_age
=> 21
And null can be prevented
class Shop < ActiveRecord::Base
typed_store :settings do |s|
s.string :language, default: 'en', blank: false
end
end
>> shop.language = ' '
>> shop.language
=> 'en'
And blank too
class Shop < ActiveRecord::Base
typed_store :settings do |s|
s.string :tags, array: true, default: [], null: false
end
end
>> shop.tags = ['ruby', 'web']
You can even store an array of values
>> shop.dinking_age?
=> true
>> shop.dinking_age_changed?
=> false
>> shop.dinking_age = 18
>> shop.dinking_age_changed?
=> true
>> shop.dinking_age_was
=> 21
Accessors are plain attributes
Jean Boussier
Shopify
https://github.com/byroot
https://github.com/byroot/activerecord-typedstore