smart_hash 0.1.0 → 0.1.1

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.
data/.gitignore CHANGED
@@ -2,10 +2,10 @@
2
2
  .ref-*
3
3
  .old*
4
4
  *-old*
5
+ /.rvmrc
5
6
 
6
7
  # Project-specific.
7
8
  /*.rb
8
9
  /doc/
9
10
  /pkg/
10
- /.rvmrc
11
11
  /.yardoc
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
  A smarter alternative to OpenStruct
3
3
  ===================================
4
4
 
5
+
5
6
  Introduction
6
7
  ------------
7
8
 
@@ -20,7 +21,7 @@ Setup
20
21
  $ gem install smart_hash
21
22
  ~~~
22
23
 
23
- , or via Bundler's `Gemfile`:
24
+ Or using Bundler's `Gemfile`:
24
25
 
25
26
  ~~~
26
27
  gem "smart_hash"
data/Rakefile CHANGED
@@ -1 +1,7 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run all specs"
5
+ RSpec::Core::RakeTask.new do |t|
6
+ #t.pattern = "./spec/**/*_spec.rb" # Should be the default pattern.
7
+ end
data/lib/smart_hash.rb CHANGED
@@ -1,7 +1,14 @@
1
1
  require "set"
2
2
 
3
- # Get us `SmartHash::Loose`. It's usually `Dir[]` in other gems, but we've only got 1 file at the moment.
4
- require File.expand_path("../smart_hash/loose", __FILE__)
3
+ # Load our stuff.
4
+ # NOTE: Our includes are capable of being loaded in arbitrary order (for spec and stuff), hence the `< Hash` in each of them.
5
+ [
6
+ "smart_hash/**/*.rb",
7
+ ].each do |fmask|
8
+ Dir[File.expand_path("../#{fmask}", __FILE__)].each do |fn|
9
+ require fn
10
+ end
11
+ end
5
12
 
6
13
  # == A smarter alternative to OpenStruct
7
14
  #
@@ -21,9 +28,6 @@ class SmartHash < Hash
21
28
  # Forbidden attrs cannot be manupulated as such and are handled as methods only.
22
29
  FORBIDDEN_ATTRS = [:default, :default_proc, :strict]
23
30
 
24
- # Gem version.
25
- VERSION = "0.1.0"
26
-
27
31
  # See #declare.
28
32
  attr_reader :declared_attrs
29
33
 
@@ -67,10 +71,10 @@ class SmartHash < Hash
67
71
  #
68
72
  # See also #undeclare.
69
73
  def declare(*attrs)
70
- raise ArgumentError, "No attrs specified" if attrs.empty?
74
+ raise ArgumentError, "No attributes specified" if attrs.empty?
71
75
  attrs.each do |attr|
72
- (v = attr).is_a?(klass = Symbol) or raise ArgumentError, "#{klass} expected, #{v.class} (#{v.inspect}) given"
73
- attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name '#{attr}'"
76
+ [attr, Symbol].tap {|v, klass| v.is_a?(klass) or raise ArgumentError, "#{klass} expected, #{v.class} (#{v.inspect}) given"}
77
+ attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name: #{attr}"
74
78
  @declared_attrs << attr # `Set` is returned.
75
79
  end
76
80
  end
@@ -85,23 +89,23 @@ class SmartHash < Hash
85
89
  #
86
90
  # See also #unprotect.
87
91
  def protect(*attrs)
88
- raise ArgumentError, "No attrs specified" if attrs.empty?
92
+ raise ArgumentError, "No attributes specified" if attrs.empty?
89
93
  attrs.each do |attr|
90
- (v = attr).is_a?(klass = Symbol) or raise ArgumentError, "#{klass} expected, #{v.class} (#{v.inspect}) given"
91
- attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name '#{attr}'"
94
+ [attr, Symbol].tap {|v, klass| v.is_a?(klass) or raise ArgumentError, "#{klass} expected, #{v.class} (#{v.inspect}) given"}
95
+ attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name: #{attr}"
92
96
  @protected_attrs << attr
93
97
  end
94
98
  end
95
99
 
96
100
  def undeclare(*attrs)
97
- raise ArgumentError, "No attrs specified" if attrs.empty?
101
+ raise ArgumentError, "No attributes specified" if attrs.empty?
98
102
  attrs.each do |attr|
99
103
  @declared_attrs.delete(attr) # `Set` is returned.
100
104
  end
101
105
  end
102
106
 
103
107
  def unprotect(*attrs)
104
- raise ArgumentError, "No attrs specified" if attrs.empty?
108
+ raise ArgumentError, "No attributes specified" if attrs.empty?
105
109
  attrs.each do |attr|
106
110
  @protected_attrs.delete(attr)
107
111
  end
@@ -110,7 +114,7 @@ class SmartHash < Hash
110
114
  private
111
115
 
112
116
  # Make private copies of methods we need.
113
- [:fetch, :instance_eval].each do |method_name|
117
+ [:fetch].each do |method_name|
114
118
  my_method_name = "_smart_hash_#{method_name}".to_sym
115
119
  alias_method my_method_name, method_name
116
120
  private my_method_name
@@ -118,6 +122,10 @@ class SmartHash < Hash
118
122
 
119
123
  # Common post-initialize routine.
120
124
  def _smart_hash_init #:nodoc:
125
+ # At early stages of construction via `[]` half-ready instances might be accessed.
126
+ # Do determine such situations we need a flag.
127
+ @is_initialized = true
128
+
121
129
  @declared_attrs = Set[]
122
130
  @strict = true
123
131
 
@@ -126,45 +134,66 @@ class SmartHash < Hash
126
134
  # he'll find a way to do it anyway.
127
135
  @protected_attrs = Set[:inspect, :to_s]
128
136
 
129
- # Suppress warnings.
130
- vrb, $VERBOSE = $VERBOSE, nil
131
-
132
- # Insert lookup routine for existing methods, such as <tt>size</tt>.
133
- methods.map(&:to_s).each do |method_name|
134
- # Install control routine on correct attribute access methods only.
135
- # NOTE: Check longer REs first.
136
- case method_name
137
- when /\A(#{ATTR_REGEXP})=\z/
138
- # Case "r.attr=".
139
- attr = $1.to_sym
140
- next if FORBIDDEN_ATTRS.include? attr
141
- _smart_hash_instance_eval <<-EOT
142
- def #{method_name}(value)
143
- raise ArgumentError, "Attribute '#{attr}' is protected" if @protected_attrs.include? :#{attr}
144
- self[:#{attr}] = value
145
- end
146
- EOT
147
- when /\A#{ATTR_REGEXP}\z/
148
- # Case "r.attr".
149
- next if FORBIDDEN_ATTRS.include? attr
150
- _smart_hash_instance_eval <<-EOT
151
- def #{method_name}(*args)
152
- if @declared_attrs.include?(:#{method_name}) or has_key?(:#{method_name})
153
- if @strict
154
- _smart_hash_fetch(:#{method_name})
137
+ # Extend own class with dynamic methods if needed.
138
+ if not self.class < DynamicMethods
139
+ method_names = methods.map(&:to_s)
140
+
141
+ # Skip methods matching forbidden attrs.
142
+ method_names -= FORBIDDEN_ATTRS.map(&:to_s) + FORBIDDEN_ATTRS.map {|_| "#{_}="}
143
+
144
+ # Collect pieces of code.
145
+ pcs = []
146
+
147
+ method_names.each do |method_name|
148
+ case method_name
149
+ when /\A(#{ATTR_REGEXP})=\z/
150
+ # Assignment.
151
+ attr = $1.to_sym
152
+ # NOTE: See `@is_initialized` checks -- our code must take control only when the object is fully initialized.
153
+ pcs << %{
154
+ def #{method_name}(value)
155
+ if @is_initialized
156
+ if @protected_attrs.include? :#{attr}
157
+ raise ArgumentError, "Attribute is protected: #{attr}"
158
+ else
159
+ self[:#{attr}] = value
160
+ end
161
+ else
162
+ super
163
+ end
164
+ end
165
+ } # pcs <<
166
+ when /\A(#{ATTR_REGEXP})\z/
167
+ # Access.
168
+ attr = $1.to_sym
169
+ pcs << %{
170
+ def #{method_name}(*args)
171
+ if @is_initialized and (@declared_attrs.include?(:#{attr}) or has_key?(:#{attr}))
172
+ if @strict
173
+ _smart_hash_fetch(:#{attr})
174
+ else
175
+ self[:#{attr}]
176
+ end
155
177
  else
156
- self[:#{method_name}]
178
+ super
157
179
  end
158
- else
159
- super
160
180
  end
161
- end
162
- EOT
163
- end # case
164
- end # each
181
+ } # pcs <<
182
+ end # case
183
+ end # method_names.each
165
184
 
166
- # Restore warnings.
167
- $VERBOSE = vrb
185
+ # Suppress warnings.
186
+ vrb, $VERBOSE = $VERBOSE, nil
187
+
188
+ # Create dynamic methods.
189
+ DynamicMethods.class_eval pcs.join("\n")
190
+
191
+ # Restore warnings.
192
+ $VERBOSE = vrb
193
+
194
+ # Include dynamic methods.
195
+ self.class.class_eval "include DynamicMethods"
196
+ end
168
197
  end
169
198
 
170
199
  def method_missing(method_name, *args)
@@ -172,17 +201,17 @@ class SmartHash < Hash
172
201
 
173
202
  case method_name
174
203
  when /\A(.+)=\z/
175
- # Case "r.attr=". Attribute assignment. Method name is pre-validated for us by Ruby.
204
+ # Assignment. Method name is pre-validated for us by Ruby.
176
205
  attr = $1.to_sym
177
- raise ArgumentError, "Attribute '#{attr}' is protected" if @protected_attrs.include? attr
178
-
206
+ raise ArgumentError, "Attribute is protected: #{attr}" if @protected_attrs.include? attr
179
207
  self[attr] = args[0]
180
- when /\A#{ATTR_REGEXP}\z/
181
- # Case "r.attr".
208
+ when /\A(#{ATTR_REGEXP})\z/
209
+ # Access.
210
+ attr = $1.to_sym
182
211
  if @strict
183
- _smart_hash_fetch(method_name)
212
+ _smart_hash_fetch(attr)
184
213
  else
185
- self[method_name]
214
+ self[attr]
186
215
  end
187
216
  else
188
217
  super
@@ -0,0 +1,7 @@
1
+ require File.expand_path("../../smart_hash", __FILE__)
2
+
3
+ class SmartHash < Hash
4
+ # This module will get dynamically extended with methods which exist in `Hash`.
5
+ module DynamicMethods
6
+ end
7
+ end
@@ -1,3 +1,5 @@
1
+ require File.expand_path("../../smart_hash", __FILE__)
2
+
1
3
  class SmartHash < Hash
2
4
  # Non-strict SmartHash
3
5
  #
@@ -0,0 +1,6 @@
1
+ require File.expand_path("../../smart_hash", __FILE__)
2
+
3
+ class SmartHash < Hash
4
+ # Gem version.
5
+ VERSION = "0.1.1"
6
+ end
data/smart_hash.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path("../lib/smart_hash", __FILE__)
1
+ require File.expand_path("../lib/smart_hash/version", __FILE__)
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "smart_hash"
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
 
15
15
  s.files = `git ls-files`.split("\n")
16
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map {|f| File.basename(f)}
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  s.add_development_dependency "rspec"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_hash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-08 00:00:00.000000000Z
12
+ date: 2012-03-13 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &85732360 !ruby/object:Gem::Requirement
16
+ requirement: &73881490 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *85732360
24
+ version_requirements: *73881490
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: yard
27
- requirement: &85732120 !ruby/object:Gem::Requirement
27
+ requirement: &73881180 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *85732120
35
+ version_requirements: *73881180
36
36
  description: A smarter alternative to OpenStruct
37
37
  email:
38
38
  - alex.r@askit.org
@@ -48,7 +48,9 @@ files:
48
48
  - README.md
49
49
  - Rakefile
50
50
  - lib/smart_hash.rb
51
+ - lib/smart_hash/dynamic_methods.rb
51
52
  - lib/smart_hash/loose.rb
53
+ - lib/smart_hash/version.rb
52
54
  - smart_hash.gemspec
53
55
  - spec/smart_hash_spec.rb
54
56
  - spec/spec_helper.rb