wahashie 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 258e46712a6b4ef449c044714d29ad3ba3ea6188
4
+ data.tar.gz: 276f1cc363c7b4b9b842c7a6edee29663507c479
5
+ SHA512:
6
+ metadata.gz: bbcb4e8a70d26344547085d4a4a07fdc730899e9b8481ddefaa0541c193558dff9e2b0baf278359e9de3db8a5e5b78fc3679024568e8bf2a76a162df383f0003
7
+ data.tar.gz: de962728e1af3e83e9bc5bab255de03d00882c27b2c9d0e1d2675e8c5451ffa31f67b6cd4eb5488143ac6867925ce27c533a5dca20ac481e8cc1b275e2ea9728
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.gem
7
+ .bundle
8
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format=nested
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - rbx
5
+ - ree
6
+ - ruby-head
7
+ - jruby
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ -m markdown
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'http://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ wahashie (1.2.0)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.1.2)
10
+ guard (0.5.1)
11
+ thor (~> 0.14.6)
12
+ guard-rspec (0.4.0)
13
+ guard (>= 0.4.0)
14
+ rake (0.9.2)
15
+ rspec (2.6.0)
16
+ rspec-core (~> 2.6.0)
17
+ rspec-expectations (~> 2.6.0)
18
+ rspec-mocks (~> 2.6.0)
19
+ rspec-core (2.6.4)
20
+ rspec-expectations (2.6.0)
21
+ diff-lcs (~> 1.1.2)
22
+ rspec-mocks (2.6.0)
23
+ thor (0.14.6)
24
+
25
+ PLATFORMS
26
+ java
27
+ ruby
28
+
29
+ DEPENDENCIES
30
+ guard
31
+ guard-rspec
32
+ rake (~> 0.9.2)
33
+ rspec (~> 2.5)
34
+ wahashie!
35
+
36
+ BUNDLED WITH
37
+ 1.15.4
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb})
3
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Intridea, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,120 @@
1
+ = Wahashie
2
+
3
+ Wahashie is a growing collection of tools that extend Hashes and make
4
+ them more useful.
5
+
6
+ == Installation
7
+
8
+ Wahashie is available as a RubyGem:
9
+
10
+ gem install wahashie
11
+
12
+ == Mash
13
+
14
+ Mash is an extended Hash that gives simple pseudo-object functionality
15
+ that can be built from hashes and easily extended. It is designed to
16
+ be used in RESTful API libraries to provide easy object-like access
17
+ to JSON and XML parsed hashes.
18
+
19
+ === Example:
20
+
21
+ mash = Wahashie::Mash.new
22
+ mash.name? # => false
23
+ mash.name # => nil
24
+ mash.name = "My Mash"
25
+ mash.name # => "My Mash"
26
+ mash.name? # => true
27
+ mash.inspect # => <Wahashie::Mash name="My Mash">
28
+
29
+ mash = Mash.new
30
+ # use bang methods for multi-level assignment
31
+ mash.author!.name = "Michael Bleigh"
32
+ mash.author # => <Wahashie::Mash name="Michael Bleigh">
33
+
34
+ <b>Note:</b> The <tt>?</tt> method will return false if a key has been set
35
+ to false or nil. In order to check if a key has been set at all, use the
36
+ <tt>mash.key?('some_key')</tt> method instead.
37
+
38
+ == Dash
39
+
40
+ Dash is an extended Hash that has a discrete set of defined properties
41
+ and only those properties may be set on the hash. Additionally, you
42
+ can set defaults for each property. You can also flag a property as
43
+ required. Required properties will raise an execption if unset.
44
+
45
+ === Example:
46
+
47
+ class Person < Wahashie::Dash
48
+ property :name, :required => true
49
+ property :email
50
+ property :occupation, :default => 'Rubyist'
51
+ end
52
+
53
+ p = Person.new # => ArgumentError: The property 'name' is required for this Dash.
54
+
55
+ p = Person.new(:name => "Bob")
56
+ p.name # => 'Bob'
57
+ p.name = nil # => ArgumentError: The property 'name' is required for this Dash.
58
+ p.email = 'abc@def.com'
59
+ p.occupation # => 'Rubyist'
60
+ p.email # => 'abc@def.com'
61
+ p[:awesome] # => NoMethodError
62
+ p[:occupation] # => 'Rubyist'
63
+
64
+ == Trash
65
+
66
+ A Trash is a Dash that allows you to translate keys on initialization.
67
+ It is used like so:
68
+
69
+ class Person < Wahashie::Trash
70
+ property :first_name, :from => :firstName
71
+ end
72
+
73
+ This will automatically translate the <tt>firstName</tt> key to <tt>first_name</tt>
74
+ when it is initialized using a hash such as through:
75
+
76
+ Person.new(:firstName => 'Bob')
77
+
78
+ == Clash
79
+
80
+ Clash is a Chainable Lazy Hash that allows you to easily construct
81
+ complex hashes using method notation chaining. This will allow you
82
+ to use a more action-oriented approach to building options hashes.
83
+
84
+ Essentially, a Clash is a generalized way to provide much of the same
85
+ kind of "chainability" that libraries like Arel or Rails 2.x's named_scopes
86
+ provide.
87
+
88
+ === Example
89
+
90
+ c = Wahashie::Clash.new
91
+ c.where(:abc => 'def').order(:created_at)
92
+ c # => {:where => {:abc => 'def}, :order => :created_at}
93
+
94
+ # You can also use bang notation to chain into sub-hashes,
95
+ # jumping back up the chain with _end!
96
+ c = Wahashie::Clash.new
97
+ c.where!.abc('def').ghi(123)._end!.order(:created_at)
98
+ c # => {:where => {:abc => 'def', :ghi => 123}, :order => :created_at}
99
+
100
+ # Multiple hashes are merged automatically
101
+ c = Wahashie::Clash.new
102
+ c.where(:abc => 'def').where(:hgi => 123)
103
+ c # => {:where => {:abc => 'def', :hgi => 123}}
104
+
105
+
106
+ == Note on Patches/Pull Requests
107
+
108
+ * Fork the project.
109
+ * Make your feature addition or bug fix.
110
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
111
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
112
+ * Send me a pull request. Bonus points for topic branches.
113
+
114
+ == Authors
115
+
116
+ * Michael Bleigh
117
+
118
+ == Copyright
119
+
120
+ Copyright (c) 2009 Intridea, Inc (http://intridea.com/). See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.setup
4
+
5
+ Bundler::GemHelper.install_tasks
6
+
7
+ require 'rspec/core/rake_task'
8
+ RSpec::Core::RakeTask.new do |spec|
9
+ # spec.libs << 'lib' << 'spec'
10
+ spec.pattern = 'spec/**/*_spec.rb'
11
+ end
12
+
13
+ task :default => :spec
data/lib/wahashie.rb ADDED
@@ -0,0 +1,9 @@
1
+ module Wahashie
2
+ autoload :HashExtensions, 'wahashie/hash_extensions'
3
+ autoload :PrettyInspect, 'wahashie/hash_extensions'
4
+ autoload :Hash, 'wahashie/hash'
5
+ autoload :Trash, 'wahashie/trash'
6
+ autoload :Mash, 'wahashie/mash'
7
+ autoload :Dash, 'wahashie/dash'
8
+ autoload :Clash, 'wahashie/clash'
9
+ end
@@ -0,0 +1,86 @@
1
+ require 'wahashie/hash'
2
+
3
+ module Wahashie
4
+ #
5
+ # A Clash is a "Chainable Lazy Hash". Inspired by libraries such as Arel,
6
+ # a Clash allows you to chain together method arguments to build a
7
+ # hash, something that's especially useful if you're doing something
8
+ # like constructing a complex options hash. Here's a basic example:
9
+ #
10
+ # c = Wahashie::Clash.new.conditions(:foo => 'bar').order(:created_at)
11
+ # c # => {:conditions => {:foo => 'bar'}, :order => :created_at}
12
+ #
13
+ # Clash provides another way to create sub-hashes by using bang notation.
14
+ # You can dive into a sub-hash by providing a key with a bang and dive
15
+ # back out again with the _end! method. Example:
16
+ #
17
+ # c = Wahashie::Clash.new.conditions!.foo('bar').baz(123)._end!.order(:created_at)
18
+ # c # => {:conditions => {:foo => 'bar', :baz => 123}, :order => :created_at}
19
+ #
20
+ # Because the primary functionality of Clash is to build options objects,
21
+ # all keys are converted to symbols since many libraries expect symbols explicitly
22
+ # for keys.
23
+ #
24
+ class Clash < ::Hash
25
+ class ChainError < ::StandardError; end
26
+ # The parent Clash if this Clash was created via chaining.
27
+ attr_reader :_parent
28
+
29
+ # Initialize a new clash by passing in a Hash to
30
+ # convert and, optionally, the parent to which this
31
+ # Clash is chained.
32
+ def initialize(other_hash = {}, parent = nil)
33
+ @_parent = parent
34
+ other_hash.each_pair do |k, v|
35
+ self[k.to_sym] = v
36
+ end
37
+ end
38
+
39
+ # Jump back up a level if you are using bang method
40
+ # chaining. For example:
41
+ #
42
+ # c = Wahashie::Clash.new.foo('bar')
43
+ # c.baz!.foo(123) # => c[:baz]
44
+ # c.baz!._end! # => c
45
+ def _end!
46
+ self._parent
47
+ end
48
+
49
+ def id(*args) #:nodoc:
50
+ method_missing(:id, *args)
51
+ end
52
+
53
+ def merge_store(key, *args) #:nodoc:
54
+ case args.length
55
+ when 1
56
+ val = args.first
57
+ val = self[key].merge(val) if self[key].is_a?(::Hash) && val.is_a?(::Hash)
58
+ else
59
+ val = args
60
+ end
61
+
62
+ self[key.to_sym] = val
63
+ self
64
+ end
65
+
66
+ def method_missing(name, *args) #:nodoc:
67
+ name = name.to_s
68
+ if name.match(/!$/) && args.empty?
69
+ key = name[0...-1].to_sym
70
+
71
+ if self[key].nil?
72
+ self[key] = Clash.new({}, self)
73
+ elsif self[key].is_a?(::Hash) && !self[key].is_a?(Clash)
74
+ self[key] = Clash.new(self[key], self)
75
+ else
76
+ raise ChainError, "Tried to chain into a non-hash key."
77
+ end
78
+
79
+ self[key]
80
+ elsif args.any?
81
+ key = name.to_sym
82
+ self.merge_store(key, *args)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,150 @@
1
+ require 'wahashie/hash'
2
+ require 'set'
3
+
4
+ module Wahashie
5
+ # A Dash is a 'defined' or 'discrete' Hash, that is, a Hash
6
+ # that has a set of defined keys that are accessible (with
7
+ # optional defaults) and only those keys may be set or read.
8
+ #
9
+ # Dashes are useful when you need to create a very simple
10
+ # lightweight data object that needs even fewer options and
11
+ # resources than something like a DataMapper resource.
12
+ #
13
+ # It is preferrable to a Struct because of the in-class
14
+ # API for defining properties as well as per-property defaults.
15
+ class Dash < Wahashie::Hash
16
+ include Wahashie::PrettyInspect
17
+ alias_method :to_s, :inspect
18
+
19
+ # Defines a property on the Dash. Options are
20
+ # as follows:
21
+ #
22
+ # * <tt>:default</tt> - Specify a default value for this property,
23
+ # to be returned before a value is set on the property in a new
24
+ # Dash.
25
+ #
26
+ # * <tt>:required</tt> - Specify the value as required for this
27
+ # property, to raise an error if a value is unset in a new or
28
+ # existing Dash.
29
+ #
30
+ def self.property(property_name, options = {})
31
+ property_name = property_name.to_sym
32
+
33
+ self.properties << property_name
34
+
35
+ if options.has_key?(:default)
36
+ self.defaults[property_name] = options[:default]
37
+ elsif self.defaults.has_key?(property_name)
38
+ self.defaults.delete property_name
39
+ end
40
+
41
+ unless instance_methods.map { |m| m.to_s }.include?("#{property_name}=")
42
+ class_eval <<-ACCESSORS
43
+ def #{property_name}(&block)
44
+ self.[](#{property_name.to_s.inspect}, &block)
45
+ end
46
+
47
+ def #{property_name}=(value)
48
+ self.[]=(#{property_name.to_s.inspect}, value)
49
+ end
50
+ ACCESSORS
51
+ end
52
+
53
+ if defined? @subclasses
54
+ @subclasses.each { |klass| klass.property(property_name, options) }
55
+ end
56
+ required_properties << property_name if options.delete(:required)
57
+ end
58
+
59
+ class << self
60
+ attr_reader :properties, :defaults
61
+ attr_reader :required_properties
62
+ end
63
+ instance_variable_set('@properties', Set.new)
64
+ instance_variable_set('@defaults', {})
65
+ instance_variable_set('@required_properties', Set.new)
66
+
67
+ def self.inherited(klass)
68
+ super
69
+ (@subclasses ||= Set.new) << klass
70
+ klass.instance_variable_set('@properties', self.properties.dup)
71
+ klass.instance_variable_set('@defaults', self.defaults.dup)
72
+ klass.instance_variable_set('@required_properties', self.required_properties.dup)
73
+ end
74
+
75
+ # Check to see if the specified property has already been
76
+ # defined.
77
+ def self.property?(name)
78
+ properties.include? name.to_sym
79
+ end
80
+
81
+ # Check to see if the specified property is
82
+ # required.
83
+ def self.required?(name)
84
+ required_properties.include? name.to_sym
85
+ end
86
+
87
+ # You may initialize a Dash with an attributes hash
88
+ # just like you would many other kinds of data objects.
89
+ def initialize(attributes = {}, &block)
90
+ super(&block)
91
+
92
+ self.class.defaults.each_pair do |prop, value|
93
+ self[prop] = value
94
+ end
95
+
96
+ attributes.each_pair do |att, value|
97
+ self[att] = value
98
+ end if attributes
99
+ assert_required_properties_set!
100
+ end
101
+
102
+ alias_method :_regular_reader, :[]
103
+ alias_method :_regular_writer, :[]=
104
+ private :_regular_reader, :_regular_writer
105
+
106
+ # Retrieve a value from the Dash (will return the
107
+ # property's default value if it hasn't been set).
108
+ def [](property)
109
+ assert_property_exists! property
110
+ value = super(property.to_s)
111
+ yield value if block_given?
112
+ value
113
+ end
114
+
115
+ # Set a value on the Dash in a Hash-like way. Only works
116
+ # on pre-existing properties.
117
+ def []=(property, value)
118
+ assert_property_required! property, value
119
+ assert_property_exists! property
120
+ super(property.to_s, value)
121
+ end
122
+
123
+ private
124
+
125
+ def assert_property_exists!(property)
126
+ unless self.class.property?(property)
127
+ raise NoMethodError, "The property '#{property}' is not defined for this Dash."
128
+ end
129
+ end
130
+
131
+ def assert_required_properties_set!
132
+ self.class.required_properties.each do |required_property|
133
+ assert_property_set!(required_property)
134
+ end
135
+ end
136
+
137
+ def assert_property_set!(property)
138
+ if send(property).nil?
139
+ raise ArgumentError, "The property '#{property}' is required for this Dash."
140
+ end
141
+ end
142
+
143
+ def assert_property_required!(property, value)
144
+ if self.class.required?(property) && value.nil?
145
+ raise ArgumentError, "The property '#{property}' is required for this Dash."
146
+ end
147
+ end
148
+
149
+ end
150
+ end