Skip to content

Commit

Permalink
Merge pull request hashie#19 from bramswenson/master
Browse files Browse the repository at this point in the history
New Dash property option :required
  • Loading branch information
Michael Bleigh committed Jul 29, 2011
2 parents a0fca68 + 901ca56 commit 5a62f37
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 47 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,3 +5,4 @@ rdoc
pkg
*.gem
.bundle
.rvmrc
39 changes: 20 additions & 19 deletions README.rdoc
Expand Up @@ -4,7 +4,7 @@ Hashie is a growing collection of tools that extend Hashes and make
them more useful.

== Installation

Hashie is available as a RubyGem:

gem install hashie
Expand All @@ -13,54 +13,54 @@ Hashie is available as a RubyGem:

Mash is an extended Hash that gives simple pseudo-object functionality
that can be built from hashes and easily extended. It is designed to
be used in RESTful API libraries to provide easy object-like access
be used in RESTful API libraries to provide easy object-like access
to JSON and XML parsed hashes.

=== Example:

mash = Hashie::Mash.new
mash.name? # => false
mash.name # => nil
mash.name = "My Mash"
mash.name # => "My Mash"
mash.name? # => true
mash.inspect # => <Hashie::Mash name="My Mash">

mash = Mash.new
# use bang methods for multi-level assignment
mash.author!.name = "Michael Bleigh"
mash.author # => <Hashie::Mash name="Michael Bleigh">

<b>Note:</b> The <tt>?</tt> method will return false if a key has been set
<b>Note:</b> The <tt>?</tt> method will return false if a key has been set
to false or nil. In order to check if a key has been set at all, use the
<tt>mash.key?('some_key')</tt> method instead.

== Dash

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.
Expand All @@ -69,12 +69,12 @@ It is used like so:
class Person < Hashie::Trash
property :first_name, :from => :firstName
end

This will automatically translate the <tt>firstName</tt> key to <tt>first_name</tt>
when it is initialized using a hash such as through:

Person.new(:firstName => 'Bob')

== Clash

Clash is a Chainable Lazy Hash that allows you to easily construct
Expand Down Expand Up @@ -102,8 +102,9 @@ provide.
c.where(:abc => 'def').where(:hgi => 123)
c # => {:where => {:abc => 'def', :hgi => 123}}


== Note on Patches/Pull Requests

* Fork the project.
* Make your feature addition or bug fix.
* Add tests for it. This is important so I don't break it in a future version unintentionally.
Expand Down
2 changes: 1 addition & 1 deletion lib/hashie.rb
@@ -1,6 +1,6 @@
module Hashie
autoload :HashExtensions, 'hashie/hash_extensions'
autoload :PrettyInspect, 'hashie/hash_extensions'
autoload :PrettyInspect, 'hashie/hash_extensions'
autoload :Hash, 'hashie/hash'
autoload :Trash, 'hashie/trash'
autoload :Mash, 'hashie/mash'
Expand Down
37 changes: 36 additions & 1 deletion lib/hashie/dash.rb
Expand Up @@ -23,13 +23,17 @@ class Dash < Hashie::Hash
# 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
Expand All @@ -49,19 +53,23 @@ def #{property_name}=(value)
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
Expand All @@ -70,6 +78,12 @@ def self.property?(name)
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)
Expand All @@ -82,6 +96,7 @@ def initialize(attributes = {}, &block)
attributes.each_pair do |att, value|
self[att] = value
end if attributes
assert_required_properties_set!
end

alias_method :_regular_reader, :[]
Expand All @@ -100,6 +115,7 @@ def [](property)
# 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
Expand All @@ -111,5 +127,24 @@ def assert_property_exists!(property)
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

0 comments on commit 5a62f37

Please sign in to comment.