Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Rails attribute serializer macro
module Macros
module Serializer
# usage
# include Macros::Serializer
# e.g. a field named :metadata must be a Hash
# serializer :metadata, Hash
def self.included(host_class)
# field must be a symbol
# klass must be a constant data structure initialized with new
# you can also add other extensions to the data by passing in a block
# such used in the serialized_hash macro
def host_class.serializer(field,klass)
serialize field, klass
# ensure we're dealing with a klass, e.g. Hash or Set
# by returning the klass (and writing the attribute) when the field is nil
# when asking for the value of this attribute
define_method(field) do
# get current field value
data = read_attribute(field)
# nil, i.e. not set, initialize it with the desired klass
if data.nil?
data = klass.new
write_attribute(field, data)
end
# in all cases, return the value of the attribute
data
end
end
# usage is special case for a serialized Hash,
# which allows us to (optionally) limit the keys to a defined list
# and fail validation if extra keys are added
# example:
# serialized_hash :metadata, [:key1, :key2]
def host_class.serialized_hash(field,valid_keys_array=[])
serializer(field, Hash)
# needs to be instance_eval to get access to e.g. metadata, errors
instance_eval do
define_method("valid_serialized_keys_for_#{field}") do
extra_keys = method(field).call.keys - valid_keys_array
if extra_keys.size != 0
errors.add(field, "contains invalid key(s): #{extra_keys.inspect}. Valid keys are #{valid_keys_array.inspect}")
end
end
end
validate "valid_serialized_keys_for_#{field}".to_sym
end
end
end
end
# not sure how to test this in general
# activesupport/lib/active_support/core_ext/hash/keys.rb
require 'rails'
require 'spec_helper'
# require 'macros/serializer'
class TestSerializerClass < SomeActiveRecordBaseObject
include Macros::Serializer
serializer :field1, Set
serialized_hash :field2, [:key1, :key2]
def self.columns_hash
{
:field1,
:field2
}
end
end
describe Macros::Serializer do
let(:record) { TestSerializerClass.new }
it "default a set to Set.new" do
record.field1.should eql(Set.new)
end
it "can set a list of serialized attributes for a Set" do
record.field1 << 'foo' << 'bar'
record.field1.map.should =~ ['foo','bar']
end
it "defaults a Hash to Hash.new" do
record.field2.should eql(Hash.new)
end
it "can set a list of serialized attributes for a Hash" do
record.field2[:key1] = 'v1'
record.field2[:key2] = 'v2'
record.field2.should eql({:key1 => 'v1', :key2 => 'v2'})
end
it "invalidates the record when the serialized Hash is set with an invalid key" do
record.field2[:invalid] = 'nope'
record.should be_invalid
record.errors.keys.should include(:field2)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.