smart_hash 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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