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 +1 -1
- data/README.md +2 -1
- data/Rakefile +6 -0
- data/lib/smart_hash.rb +85 -56
- data/lib/smart_hash/dynamic_methods.rb +7 -0
- data/lib/smart_hash/loose.rb +2 -0
- data/lib/smart_hash/version.rb +6 -0
- data/smart_hash.gemspec +2 -2
- metadata +8 -6
data/.gitignore
CHANGED
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
|
-
|
24
|
+
Or using Bundler's `Gemfile`:
|
24
25
|
|
25
26
|
~~~
|
26
27
|
gem "smart_hash"
|
data/Rakefile
CHANGED
data/lib/smart_hash.rb
CHANGED
@@ -1,7 +1,14 @@
|
|
1
1
|
require "set"
|
2
2
|
|
3
|
-
#
|
4
|
-
|
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
|
74
|
+
raise ArgumentError, "No attributes specified" if attrs.empty?
|
71
75
|
attrs.each do |attr|
|
72
|
-
|
73
|
-
attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name
|
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
|
92
|
+
raise ArgumentError, "No attributes specified" if attrs.empty?
|
89
93
|
attrs.each do |attr|
|
90
|
-
|
91
|
-
attr.to_s.match /\A#{ATTR_REGEXP}\z/ or raise ArgumentError, "Incorrect attribute name
|
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
|
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
|
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
|
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
|
-
#
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
178
|
+
super
|
157
179
|
end
|
158
|
-
else
|
159
|
-
super
|
160
180
|
end
|
161
|
-
|
162
|
-
|
163
|
-
end #
|
164
|
-
end # each
|
181
|
+
} # pcs <<
|
182
|
+
end # case
|
183
|
+
end # method_names.each
|
165
184
|
|
166
|
-
|
167
|
-
|
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
|
-
#
|
204
|
+
# Assignment. Method name is pre-validated for us by Ruby.
|
176
205
|
attr = $1.to_sym
|
177
|
-
raise ArgumentError, "Attribute
|
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
|
-
#
|
208
|
+
when /\A(#{ATTR_REGEXP})\z/
|
209
|
+
# Access.
|
210
|
+
attr = $1.to_sym
|
182
211
|
if @strict
|
183
|
-
_smart_hash_fetch(
|
212
|
+
_smart_hash_fetch(attr)
|
184
213
|
else
|
185
|
-
self[
|
214
|
+
self[attr]
|
186
215
|
end
|
187
216
|
else
|
188
217
|
super
|
data/lib/smart_hash/loose.rb
CHANGED
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{
|
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.
|
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-
|
12
|
+
date: 2012-03-13 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
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: *
|
24
|
+
version_requirements: *73881490
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: yard
|
27
|
-
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: *
|
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
|