Skip to content

Instantly share code, notes, and snippets.

@localshred
Created November 22, 2011 20:41
Show Gist options
  • Save localshred/1386875 to your computer and use it in GitHub Desktop.
Save localshred/1386875 to your computer and use it in GitHub Desktop.
Container objects that act like hashes with pre-definable keys.
# 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
# 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
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