diff options
author | Bram Swenson <bram.swenson@akqa.com> | 2011-07-23 11:25:38 -0700 |
---|---|---|
committer | Bram Swenson <bram.swenson@akqa.com> | 2011-07-23 11:25:38 -0700 |
commit | 901ca566cae9ddac09f14b6fa59cea7100c1003a (patch) | |
tree | d69b5280643d210a07ce19f81bd5101fcaae8719 | |
parent | e470eb50c50268689a88bd5383a189376d645a2a (diff) | |
download | hashie-901ca566cae9ddac09f14b6fa59cea7100c1003a.tar.gz |
[REQUIRED] properties added to Dash, removed Lash class
-rw-r--r-- | README.rdoc | 42 | ||||
-rw-r--r-- | lib/hashie.rb | 1 | ||||
-rw-r--r-- | lib/hashie/dash.rb | 37 | ||||
-rw-r--r-- | lib/hashie/lash.rb | 89 | ||||
-rw-r--r-- | spec/hashie/dash_spec.rb | 85 | ||||
-rw-r--r-- | spec/hashie/lash_spec.rb | 194 |
6 files changed, 103 insertions, 345 deletions
diff --git a/README.rdoc b/README.rdoc index 76e237d..6b0dd16 100644 --- a/README.rdoc +++ b/README.rdoc @@ -39,28 +39,28 @@ to false or nil. In order to check if a key has been set at all, use the Dash is an extended Hash that has a discrete set of defined properties and only those properties may be set on the hash. Additionally, you -can set defaults for each property. +can set defaults for each property. You can also flag a property as +required. Required properties will raise an execption if unset. === Example: class Person < Hashie::Dash - property :name + property :name, :required => true property :email property :occupation, :default => 'Rubyist' end - p = Person.new - p.name # => nil + p = Person.new # => ArgumentError: The property 'name' is required for this Dash. + + p = Person.new(:name => "Bob") + p.name # => 'Bob' + p.name = nil # => ArgumentError: The property 'name' is required for this Dash. p.email = 'abc@def.com' p.occupation # => 'Rubyist' p.email # => 'abc@def.com' p[:awesome] # => NoMethodError p[:occupation] # => 'Rubyist' - p = Person.new(:name => "Bob") - p.name # => 'Bob' - p.occupation # => 'Rubyist' - == Trash A Trash is a Dash that allows you to translate keys on initialization. @@ -102,32 +102,6 @@ provide. c.where(:abc => 'def').where(:hgi => 123) c # => {:where => {:abc => 'def', :hgi => 123}} -== Lash - -Lash is an extended Dash that allows properties to be set -as required. These required properties will cause new Lash -instances to "lash out" by raising an error when passed a -nil value. - -=== Example: - - class Person < Hashie::Dash - property :name, :required => true - property :email - property :occupation, :default => 'Rubyist' - end - - p = Person.new # => ArgumentError: The property 'name' is required for this Lash. - - p = Person.new(:name => 'Bob') - p.name # => 'Bob' - p.email = 'abc@def.com' - p.occupation # => 'Rubyist' - p.email # => 'abc@def.com' - p[:awesome] # => NoMethodError - p[:occupation] # => 'Rubyist' - - p.name = nil # => ArgumentError: The property 'name' is required for this Lash. == Note on Patches/Pull Requests diff --git a/lib/hashie.rb b/lib/hashie.rb index eed21fc..b5f6f0b 100644 --- a/lib/hashie.rb +++ b/lib/hashie.rb @@ -6,5 +6,4 @@ module Hashie autoload :Mash, 'hashie/mash' autoload :Dash, 'hashie/dash' autoload :Clash, 'hashie/clash' - autoload :Lash, 'hashie/lash' end diff --git a/lib/hashie/dash.rb b/lib/hashie/dash.rb index f734abc..cbfc1f5 100644 --- a/lib/hashie/dash.rb +++ b/lib/hashie/dash.rb @@ -23,13 +23,17 @@ module Hashie # to be returned before a value is set on the property in a new # Dash. # + # * <tt>:required</tt> - Specify the value as required for this + # property, to raise an error if a value is unset in a new or + # existing Dash. + # def self.property(property_name, options = {}) property_name = property_name.to_sym self.properties << property_name if options.has_key?(:default) - self.defaults[property_name] = options[:default] + self.defaults[property_name] = options[:default] elsif self.defaults.has_key?(property_name) self.defaults.delete property_name end @@ -49,19 +53,23 @@ module Hashie if defined? @subclasses @subclasses.each { |klass| klass.property(property_name, options) } end + required_properties << property_name if options.delete(:required) end class << self attr_reader :properties, :defaults + attr_reader :required_properties end instance_variable_set('@properties', Set.new) instance_variable_set('@defaults', {}) + instance_variable_set('@required_properties', Set.new) def self.inherited(klass) super (@subclasses ||= Set.new) << klass klass.instance_variable_set('@properties', self.properties.dup) klass.instance_variable_set('@defaults', self.defaults.dup) + klass.instance_variable_set('@required_properties', self.required_properties.dup) end # Check to see if the specified property has already been @@ -70,6 +78,12 @@ module Hashie properties.include? name.to_sym end + # Check to see if the specified property is + # required. + def self.required?(name) + required_properties.include? name.to_sym + end + # You may initialize a Dash with an attributes hash # just like you would many other kinds of data objects. def initialize(attributes = {}, &block) @@ -82,6 +96,7 @@ module Hashie attributes.each_pair do |att, value| self[att] = value end if attributes + assert_required_properties_set! end alias_method :_regular_reader, :[] @@ -100,6 +115,7 @@ module Hashie # Set a value on the Dash in a Hash-like way. Only works # on pre-existing properties. def []=(property, value) + assert_property_required! property, value assert_property_exists! property super(property.to_s, value) end @@ -111,5 +127,24 @@ module Hashie raise NoMethodError, "The property '#{property}' is not defined for this Dash." end end + + def assert_required_properties_set! + self.class.required_properties.each do |required_property| + assert_property_set!(required_property) + end + end + + def assert_property_set!(property) + if send(property).nil? + raise ArgumentError, "The property '#{property}' is required for this Dash." + end + end + + def assert_property_required!(property, value) + if self.class.required?(property) && value.nil? + raise ArgumentError, "The property '#{property}' is required for this Dash." + end + end + end end diff --git a/lib/hashie/lash.rb b/lib/hashie/lash.rb deleted file mode 100644 index 2bc47b9..0000000 --- a/lib/hashie/lash.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'hashie/dash' - -module Hashie - # A Lash is just like a Dash, except that a Lash allows - # that properties can set as required. This means that if - # an attempt is made to create a Lash with out it, an error - # will be raised. - # - # Lashes are useful when you need to create a very simple - # lightweight data object that needs even fewer options and - # resources than something like a DataMapper resource, and - # certain attributes are required. - # - # It is preferrable to a Struct because of the in-class - # API for defining properties as well as per-property defaults - # and requirements. - class Lash < Hashie::Dash - - # Defines a property on the Lash. Options are - # as follows: - # - # * <tt>:default</tt> - Specify a default value for this property, - # to be returned before a value is set on the property in a new - # Lash. - # - # * <tt>:required</tt> - Specify the value as required for this - # property, to raise an error if a value is unset in a new or - # existing Lash. - # - def self.property(property_name, options = {}) - super - required_properties << property_name if options.delete(:required) - end - - class << self - attr_reader :required_properties - end - instance_variable_set('@required_properties', Set.new) - - def self.inherited(klass) - super - klass.instance_variable_set('@required_properties', self.required_properties.dup) - end - - # Check to see if the specified property is - # required. - def self.required?(name) - required_properties.include? name.to_sym - end - - # You may initialize a Lash with an attributes hash - # just like you would many other kinds of data objects. - # Remember to pass in the values for the properties - # you set as required. - def initialize(attributes = {}, &block) - super(attributes, &block) - assert_required_properties_set! - end - - # Set a value on the Lash in a Hash-like way. Only works - # on pre-existing properties and errors if you try to - # set a required value to nil. - def []=(property, value) - assert_property_required! property, value - super(property.to_s, value) - end - - - private - - def assert_required_properties_set! - self.class.required_properties.each do |required_property| - assert_property_set!(required_property) - end - end - - def assert_property_set!(property) - if send(property).nil? - raise ArgumentError, "The property '#{property}' is required for this Lash." - end - end - - def assert_property_required!(property, value) - if self.class.required?(property) && value.nil? - raise ArgumentError, "The property '#{property}' is required for this Lash." - end - end - end -end diff --git a/spec/hashie/dash_spec.rb b/spec/hashie/dash_spec.rb index 2de5490..893f903 100644 --- a/spec/hashie/dash_spec.rb +++ b/spec/hashie/dash_spec.rb @@ -7,38 +7,59 @@ Hashie::Hash.class_eval do end class DashTest < Hashie::Dash + property :first_name, :required => true + property :email + property :count, :default => 0 +end + +class DashNoRequiredTest < Hashie::Dash property :first_name property :email property :count, :default => 0 end class Subclassed < DashTest - property :last_name + property :last_name, :required => true end describe DashTest do - + + subject { DashTest.new(:first_name => 'Bob', :email => 'bob@example.com') } + it('subclasses Hashie::Hash') { should respond_to(:to_mash) } - - its(:to_s) { should == '<#DashTest count=0>' } - + + its(:to_s) { should == '<#DashTest count=0 email="bob@example.com" first_name="Bob">' } + it 'lists all set properties in inspect' do subject.first_name = 'Bob' subject.email = 'bob@example.com' subject.inspect.should == '<#DashTest count=0 email="bob@example.com" first_name="Bob">' end - + its(:count) { should be_zero } it { should respond_to(:first_name) } it { should respond_to(:first_name=) } it { should_not respond_to(:nonexistent) } - + it 'errors out for a non-existent property' do lambda { subject['nonexistent'] }.should raise_error(NoMethodError) end + it 'errors out when attempting to set a required property to nil' do + lambda { subject.first_name = nil }.should raise_error(ArgumentError) + end + context 'writing to properties' do + + it 'fails writing a required property to nil' do + lambda { subject.first_name = nil }.should raise_error(ArgumentError) + end + + it 'fails writing a required property to nil using []=' do + lambda { subject['first_name'] = nil }.should raise_error(ArgumentError) + end + it 'fails writing to a non-existent property using []=' do lambda { subject['nonexistent'] = 123 }.should raise_error(NoMethodError) end @@ -54,19 +75,19 @@ describe DashTest do subject.first_name.should == 'Franklin' end end - + context 'reading from properties' do it 'fails reading from a non-existent property using []' do lambda { subject['nonexistent'] }.should raise_error(NoMethodError) end - + it "should be able to retrieve properties through blocks" do subject["first_name"] = "Aiden" value = nil subject.[]("first_name") { |v| value = v } value.should == "Aiden" end - + it "should be able to retrieve properties through blocks with method calls" do subject["first_name"] = "Frodo" value = nil @@ -84,32 +105,42 @@ describe DashTest do obj = described_class.new :first_name => 'Michael' obj.first_name.should == 'Michael' end - + it 'accepts nil' do - lambda { described_class.new(nil) }.should_not raise_error + lambda { DashNoRequiredTest.new(nil) }.should_not raise_error end - + it 'accepts block to define a global default' do obj = described_class.new { |hash, key| key.to_s.upcase } obj.first_name.should == 'FIRST_NAME' obj.count.should be_zero end + + it "fails when required values are missing" do + expect { DashTest.new }.to raise_error(ArgumentError) + end + end - + describe 'properties' do it 'lists defined properties' do described_class.properties.should == Set.new([:first_name, :email, :count]) end - + it 'checks if a property exists' do described_class.property?('first_name').should be_true described_class.property?(:first_name).should be_true end - + + it 'checks if a property is required' do + described_class.required?('first_name').should be_true + described_class.required?(:first_name).should be_true + end + it 'doesnt include property from subclass' do described_class.property?(:last_name).should be_false end - + it 'lists declared defaults' do described_class.defaults.should == { :count => 0 } end @@ -122,31 +153,31 @@ describe Hashie::Dash, 'inheritance' do @middle = Class.new(@top) @bottom = Class.new(@middle) end - + it 'reports empty properties when nothing defined' do @top.properties.should be_empty @top.defaults.should be_empty end - + it 'inherits properties downwards' do @top.property :echo @middle.properties.should include(:echo) @bottom.properties.should include(:echo) end - + it 'doesnt inherit properties upwards' do @middle.property :echo @top.properties.should_not include(:echo) @bottom.properties.should include(:echo) end - + it 'allows overriding a default on an existing property' do @top.property :echo @middle.property :echo, :default => 123 @bottom.properties.to_a.should == [:echo] @bottom.new.echo.should == 123 end - + it 'allows clearing an existing default' do @top.property :echo @middle.property :echo, :default => 123 @@ -163,20 +194,22 @@ describe Hashie::Dash, 'inheritance' do end describe Subclassed do - + + subject { Subclassed.new(:first_name => 'Bob', :last_name => 'McNob', :email => 'bob@example.com') } + its(:count) { should be_zero } it { should respond_to(:first_name) } it { should respond_to(:first_name=) } it { should respond_to(:last_name) } it { should respond_to(:last_name=) } - + it 'has one additional property' do described_class.property?(:last_name).should be_true end - + it "didn't override superclass inheritance logic" do described_class.instance_variable_get('@inheritance_test').should be_true end - + end diff --git a/spec/hashie/lash_spec.rb b/spec/hashie/lash_spec.rb deleted file mode 100644 index 708766f..0000000 --- a/spec/hashie/lash_spec.rb +++ /dev/null @@ -1,194 +0,0 @@ -require 'spec_helper' - -Hashie::Hash.class_eval do - def self.inherited(klass) - klass.instance_variable_set('@inheritance_test', true) - end -end - -class LashTest < Hashie::Lash - property :first_name, :required => true - property :email, :required => true - property :count, :default => 0, :required => true -end - -class SubclassedLash < LashTest - property :last_name, :required => true -end - -describe LashTest, :wip => true do - - context "without a required value" do - it "errors out when values are missing" do - expect { LashTest.new }.to raise_error(ArgumentError) - end - end - - subject { LashTest.new(:first_name => 'Bob', :email => 'bob@example.com') } - - it('subclasses Hashie::Hash') { should respond_to(:to_mash) } - - it 'lists all set properties in inspect' do - subject.first_name = 'Bob' - subject.email = 'bob@example.com' - subject.inspect.should == '<#LashTest count=0 email="bob@example.com" first_name="Bob">' - end - - its(:count) { should be_zero } - - it { should respond_to(:first_name) } - it { should respond_to(:first_name=) } - it { should_not respond_to(:nonexistent) } - - it 'errors out for a non-existent property' do - lambda { subject['nonexistent'] }.should raise_error(NoMethodError) - end - - it 'errors out when attempting to set a required property to nil' do - lambda { subject.first_name = nil }.should raise_error(ArgumentError) - end - - context 'writing to properties' do - it 'fails writing to a non-existent property using []=' do - lambda { subject['nonexistent'] = 123 }.should raise_error(NoMethodError) - end - - it 'works for an existing property using []=' do - subject['first_name'] = 'Bob' - subject['first_name'].should == 'Bob' - subject[:first_name].should == 'Bob' - end - - it 'works for an existing property using a method call' do - subject.first_name = 'Franklin' - subject.first_name.should == 'Franklin' - end - end - - context 'reading from properties' do - it 'fails reading from a non-existent property using []' do - lambda { subject['nonexistent'] }.should raise_error(NoMethodError) - end - - it "should be able to retrieve properties through blocks" do - subject["first_name"] = "Aiden" - value = nil - subject.[]("first_name") { |v| value = v } - value.should == "Aiden" - end - - it "should be able to retrieve properties through blocks with method calls" do - subject["first_name"] = "Frodo" - value = nil - subject.first_name { |v| value = v } - value.should == "Frodo" - end - end - - describe '.new' do - it 'fails with non-existent properties' do - lambda { described_class.new(:bork => '') }.should raise_error(NoMethodError) - end - - it 'should set properties that it is able to' do - obj = described_class.new :first_name => 'Michael', :email => 'michael@example.com' - obj.first_name.should == 'Michael' - end - - it 'does not accept nil' do - lambda { described_class.new(nil) }.should raise_error(ArgumentError) - end - - it 'accepts block to define a global default' do - obj = described_class.new { |hash, key| key.to_s.upcase } - obj.first_name.should == 'FIRST_NAME' - obj.count.should be_zero - end - end - - describe 'properties' do - it 'lists defined properties' do - described_class.properties.should == Set.new([:first_name, :email, :count]) - end - - it 'checks if a property exists' do - described_class.property?('first_name').should be_true - described_class.property?(:first_name).should be_true - end - - it 'doesnt include property from subclass' do - described_class.property?(:last_name).should be_false - end - - it 'lists declared defaults' do - described_class.defaults.should == { :count => 0 } - end - end -end - -describe Hashie::Lash, 'inheritance' do - before do - @top = Class.new(Hashie::Lash) - @middle = Class.new(@top) - @bottom = Class.new(@middle) - end - - it 'reports empty properties when nothing defined' do - @top.properties.should be_empty - @top.defaults.should be_empty - end - - it 'inherits properties downwards' do - @top.property :echo - @middle.properties.should include(:echo) - @bottom.properties.should include(:echo) - end - - it 'doesnt inherit properties upwards' do - @middle.property :echo - @top.properties.should_not include(:echo) - @bottom.properties.should include(:echo) - end - - it 'allows overriding a default on an existing property' do - @top.property :echo - @middle.property :echo, :default => 123 - @bottom.properties.to_a.should == [:echo] - @bottom.new.echo.should == 123 - end - - it 'allows clearing an existing default' do - @top.property :echo - @middle.property :echo, :default => 123 - @bottom.property :echo - @bottom.properties.to_a.should == [:echo] - @bottom.new.echo.should be_nil - end - - it 'should allow nil defaults' do - @bottom.property :echo, :default => nil - @bottom.new.should have_key('echo') - end - -end - -describe SubclassedLash do - - subject { SubclassedLash.new(:first_name => 'Bob', :email => 'bob@example.com', :last_name => 'Example') } - - its(:count) { should be_zero } - - it { should respond_to(:first_name) } - it { should respond_to(:first_name=) } - it { should respond_to(:last_name) } - it { should respond_to(:last_name=) } - - it 'has one additional property' do - described_class.property?(:last_name).should be_true - end - - it "didn't override superclass inheritance logic" do - described_class.instance_variable_get('@inheritance_test').should be_true - end - -end |