summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Swenson <bram.swenson@akqa.com>2011-07-23 11:25:38 -0700
committerBram Swenson <bram.swenson@akqa.com>2011-07-23 11:25:38 -0700
commit901ca566cae9ddac09f14b6fa59cea7100c1003a (patch)
treed69b5280643d210a07ce19f81bd5101fcaae8719
parente470eb50c50268689a88bd5383a189376d645a2a (diff)
downloadhashie-901ca566cae9ddac09f14b6fa59cea7100c1003a.tar.gz
[REQUIRED] properties added to Dash, removed Lash class
-rw-r--r--README.rdoc42
-rw-r--r--lib/hashie.rb1
-rw-r--r--lib/hashie/dash.rb37
-rw-r--r--lib/hashie/lash.rb89
-rw-r--r--spec/hashie/dash_spec.rb85
-rw-r--r--spec/hashie/lash_spec.rb194
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