Created
November 22, 2011 20:41
-
-
Save localshred/1386875 to your computer and use it in GitHub Desktop.
Container objects that act like hashes with pre-definable keys.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Just a sample, obviously very rudimentary but you get the idea | |
require 'sinatra' | |
require 'json' | |
require 'lib/container/base' | |
class User < Container::Base | |
attr_accessor :guid | |
attr_accessor :name | |
attr_accessor :email | |
attr_accessor :phone | |
attr_accessor :address | |
end | |
get '/user/Jakobim' do | |
user = Container::User.new(guid: 'user guid', name: 'Jakobim Mugatu') | |
[200, {Content-Type:'application/json'}, user.to_hash.to_json] | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Provides data container class which acts like a hash. | |
# `to_hash` and `initialize` are provided | |
# to subclasses which should only need to implement attr_accessor | |
# calls to set up the valid attributes. | |
# | |
# NOTE: FIELD VALIDATIONS ARE NOT SUPPORTED. | |
# | |
module Container | |
class Base | |
NON_ATTRIBUTE_METHODS = [:to_hash, :attributes] | |
# Accepts a possible hash argument to auto-assign | |
# values from valid keys to their corresponding attributes. | |
# Values with keys that do not have a paired attribute are silently ignored. | |
def initialize hash_args={} | |
if hash_args.respond_to? :each_pair | |
hash_args.each_pair do |k,v| | |
setter = :"#{k}=" | |
self.__send__(setter, v) if self.respond_to? setter | |
end | |
end | |
end | |
# Output a semi-recursive hash of the assigned attributes. | |
# Currently will not iterate array or hash objects, | |
# but will take child Container classes and call `to_hash` on them. | |
# Does not assign key/value if value is nil. | |
def to_hash | |
Hash.new.tap do |h| | |
attributes.each do |m| | |
unless m =~ /=$/ | |
v = self.__send__(m) | |
if v.kind_of?(Container::Base) | |
h[m] = v.to_hash | |
elsif !v.nil? | |
h[m] = v | |
end | |
end | |
end | |
end | |
end | |
# Returns a list of attributes that were set with attr_accessor or attr_reader | |
def attributes | |
(self.class.instance_methods - Object.instance_methods - NON_ATTRIBUTE_METHODS).select{|m| !(m =~ /=$/) } | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'spec_helper' | |
require 'container/base' | |
ATTRIBUTES = [ | |
:string_attr, | |
:int_attr, | |
:float_attr, | |
:bool_attr, | |
:array_attr, | |
:container_attr | |
] | |
describe Container::Base do | |
let(:attributes) { ATTRIBUTES } | |
before(:all) do | |
class TestContainer < Container::Base | |
ATTRIBUTES.each do |attribute| | |
attr_accessor attribute | |
end | |
end | |
end | |
subject { TestContainer.new } | |
describe '#initialize' do | |
it 'accepts a hash and auto-assigns values to valid attributes' do | |
tc = TestContainer.new({ | |
:string_attr => 'Hello, World!', | |
:int_attr => 42, | |
:float_attr => 42.1, | |
:bool_attr => true, | |
:array_attr => ['Hello', 'World'], | |
:container_attr => TestContainer.new({ | |
:string_attr => 'Inner Container' | |
}) | |
}) | |
tc.string_attr.should eq 'Hello, World!' | |
tc.int_attr.should eq 42 | |
tc.float_attr.should eq 42.1 | |
tc.bool_attr.should eq true | |
tc.array_attr.should eq ['Hello', 'World'] | |
tc.container_attr.string_attr.should eq 'Inner Container' | |
end | |
it 'ignores values with keys that are not valid attributes' do | |
tc = TestContainer.new({ | |
:bogus => 1, | |
:dumb => 2, | |
:string_attr => 'Should be there' | |
}) | |
expect { tc.bogus }.should raise_error(NoMethodError) | |
expect { tc.dumb }.should raise_error(NoMethodError) | |
expect { tc.bogus = 10 }.should raise_error(NoMethodError) | |
expect { tc.dumb = 20 }.should raise_error(NoMethodError) | |
tc.string_attr.should eq 'Should be there' | |
end | |
end | |
describe '#to_hash' do | |
it 'provides hashability to all subclasses' do | |
subject.string_attr = 'Hello, World!' | |
subject.int_attr = 42 | |
subject.float_attr = 42.1 | |
subject.bool_attr = true | |
subject.array_attr = ['Hello', 'World'] | |
subject.container_attr = TestContainer.new.tap{|tc| tc.string_attr = 'Inner Container' } | |
subject.to_hash.should eq({ | |
:string_attr => 'Hello, World!', | |
:int_attr => 42, | |
:float_attr => 42.1, | |
:bool_attr => true, | |
:array_attr => ['Hello', 'World'], | |
:container_attr => { | |
:string_attr => 'Inner Container' | |
} | |
}) | |
end | |
end | |
describe '#attributes' do | |
it 'returns an array of defined attributes' do | |
subject.attributes.should eq ATTRIBUTES | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment