hashing 0.0.1 → 0.1.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 +13 -5
- data/CHANGELOG.md +13 -0
- data/README.md +97 -36
- data/hashing.gemspec +2 -2
- data/lib/hashing.rb +34 -155
- data/lib/hashing/hasher.rb +184 -0
- data/lib/hashing/ivar.rb +3 -38
- data/lib/hashing/ivar_collection.rb +23 -0
- data/lib/hashing/macros.rb +38 -0
- data/lib/hashing/unconfigured_ivar_error.rb +13 -0
- data/lib/hashing/version.rb +1 -1
- data/test/hashing/hasher_test.rb +35 -0
- data/test/hashing/nested_test.rb +35 -31
- data/test/hashing_test.rb +43 -35
- metadata +19 -14
- data/lib/hasherize.rb +0 -40
- data/test/hasherize_test.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YWJiN2EwYTk5Y2MyNDQzZjQ1MGQxYzcyMjE0YTE1MjRlMmRlZDIyMQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
M2NkMjcxOThkMGI2ZWVkY2JhZjY4YTMwYjEzMGY0YTliYjVhOWQ2OA==
|
5
7
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
M2I3ODUxM2UxZWRkZWQxNmM2NjcxMDc5NDQwNTU5ZmM3YzViZWRjMDQyMGY5
|
10
|
+
NDZlNDYyMTdmNTU3YmYwYWI0NGNhYjgyMGExZDcxZDNhZjA0NTljNGMxMmJm
|
11
|
+
MzkyM2FkNGYyZjJlN2FlMGJiZjZlNDJlMmFlNWM2OTQ0Mjk2YmY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MWQ2ZjMyYjQxMDMyNmYwMWUzOTRkZDJhMjVhMDQ0YmUzNTY0NmZiMTA1MzZi
|
14
|
+
ZGQxM2U0NWQ5NjQyYmExYzAyOTlhY2IyYTBlOTYxZDQ0ZDNiODM5ZWVmNDZj
|
15
|
+
ZGZkMzJlZjRlYmI2NDZlYzRkZDdhOWNmZmJlOWM1NTA5ZDI2ZTM=
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# v0.1.0 2014-05-23
|
2
|
+
|
3
|
+
## better api naming and conventions
|
4
|
+
|
5
|
+
* removes the Hash as last parameters, introducing a well established api
|
6
|
+
|
7
|
+
# v0.0.1 2014-05-02
|
8
|
+
|
9
|
+
## first public release :heartpulse:
|
10
|
+
|
11
|
+
* module `Hashing` and class methods `hasherize` and `loading`
|
12
|
+
* class (module) `Hasherize` to sugar the `ivars` declaration
|
13
|
+
* nested `Hashing` for collections
|
data/README.md
CHANGED
@@ -6,9 +6,9 @@ method. Also gives you a `YourClass::from_hash` to reconstruct the instances.
|
|
6
6
|
|
7
7
|
## Status
|
8
8
|
[](http://badge.fury.io/rb/hashing)
|
9
|
-
[](http://travis-ci.org/ricardovaleriano/hashing?branch=master)
|
10
10
|
[](https://codeclimate.com/github/ricardovaleriano/hashing)
|
11
|
-
[](http://inch-pages.github.io/github/ricardovaleriano/hashing)
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -24,6 +24,12 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
$ gem install hashing
|
26
26
|
|
27
|
+
## Contact
|
28
|
+
|
29
|
+
* API Doc: http://rdoc.info/gems/hashing
|
30
|
+
* Bugs, issues and feature requests: https://github.com/ricardovaleriano/hashing/issues
|
31
|
+
* Support: http://stackoverflow.com/questions/tagged/hashing-ruby
|
32
|
+
|
27
33
|
## Usage
|
28
34
|
|
29
35
|
Given a `File` class like this:
|
@@ -82,11 +88,8 @@ valid `Hash` like the one created by a `#to_h` call:
|
|
82
88
|
```ruby
|
83
89
|
class File
|
84
90
|
include Hashing
|
85
|
-
hasherize
|
86
|
-
|
87
|
-
loading ->(hash) {
|
88
|
-
new hash[:path], hash[:commit], hash[:content]
|
89
|
-
}
|
91
|
+
hasherize(:path, :commit, :content).
|
92
|
+
loading ->(hash) { new hash[:path], hash[:commit], hash[:content] }
|
90
93
|
|
91
94
|
# ...
|
92
95
|
end
|
@@ -101,36 +104,58 @@ should call them whatever you need in your programs. But the `::from_hash`
|
|
101
104
|
method will be called by `Hashing` when building your instances from hashes (more
|
102
105
|
about this: [nested hasherizing](#nested-hasherized-objects)).
|
103
106
|
|
104
|
-
|
107
|
+
### Custom "hasherizing" and loading strategies
|
105
108
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
+
Many times you will need apply some computation over the contents of your
|
110
|
+
`ivars` transform them in a primitive that can be stored as a `Hash`.
|
111
|
+
|
112
|
+
Following the `File` class example, maybe you want to store the `@content` as a
|
113
|
+
`Base64` enconded string.
|
114
|
+
|
115
|
+
`Hashing` allows you to specify the strategy of serialization and loading when
|
116
|
+
indicating which `ivars` should be part of the final `Hash`:
|
109
117
|
|
110
118
|
```ruby
|
119
|
+
require 'base64'
|
120
|
+
|
111
121
|
class File
|
112
|
-
include
|
122
|
+
include Hashing
|
123
|
+
|
124
|
+
hasherize :path, :commit
|
113
125
|
|
114
|
-
|
115
|
-
|
116
|
-
|
126
|
+
hasherize(:content).
|
127
|
+
to(->(content) { Base64.encode64 content }).
|
128
|
+
from(->(content_string) { Base64.decode64 content_string }).
|
129
|
+
loading ->(hash) { new hash[:path], hash[:commit], hash[:content] }
|
117
130
|
|
118
131
|
# ...
|
119
132
|
end
|
120
133
|
```
|
121
134
|
|
122
|
-
|
135
|
+
But I will recomend this approach only if your strategy for serialization is
|
136
|
+
more complex than just call a method in an object passing the raw value. If your
|
137
|
+
need is exactly like this, you can just indicate the object and the methods that
|
138
|
+
should be called in what moment:
|
123
139
|
|
124
|
-
|
140
|
+
```ruby
|
141
|
+
require 'base64'
|
125
142
|
|
126
|
-
|
127
|
-
|
143
|
+
class File
|
144
|
+
include Hashing
|
128
145
|
|
129
|
-
|
130
|
-
|
146
|
+
hasherize(:path, :commit).
|
147
|
+
loading ->(hash) { new hash[:path], hash[:commit], hash[:content] }
|
131
148
|
|
132
|
-
|
133
|
-
|
149
|
+
hasherize(:content).using(Base64).to(:encode64).from(:decode64)
|
150
|
+
|
151
|
+
# ...
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
And finally, if the your serialization logic is worth a method on it's own, you
|
156
|
+
can indicate this by passing the method names via symbol to the `:to` and
|
157
|
+
`:from` options. Since those methods don't necessarily make sense as part of
|
158
|
+
your public api, so you can even make then private:
|
134
159
|
|
135
160
|
```ruby
|
136
161
|
require 'base64'
|
@@ -138,15 +163,21 @@ require 'base64'
|
|
138
163
|
class File
|
139
164
|
include Hashing
|
140
165
|
|
141
|
-
hasherize
|
166
|
+
hasherize(:path, :commit).
|
167
|
+
loading ->(hash) { new hash[:path], hash[:commit], hash[:content] }
|
168
|
+
|
169
|
+
hasherize(:content).to(:encode).from(:decode)
|
142
170
|
|
143
|
-
|
144
|
-
to_hash: ->(content) { Base64.encode64 content },
|
145
|
-
from_hash: ->(content_string) { Base64.decode64 content_string }
|
171
|
+
# ...
|
146
172
|
|
147
|
-
|
148
|
-
|
149
|
-
|
173
|
+
private
|
174
|
+
def encode(content)
|
175
|
+
Base64.encode64 content
|
176
|
+
end
|
177
|
+
|
178
|
+
def decode(content)
|
179
|
+
Base64.decode64 content
|
180
|
+
end
|
150
181
|
|
151
182
|
# ...
|
152
183
|
end
|
@@ -161,12 +192,16 @@ multiple `ivars` if it makes sense to your program:
|
|
161
192
|
class File
|
162
193
|
include Hashing
|
163
194
|
|
164
|
-
hasherize
|
165
|
-
|
166
|
-
|
195
|
+
hasherize(:path, :commit).
|
196
|
+
to(->(value) { value.downcase }).
|
197
|
+
from(->(value) { value.upcase })
|
167
198
|
end
|
168
199
|
```
|
169
200
|
|
201
|
+
This will guarantees that the final `Hash` has the `path` and the `commit`
|
202
|
+
values "downcased" when your object is serialized, and "upcased" when the
|
203
|
+
instance is reconstructed.
|
204
|
+
|
170
205
|
#### Nested hasherized objects
|
171
206
|
|
172
207
|
But if your transformations are a little more complicated than a simple `Base64`
|
@@ -206,13 +241,14 @@ end
|
|
206
241
|
|
207
242
|
So in this case, if you wants a file to be `hasherized®` with it's internall
|
208
243
|
`@annotations` preserved, you just indicate this in the `File` class. The
|
209
|
-
example now can be
|
244
|
+
example now can be rewritten as:
|
210
245
|
|
211
246
|
```ruby
|
212
247
|
class File
|
213
248
|
include Hashing
|
214
249
|
|
215
|
-
hasherize :path, :commit
|
250
|
+
hasherize :path, :commit
|
251
|
+
hasherize(:annotations).collection Annotation
|
216
252
|
|
217
253
|
# ...
|
218
254
|
end
|
@@ -221,9 +257,34 @@ end
|
|
221
257
|
Since the `Annotation` class has it's own notion of `#to_h` and `::from_hash`,
|
222
258
|
this is all that `Hashing` needs to build a `File` instances from a valid `Hash`.
|
223
259
|
|
260
|
+
#### Defining `attr_reader` within the `.hasherize` invocation
|
261
|
+
|
262
|
+
If you want to define `readers` for the `ivars` passed to `.hasherize`, you can
|
263
|
+
do this with the option `attr: true` (defaults to `false`).
|
264
|
+
|
265
|
+
So, the following example:
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
class File
|
269
|
+
include Hashing
|
270
|
+
hasherize :path, :commit, :content
|
271
|
+
|
272
|
+
attr_reader :path, :commit, :content
|
273
|
+
end
|
274
|
+
```
|
275
|
+
|
276
|
+
Can be written as:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
class File
|
280
|
+
include Hashing
|
281
|
+
hasherize(:path, :commit, :content).reader true
|
282
|
+
end
|
283
|
+
```
|
284
|
+
|
224
285
|
## Contributing
|
225
286
|
|
226
|
-
This
|
287
|
+
This is a rapid "scratch your own itch" kind of project. It will make me really
|
227
288
|
happy if it can be used used in your software anyhow. If you need something
|
228
289
|
different than what is in it, or can solve us some bugs or add documentation, it
|
229
290
|
will be very well received!
|
data/hashing.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Hashing::VERSION
|
9
9
|
spec.authors = ["Ricardo Valeriano"]
|
10
10
|
spec.email = ["ricardo.valeriano@gmail.com"]
|
11
|
-
spec.summary = %q{Serialize your objects
|
12
|
-
spec.description = %q{
|
11
|
+
spec.summary = %q{Serialize your objects into Hashes}
|
12
|
+
spec.description = %q{Provides an easy way to specify which instances vars of your objects should be used as `key` in a Hash returned by the `#to_h` method. Also gives you a `YourClass::from_hash` to reconstruct the instances.}
|
13
13
|
spec.homepage = "http://github.com/ricardovaleriano/hashing"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
data/lib/hashing.rb
CHANGED
@@ -1,49 +1,40 @@
|
|
1
|
-
require 'hashing/
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'hashing/macros'
|
2
|
+
|
3
|
+
# Inject the public api into the host class, and it's instances.
|
4
|
+
# By "public api" we understand the class methods
|
5
|
+
#
|
6
|
+
# - {Hashing::Macros#hasherize}
|
7
|
+
# - {Hashing::Macros#from_hash}
|
8
|
+
# - {Hashing::Macros#__hasher}
|
9
|
+
#
|
10
|
+
# And the instance method:
|
11
|
+
#
|
12
|
+
# - {Hashing#to_h}
|
13
|
+
#
|
14
|
+
# @since 0.0.1
|
15
|
+
#
|
16
|
+
# @example including Hashing
|
17
|
+
# require 'hashing'
|
18
|
+
#
|
19
|
+
# class File
|
20
|
+
# include Hashing
|
21
|
+
# hasherize :path, :commit
|
22
|
+
#
|
23
|
+
# def initialize(path, commit)
|
24
|
+
# @path, @commit = path, commit
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# When `Hashing` is included, the host class will gain the `.from_hash({})`
|
29
|
+
# method and the `#to_h` instance method.
|
30
|
+
# Another method that will be added is the private class method `.hasherize`
|
31
|
+
# will be added so you can indicate what ivars do you want in your sarialized
|
32
|
+
# objects.
|
5
33
|
module Hashing
|
6
|
-
# Inform the user about an attempt to create an instance, using a `Hash` with
|
7
|
-
# keys that does not correspond to the mape made using `.hasherize`
|
8
|
-
class UnconfiguredIvar < StandardError
|
9
|
-
def initialize(ivar_name, class_name)
|
10
|
-
super "The Hash has a :#{ivar_name} key, "+
|
11
|
-
"but no @#{ivar_name} was configured in #{class_name}"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
# Inject the public api into the client class.
|
16
|
-
#
|
17
|
-
# @since 0.0.1
|
18
|
-
#
|
19
|
-
# @example including Hashing
|
20
|
-
# require 'hashing'
|
21
|
-
#
|
22
|
-
# class File
|
23
|
-
# include Hashing
|
24
|
-
# hasherize :path, :commit
|
25
|
-
#
|
26
|
-
# def initialize(path, commit)
|
27
|
-
# @path, @commit = path, commit
|
28
|
-
# end
|
29
|
-
# end
|
30
|
-
#
|
31
|
-
# When `Hashing` is included, the host class will gain the `.from_hash({})`
|
32
|
-
# method and the `#to_h` instance method.
|
33
|
-
# Another method that will be added is the private class method `.hasherize`
|
34
|
-
# will be added so you can indicate what ivars do you want in your sarialized
|
35
|
-
# objects.
|
36
34
|
def self.included(client_class)
|
37
|
-
client_class.extend
|
35
|
+
client_class.extend Macros
|
38
36
|
end
|
39
37
|
|
40
|
-
def meta_data(name, value)
|
41
|
-
@_hashing_meta_data ||= { __hashing__: { types: {} } }
|
42
|
-
@_hashing_meta_data[:__hashing__][:types][name] = value
|
43
|
-
@_hashing_meta_data
|
44
|
-
end
|
45
|
-
|
46
|
-
#
|
47
38
|
# The `Hash` returned by `#to_h` will be formed by keys based on the ivars
|
48
39
|
# names passed to `hasherize` method.
|
49
40
|
#
|
@@ -53,118 +44,6 @@ module Hashing
|
|
53
44
|
# file.to_h
|
54
45
|
# # => { path: 'README.md', commit: 'cfe9aacbc02528b' }
|
55
46
|
def to_h
|
56
|
-
|
57
|
-
value = instance_variable_get "@#{ivar}"
|
58
|
-
if value.respond_to? :map
|
59
|
-
meta_data ivar.to_sym, value.first.class
|
60
|
-
end
|
61
|
-
[ivar.to_sym, ivar.to_h(value)]
|
62
|
-
}
|
63
|
-
Hash[hash_pairs].merge(@_hashing_meta_data || {})
|
64
|
-
end
|
65
|
-
|
66
|
-
# Define the class methods that should be available in a 'hasherized ®' class
|
67
|
-
# (a class that include `Hashing`).
|
68
|
-
module Hasherizer
|
69
|
-
# Configures which instance variables will be used to compose the `Hash`
|
70
|
-
# generated by `#to_h`
|
71
|
-
#
|
72
|
-
# @api
|
73
|
-
# @param ivars_and_options [*arguments]
|
74
|
-
def hasherize(*ivars_and_options)
|
75
|
-
@ivars ||= []
|
76
|
-
ivars = extract_ivars ivars_and_options
|
77
|
-
to_strategy = strategy_for_key :to_hash, ivars_and_options
|
78
|
-
from_strategy = strategy_for_key :from_hash, ivars_and_options
|
79
|
-
@ivars += ivars.map { |ivar| Ivar.new ivar, to_strategy, from_strategy }
|
80
|
-
end
|
81
|
-
|
82
|
-
# Configures the strategy to (re)create an instance of the 'hasherized ®'
|
83
|
-
# class based on a `Hash` instance. This strategy will be used by the
|
84
|
-
# `.from_hash({})` method.
|
85
|
-
#
|
86
|
-
# This configuration is optional, if it's not called, then the strategy will
|
87
|
-
# be just repassing the `Hash` to the initializer.
|
88
|
-
#
|
89
|
-
# @param strategy [#call]
|
90
|
-
# @return void
|
91
|
-
def loading(strategy)
|
92
|
-
@strategy = strategy
|
93
|
-
end
|
94
|
-
|
95
|
-
# Provides the default strategy for recreate objects from hashes (which is
|
96
|
-
# just call .new passing the `Hash` as is.
|
97
|
-
#
|
98
|
-
# @return the result of calling the strategy
|
99
|
-
def strategy
|
100
|
-
@strategy || ->(h) { new h }
|
101
|
-
end
|
102
|
-
|
103
|
-
# those methods are private but part of the class api (macros).
|
104
|
-
# #TODO: there is a way to document the 'macros' for a class in YARD?
|
105
|
-
private :hasherize, :loading, :strategy
|
106
|
-
|
107
|
-
# provides access to the current configuration on what `ivars` should be
|
108
|
-
# used to generate a `Hash` representation of instances of the client class.
|
109
|
-
#
|
110
|
-
# @return [Array] ivars that should be included in the final Hash
|
111
|
-
def ivars
|
112
|
-
@ivars ||= []
|
113
|
-
end
|
114
|
-
|
115
|
-
# Receives a `Hash` and uses the strategy configured by `.loading` to
|
116
|
-
# (re)create an instance of the 'hasherized ®' class.
|
117
|
-
#
|
118
|
-
# @param pairs [Hash] in a valid form defined by `.hasherize`
|
119
|
-
# @return new object
|
120
|
-
def from_hash(pairs)
|
121
|
-
metadata = pairs.delete(:__hashing__) || {}
|
122
|
-
hash_to_load = pairs.map do |ivar_name, value|
|
123
|
-
ivar = ivar_by_name ivar_name.to_sym
|
124
|
-
[ivar.to_sym, ivar.from_hash(value, metadata)]
|
125
|
-
end
|
126
|
-
strategy.call Hash[hash_to_load]
|
127
|
-
end
|
128
|
-
|
129
|
-
private
|
130
|
-
# Cleanup the arguments received by `.hasherize` so only the `ivar` names
|
131
|
-
# are returned. This is necessarey since the `.hasherize` can receive a
|
132
|
-
# `Hash` with strategies `:from_hash` and `:to_hash` as the last argument.
|
133
|
-
#
|
134
|
-
# @param ivars_and_options [Array] arguments received by `.serialize`
|
135
|
-
# @return [Array[:Symbol]] ivar names that should be used in the `Hash` serialization
|
136
|
-
def extract_ivars(ivars_and_options)
|
137
|
-
ivars = ivars_and_options.dup
|
138
|
-
if ivars.last.is_a? Hash
|
139
|
-
ivars.pop
|
140
|
-
end
|
141
|
-
ivars
|
142
|
-
end
|
143
|
-
|
144
|
-
# Fetches the strategy to serialize or deserialize (defined by the first
|
145
|
-
# param `strategy`) the `ivars` passed as second parameter
|
146
|
-
#
|
147
|
-
# @param hash_key [Symbol] (:to_hash || :from_hash) type of strategy to fetch
|
148
|
-
# @param ivars_and_options [*args | *args, Hash]
|
149
|
-
# @return [#call] strategy to be used
|
150
|
-
def strategy_for_key(strategy, ivars_and_options)
|
151
|
-
default_strategy_just_returns = ->(ivar_value) { ivar_value }
|
152
|
-
strategies = { strategy => default_strategy_just_returns }
|
153
|
-
strategies = ivars_and_options.last if ivars_and_options.last.is_a? Hash
|
154
|
-
strategies[strategy]
|
155
|
-
end
|
156
|
-
|
157
|
-
# Search an `ivar` by it's name in the class ivars collection
|
158
|
-
#
|
159
|
-
# #TODO: Can be enhanced since now the ivars doesn't have a sense of
|
160
|
-
# equality.
|
161
|
-
#
|
162
|
-
# @param ivar_name [Symbol] `ivar` name
|
163
|
-
# @return [Ivar]
|
164
|
-
def ivar_by_name(ivar_name)
|
165
|
-
ivar = ivars.select { |ivar| ivar.to_sym == ivar_name }.first
|
166
|
-
raise UnconfiguredIvar.new ivar_name, name unless ivar
|
167
|
-
ivar
|
168
|
-
end
|
47
|
+
self.class.__hasher.to_h self
|
169
48
|
end
|
170
49
|
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require "hashing/ivar"
|
2
|
+
require "hashing/unconfigured_ivar_error"
|
3
|
+
|
4
|
+
module Hashing
|
5
|
+
# This class contains the interface "exported" by a call to the `.hasherize`
|
6
|
+
# method in any class that includes the module [Hashing].
|
7
|
+
class Hasher
|
8
|
+
attr_reader :ivars
|
9
|
+
|
10
|
+
# @param host_class [Class] the class whose ivars will be used to serialize
|
11
|
+
# @param ivars [Array<Symbol>] the ivars that will be serialized
|
12
|
+
def initialize(host_class, ivars = nil)
|
13
|
+
@host_class = host_class
|
14
|
+
add ivars
|
15
|
+
end
|
16
|
+
|
17
|
+
# --- api {{{
|
18
|
+
|
19
|
+
# Provides the api to configure the strategy to serialize an instance of a
|
20
|
+
# class that includes {Hashing} into a hash object.
|
21
|
+
#
|
22
|
+
# @since 0.1.0
|
23
|
+
#
|
24
|
+
# @param strategy [#call] the logic to convert some value to a [Hash]
|
25
|
+
# @return [Hasher]
|
26
|
+
def to(strategy)
|
27
|
+
logic_for :to_h, strategy
|
28
|
+
end
|
29
|
+
|
30
|
+
# Provides the api to configure the logic to serialize an instance of a
|
31
|
+
# class that includes {Hashing} into a hash object.
|
32
|
+
#
|
33
|
+
# @since 0.1.0
|
34
|
+
#
|
35
|
+
# @param strategy [#call] the logic to convert some value to a [Hash]
|
36
|
+
# @return [Hasher]
|
37
|
+
def from(strategy)
|
38
|
+
logic_for :from_hash, strategy
|
39
|
+
end
|
40
|
+
|
41
|
+
# Provides the api to create attr_readers in the host class for the current
|
42
|
+
# configured ivars (those passed to {#add}).
|
43
|
+
#
|
44
|
+
# @since 0.1.0
|
45
|
+
#
|
46
|
+
# @example: creating attr_reader from :path and :commit
|
47
|
+
# hasherize(:path, :commit).reader true
|
48
|
+
#
|
49
|
+
# The example above will create accessors for :path and :commit
|
50
|
+
#
|
51
|
+
# @return [Hasher]
|
52
|
+
def reader(should_create_attr_reader = true)
|
53
|
+
if should_create_attr_reader
|
54
|
+
@current_ivars.each { |ivar| @host_class.send :attr_reader, ivar.to_sym }
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# Provides the api to indicate an object with the serialization and
|
60
|
+
# unserialization logic.
|
61
|
+
#
|
62
|
+
# @example: using Base64 to serialize and unserialize some ivar:
|
63
|
+
# hahserize(:content).unsing(Base64).to(:encode64).from(:decode64)
|
64
|
+
#
|
65
|
+
# Note: any object can be passe to {#using}, and the methods in these object
|
66
|
+
# passed as arguments to {#to} and {#from} need to be public.
|
67
|
+
#
|
68
|
+
# @since 0.1.0
|
69
|
+
#
|
70
|
+
# @return [Hasher]
|
71
|
+
def using(serializator)
|
72
|
+
@serializator = serializator
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Provides the api to say if an ivar is a collection (#map) of instances of
|
77
|
+
# a class that includes {Hashing}.
|
78
|
+
# The idea here is just to be able to restore nested {Hashing} objects.
|
79
|
+
#
|
80
|
+
# @since 0.1.0
|
81
|
+
#
|
82
|
+
# @return [Hasher]
|
83
|
+
def collection(type)
|
84
|
+
# replace current ivar for it's collection version...
|
85
|
+
collections = @current_ivars.map { |ivar| IvarCollection.new ivar, type }
|
86
|
+
@current_ivars.each { |ivar| @ivars.delete ivar }
|
87
|
+
@ivars += collections
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Configures the strategy to (re)create an instance of the 'hasherized ®'
|
92
|
+
# class based on a `Hash` instance. This strategy will be used by the
|
93
|
+
# `.from_hash({})` method.
|
94
|
+
#
|
95
|
+
# This configuration is optional, if it's not called, then the strategy will
|
96
|
+
# be just repassing the `Hash` to the initializer.
|
97
|
+
#
|
98
|
+
# @param strategy [#call]
|
99
|
+
# @return [Hasher] (fluent interface)
|
100
|
+
def loading(strategy)
|
101
|
+
@loading = strategy
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# }}} api
|
106
|
+
|
107
|
+
# This method can be called to define which ivars will be used as keys in
|
108
|
+
# the final hash.
|
109
|
+
# Ivar names passed as arguments to this method will be used to create
|
110
|
+
# instances of [Ivar]. Those have the real logic of serialization and
|
111
|
+
# unserialization of an [Ivar].
|
112
|
+
# This method also keeps a reference to the last ivars passed as parameter.
|
113
|
+
# This is necessary to allow us to do the following call:
|
114
|
+
#
|
115
|
+
# hasherize(:ivar1, :ivar2).to(->(value){})
|
116
|
+
#
|
117
|
+
# The previous call will configure the `:to` strategy for `:ivar1` and
|
118
|
+
# `:ivar2`.
|
119
|
+
#
|
120
|
+
# @param ivar_names [Array<Symbol>] ivars to be used to hasherize the instance
|
121
|
+
# @return [Hasher]
|
122
|
+
def add(ivar_names)
|
123
|
+
ivar_names = Array(ivar_names)
|
124
|
+
@current_ivars = ivar_names.map { |ivar_name| Ivar.new ivar_name }
|
125
|
+
@ivars ||= []
|
126
|
+
@ivars += @current_ivars
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
# Provides the logic to transform an instance of a {Hashing} class into an
|
131
|
+
# hash object.
|
132
|
+
#
|
133
|
+
# @return [Hash] a new hash in which keys are the ivar names and values the string value for those ivars.
|
134
|
+
def to_h(instance)
|
135
|
+
pairs = @ivars.map { |ivar|
|
136
|
+
ivar_value = instance.instance_variable_get :"@#{ivar.to_sym}"
|
137
|
+
[ivar.to_sym, ivar.to_h(ivar_value)]
|
138
|
+
}
|
139
|
+
Hash[pairs]
|
140
|
+
end
|
141
|
+
|
142
|
+
# This method will be called to reconstruct an instance of the type which
|
143
|
+
# includes {Hashing}.
|
144
|
+
# The `loader` in which `#call` will be called here is the one passed to the
|
145
|
+
# {Hashing::Hasher#loading}. If none was passed, this method will just call
|
146
|
+
# `.new` in the host class passing the [Hash] as argument.
|
147
|
+
#
|
148
|
+
# @param [Hash] hash serialized by a call to {Hashing::Hasher#to_h}
|
149
|
+
def load(hash)
|
150
|
+
check_for_unconfigured_keys hash
|
151
|
+
loader = @loading || ->(serialized) { @host_class.new serialized }
|
152
|
+
loader.call process_hash_values hash
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
# @since 0.1.0
|
158
|
+
#
|
159
|
+
# @param way [Symbol] :from_hash or :to_h strategy
|
160
|
+
# @param strategy [#call] the strategy to convert some value to or from a [Hash]
|
161
|
+
# @return [Hasher]
|
162
|
+
def logic_for(way, strategy)
|
163
|
+
if @serializator
|
164
|
+
strategy = @serializator.method strategy
|
165
|
+
end
|
166
|
+
@current_ivars.each { |ivar| ivar.send :"#{way}=", strategy }
|
167
|
+
self
|
168
|
+
end
|
169
|
+
|
170
|
+
def check_for_unconfigured_keys(hash)
|
171
|
+
unrecognized_keys = hash.keys - @ivars.map(&:to_sym)
|
172
|
+
if unrecognized_keys.count > 0
|
173
|
+
raise Hashing::UnconfiguredIvarError.new unrecognized_keys, @host_class
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def process_hash_values(hash)
|
178
|
+
transformed_hash = @ivars.map { |ivar|
|
179
|
+
[ivar.to_sym, ivar.from_hash(hash[ivar.to_sym])]
|
180
|
+
}
|
181
|
+
Hash[transformed_hash]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/lib/hashing/ivar.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
+
require_relative 'ivar_collection'
|
2
|
+
|
1
3
|
module Hashing
|
2
4
|
# Represents each one of the instance variables in a class that should be used
|
3
5
|
# to represent an object in a `Hash` form (serialization).
|
4
6
|
class Ivar
|
5
7
|
attr_reader :name
|
8
|
+
attr_writer :to_h, :from_hash
|
6
9
|
|
7
10
|
# Configure the name of an `ivar` and the 'callable' objects thath will be
|
8
11
|
# used to prepare the `ivar` value for serialization, and to load the object
|
@@ -28,10 +31,6 @@ module Hashing
|
|
28
31
|
# @return the value that will be stored in the `Hash`
|
29
32
|
def to_h(value)
|
30
33
|
return value unless @to_h
|
31
|
-
|
32
|
-
if value.respond_to? :map
|
33
|
-
value = hasherize value
|
34
|
-
end
|
35
34
|
@to_h.call value
|
36
35
|
end
|
37
36
|
|
@@ -56,39 +55,5 @@ module Hashing
|
|
56
55
|
def to_s
|
57
56
|
@name.to_s
|
58
57
|
end
|
59
|
-
|
60
|
-
private
|
61
|
-
# Is an object descendent of {Hashing}?
|
62
|
-
#
|
63
|
-
# @param value [Object]
|
64
|
-
def hashing?(value)
|
65
|
-
value.class.ancestors.include? Hashing
|
66
|
-
end
|
67
|
-
|
68
|
-
# Hasherize a value when it has {Hashing} in it's method lookup or return
|
69
|
-
# the value. Util when a collection of {Hashing} objects is given and need
|
70
|
-
# to be "hasherized"
|
71
|
-
#
|
72
|
-
# @param value [#map] the value to be verified as a {Hashing} (or not)
|
73
|
-
# @return [#map] collection of hashes
|
74
|
-
def hasherize(collection)
|
75
|
-
collection.map { |item| hashing?(item) ? item.to_h : item }
|
76
|
-
end
|
77
|
-
|
78
|
-
# If a collection of {Hashing} objects is given, we have to reconstruct all
|
79
|
-
# collections members before while reconstructing the collection itself.
|
80
|
-
# This method provides that
|
81
|
-
#
|
82
|
-
# TODO: (need?) recursion to reconstruct collections of collections
|
83
|
-
#
|
84
|
-
# @param value [#map] the collection of {Hashing} objects
|
85
|
-
# @param metadata [Hash] containing serialized data about the original object
|
86
|
-
# @return [#map] collection of {Hashing} instances
|
87
|
-
def normalize(collection, metadata)
|
88
|
-
elements_class = metadata.fetch(:types, {}).fetch(@name, nil)
|
89
|
-
return collection unless elements_class.respond_to? :from_hash
|
90
|
-
|
91
|
-
collection.map { |element| elements_class.from_hash element }
|
92
|
-
end
|
93
58
|
end
|
94
59
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Hashing
|
2
|
+
# Represents an ivar in a class that includes {Hashing} which contains a
|
3
|
+
# collection of other {Hashing} instances.
|
4
|
+
class IvarCollection
|
5
|
+
extend Forwardable
|
6
|
+
def_delegators :@holder, :to_sym, :to_s, :name, :to_h=, :from_hash=
|
7
|
+
|
8
|
+
def initialize(collection_holder_ivar, type)
|
9
|
+
@holder = collection_holder_ivar
|
10
|
+
@type = type
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h(value)
|
14
|
+
@holder.to_h value.map { |item|
|
15
|
+
item.respond_to?(:to_h) ? item.to_h : item
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def from_hash(value)
|
20
|
+
@holder.from_hash value.map { |item| @type.from_hash item }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "hashing/hasher"
|
2
|
+
|
3
|
+
module Hashing
|
4
|
+
# Define the class methods that should be available in a 'hasherized ®' class
|
5
|
+
# (a class that include {Hashing}).
|
6
|
+
module Macros
|
7
|
+
# Configures which instance variables will be used to compose the `Hash`
|
8
|
+
# generated by `#to_h`
|
9
|
+
#
|
10
|
+
# @api
|
11
|
+
# @param ivars [Array<Symbol>]
|
12
|
+
def hasherize(*ivars)
|
13
|
+
__hasher.add ivars
|
14
|
+
end
|
15
|
+
|
16
|
+
# those methods are private but part of the class api (macros).
|
17
|
+
# #TODO: there is a way to document the 'macros' for a class in YARD?
|
18
|
+
private :hasherize
|
19
|
+
|
20
|
+
# Receives a `Hash` and uses the strategy configured by `.loading` to
|
21
|
+
# (re)create an instance of the 'hasherized ®' class.
|
22
|
+
#
|
23
|
+
# @param hash [Hash] in a valid form defined by `.hasherize`
|
24
|
+
# @return new object
|
25
|
+
def from_hash(hash)
|
26
|
+
__hasher.load hash
|
27
|
+
end
|
28
|
+
|
29
|
+
# Provides the entry point to the object that has the actual logic of
|
30
|
+
# serialization/unserialization for {Hashing} instances.
|
31
|
+
# The ideia here is to not polute the host class with a bunch of methods
|
32
|
+
# included by the {Hashing}. Instead, we just inject the api method
|
33
|
+
# {#hasherize}, {#from_hash} and the internally used {#__hasher} method.
|
34
|
+
def __hasher
|
35
|
+
@__hasher ||= Hasher.new self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Hashing
|
2
|
+
# Inform the user about an attempt to create an instance, using a `Hash` with
|
3
|
+
# keys that does not correspond to the mape made using `.hasherize`
|
4
|
+
class UnconfiguredIvarError < StandardError
|
5
|
+
def initialize(ivar_names, class_name)
|
6
|
+
super [
|
7
|
+
"The hash passed to #{class_name}.from_hash has the following ",
|
8
|
+
"keys that aren't configured by the .hasherize method: ",
|
9
|
+
"#{ivar_names.join ","}."
|
10
|
+
].join
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/hashing/version.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
describe Hashing::Hasher do
|
2
|
+
before do
|
3
|
+
class WowSuchClass
|
4
|
+
include Hashing
|
5
|
+
def initialize(so_ivar = nil)
|
6
|
+
@so_ivar = so_ivar
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:hasher) { Hashing::Hasher.new WowSuchClass }
|
12
|
+
|
13
|
+
describe "#attr" do
|
14
|
+
it "with true, provides attr_readers for the current ivars" do
|
15
|
+
hasher.add(:very_ivar).reader true
|
16
|
+
WowSuchClass.new.respond_to?(:very_ivar).must_be :==, true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#using" do
|
21
|
+
require 'base64'
|
22
|
+
|
23
|
+
it "stores an object with methods to transform the current ivar" do
|
24
|
+
class WowSuchClass
|
25
|
+
hasherize(:so_ivar).reader(true).
|
26
|
+
using(Base64).to(:encode64).from(:decode64).
|
27
|
+
loading ->(hash) { new hash[:so_ivar] }
|
28
|
+
end
|
29
|
+
|
30
|
+
wow = WowSuchClass.new "borba"
|
31
|
+
wow.to_h.must_be :==, {so_ivar: Base64.encode64("borba")}
|
32
|
+
WowSuchClass.from_hash({so_ivar: Base64.encode64("borba")}).so_ivar.must_be :==, 'borba'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/test/hashing/nested_test.rb
CHANGED
@@ -1,29 +1,31 @@
|
|
1
1
|
describe Hashing do
|
2
2
|
describe 'when one of the ivars is an `Array` of hasherized objects' do
|
3
|
-
class HashingCollectionOwner
|
4
|
-
attr_reader :file, :commit, :annotations
|
5
|
-
include Hasherize.new :file, :commit, :annotations
|
6
|
-
|
7
|
-
loading ->(hash) { new hash[:file], hash[:commit], hash[:annotations] }
|
8
|
-
|
9
|
-
def initialize(file, commit, annotations)
|
10
|
-
@file, @commit, @annotations = file, commit, annotations
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
3
|
class HashingCollectionMember
|
15
4
|
attr_reader :annotation
|
16
|
-
include
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
5
|
+
include Hashing
|
6
|
+
hasherize(:annotation).
|
7
|
+
to(->(value) { "--#{value}" }).
|
8
|
+
from(->(value) { "#{value}--" }).
|
9
|
+
loading(->(hash) { new hash[:annotation] })
|
21
10
|
|
22
11
|
def initialize(annotation)
|
23
12
|
@annotation = annotation
|
24
13
|
end
|
25
14
|
end
|
26
15
|
|
16
|
+
class HashingCollectionOwner
|
17
|
+
attr_reader :file, :commit, :annotations
|
18
|
+
include Hashing
|
19
|
+
hasherize :file, :commit
|
20
|
+
hasherize(:annotations).
|
21
|
+
collection(HashingCollectionMember).
|
22
|
+
loading ->(hash) { new hash[:file], hash[:commit], hash[:annotations] }
|
23
|
+
|
24
|
+
def initialize(file, commit, annotations)
|
25
|
+
@file, @commit, @annotations = file, commit, annotations
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
27
29
|
describe '#to_h' do
|
28
30
|
it 'calls #to_h for each array item when hashifying the object' do
|
29
31
|
owner = HashingCollectionOwner.new 'README.md', 'cfe9aacbc02528b', [
|
@@ -36,17 +38,24 @@ describe Hashing do
|
|
36
38
|
annotations: [
|
37
39
|
{ annotation: '--first' },
|
38
40
|
{ annotation: '--second' },
|
39
|
-
]
|
40
|
-
__hashing__: {
|
41
|
-
types: {
|
42
|
-
annotations: HashingCollectionMember
|
43
|
-
}
|
44
|
-
}
|
41
|
+
]
|
45
42
|
}
|
46
|
-
p owner.to_h
|
47
43
|
end
|
48
44
|
|
49
|
-
it "don't call the #to_h on inner object that don't include `Hashing`"
|
45
|
+
it "don't call the #to_h on inner object that don't include `Hashing`" do
|
46
|
+
owner = HashingCollectionOwner.new 'README.md', 'cfe9aacbc02528b', [
|
47
|
+
HashingCollectionMember.new('first'),
|
48
|
+
"xpto",
|
49
|
+
]
|
50
|
+
owner.to_h.must_be :==, {
|
51
|
+
file: 'README.md',
|
52
|
+
commit: 'cfe9aacbc02528b',
|
53
|
+
annotations: [
|
54
|
+
{ annotation: '--first' },
|
55
|
+
'xpto',
|
56
|
+
]
|
57
|
+
}
|
58
|
+
end
|
50
59
|
end
|
51
60
|
|
52
61
|
describe '#from_h' do
|
@@ -57,16 +66,11 @@ describe Hashing do
|
|
57
66
|
annotations: [
|
58
67
|
{annotation: "first"},
|
59
68
|
{annotation: "second"}
|
60
|
-
]
|
61
|
-
__hashing__: {
|
62
|
-
types: {
|
63
|
-
annotations: HashingCollectionMember
|
64
|
-
}
|
65
|
-
}
|
69
|
+
]
|
66
70
|
}
|
67
71
|
end
|
68
72
|
|
69
|
-
it 'calls #
|
73
|
+
it 'calls #from_hash for each element on an yaml array that contains hasherized objects' do
|
70
74
|
owner = HashingCollectionOwner.from_hash hash_values
|
71
75
|
owner.annotations.first.annotation.must_be :==, 'first--'
|
72
76
|
owner.annotations.last.annotation.must_be :==, 'second--'
|
data/test/hashing_test.rb
CHANGED
@@ -3,14 +3,9 @@ require "base64"
|
|
3
3
|
describe Hashing do
|
4
4
|
describe 'interface' do
|
5
5
|
let(:hasherized) do
|
6
|
-
# if in doubt about the absense of assertions in this test, please
|
7
|
-
# refer to:
|
8
|
-
# - http://blog.zenspider.com/blog/2012/01/assert_nothing_tested.html
|
9
|
-
# and https://github.com/seattlerb/minitest/issues/159
|
10
6
|
Class.new do
|
11
7
|
include Hashing
|
12
|
-
hasherize
|
13
|
-
loading ->() {}
|
8
|
+
hasherize(:ivar)
|
14
9
|
end
|
15
10
|
end
|
16
11
|
|
@@ -24,48 +19,61 @@ describe Hashing do
|
|
24
19
|
end# interface
|
25
20
|
|
26
21
|
describe 'Recreating a `hasherized` class instance' do
|
27
|
-
|
28
|
-
Class.new do
|
29
|
-
attr_reader :h
|
22
|
+
describe '.loading' do
|
30
23
|
|
31
|
-
|
32
|
-
|
24
|
+
before do
|
25
|
+
@original_stdout, $stdout = $stdout, StringIO.new
|
26
|
+
end
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
end
|
28
|
+
after do
|
29
|
+
$stdout = @original_stdout
|
37
30
|
end
|
38
|
-
end
|
39
31
|
|
40
|
-
describe '.loading' do
|
41
32
|
it 'uses (`#call`) the strategy defined by `.loading`' do
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
Class.new do
|
34
|
+
include Hashing
|
35
|
+
hasherize(:omg).loading ->(hash) { $stdout.write hash }
|
36
|
+
end.from_hash omg: 'lol'
|
37
|
+
$stdout.string.must_be :==, '{:omg=>"lol"}'
|
47
38
|
end
|
48
39
|
end
|
49
40
|
|
50
41
|
describe '#from_hash' do
|
51
|
-
it '
|
52
|
-
new_object =
|
53
|
-
|
42
|
+
it 'default strategy is just call `.new` passing the hash' do
|
43
|
+
new_object = Class.new do
|
44
|
+
attr_reader :h
|
45
|
+
|
46
|
+
include Hashing
|
47
|
+
hasherize :omg
|
48
|
+
|
49
|
+
def initialize(h)
|
50
|
+
@h = h
|
51
|
+
end
|
52
|
+
end.from_hash omg: 'lol'
|
53
|
+
|
54
|
+
new_object.h.must_be :==, { omg: 'lol' }
|
54
55
|
end
|
55
56
|
|
56
57
|
it 'give an informative message in case the Hash is malformed' do
|
57
|
-
OmgLolBBQ =
|
58
|
+
OmgLolBBQ = Class.new do
|
59
|
+
include Hashing
|
60
|
+
hasherize(:h).loading ->(hash) { $stdout.write hash }
|
61
|
+
end
|
62
|
+
|
58
63
|
message = nil
|
59
|
-
|
64
|
+
|
65
|
+
-> {
|
60
66
|
begin
|
61
67
|
OmgLolBBQ.from_hash xpto: 'JUST NO!'
|
62
68
|
rescue => e
|
63
69
|
message = e.message
|
64
70
|
raise e
|
65
71
|
end
|
66
|
-
}.must_raise Hashing::
|
67
|
-
|
68
|
-
|
72
|
+
}.must_raise Hashing::UnconfiguredIvarError
|
73
|
+
|
74
|
+
message.must_be :==, 'The hash passed to OmgLolBBQ.from_hash has the '+
|
75
|
+
'following keys that aren\'t configured by the .hasherize method: '+
|
76
|
+
'xpto.'
|
69
77
|
end
|
70
78
|
end
|
71
79
|
end
|
@@ -78,10 +86,10 @@ describe Hashing do
|
|
78
86
|
attr_reader :content
|
79
87
|
|
80
88
|
hasherize :file, :commit
|
81
|
-
hasherize
|
82
|
-
|
83
|
-
|
84
|
-
|
89
|
+
hasherize(:content).
|
90
|
+
to(->(content) { Base64.encode64(content) }).
|
91
|
+
from(->(hash_string) { Base64.decode64(hash_string) }).
|
92
|
+
loading(->(hash) { new hash[:file], hash[:commit], hash[:content] })
|
85
93
|
|
86
94
|
def initialize(file, commit, content)
|
87
95
|
@file, @commit, @content = file, commit, content
|
@@ -106,8 +114,8 @@ describe Hashing do
|
|
106
114
|
let(:hasherized) do
|
107
115
|
Class.new do
|
108
116
|
include Hashing
|
109
|
-
hasherize
|
110
|
-
|
117
|
+
hasherize(:file, :commit).
|
118
|
+
loading ->(hash) { new hash[:file], hash[:commit] }
|
111
119
|
|
112
120
|
def initialize(file, commit)
|
113
121
|
@file, @commit = file, commit
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashing
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ricardo Valeriano
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -28,14 +28,14 @@ dependencies:
|
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - '>='
|
31
|
+
- - ! '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - '>='
|
38
|
+
- - ! '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
@@ -52,9 +52,9 @@ dependencies:
|
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.0'
|
55
|
-
description:
|
56
|
-
should be used as `key
|
57
|
-
|
55
|
+
description: Provides an easy way to specify which instances vars of your objects
|
56
|
+
should be used as `key` in a Hash returned by the `#to_h` method. Also gives you
|
57
|
+
a `YourClass::from_hash` to reconstruct the instances.
|
58
58
|
email:
|
59
59
|
- ricardo.valeriano@gmail.com
|
60
60
|
executables: []
|
@@ -63,17 +63,21 @@ extra_rdoc_files: []
|
|
63
63
|
files:
|
64
64
|
- .gitignore
|
65
65
|
- .travis.yml
|
66
|
+
- CHANGELOG.md
|
66
67
|
- Gemfile
|
67
68
|
- LICENSE.txt
|
68
69
|
- README.md
|
69
70
|
- Rakefile
|
70
71
|
- hashing.gemspec
|
71
|
-
- lib/hasherize.rb
|
72
72
|
- lib/hashing.rb
|
73
|
+
- lib/hashing/hasher.rb
|
73
74
|
- lib/hashing/ivar.rb
|
75
|
+
- lib/hashing/ivar_collection.rb
|
76
|
+
- lib/hashing/macros.rb
|
77
|
+
- lib/hashing/unconfigured_ivar_error.rb
|
74
78
|
- lib/hashing/version.rb
|
75
79
|
- tasks/test.rake
|
76
|
-
- test/
|
80
|
+
- test/hashing/hasher_test.rb
|
77
81
|
- test/hashing/ivar_test.rb
|
78
82
|
- test/hashing/nested_test.rb
|
79
83
|
- test/hashing_test.rb
|
@@ -88,23 +92,24 @@ require_paths:
|
|
88
92
|
- lib
|
89
93
|
required_ruby_version: !ruby/object:Gem::Requirement
|
90
94
|
requirements:
|
91
|
-
- - '>='
|
95
|
+
- - ! '>='
|
92
96
|
- !ruby/object:Gem::Version
|
93
97
|
version: '0'
|
94
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
99
|
requirements:
|
96
|
-
- - '>='
|
100
|
+
- - ! '>='
|
97
101
|
- !ruby/object:Gem::Version
|
98
102
|
version: '0'
|
99
103
|
requirements: []
|
100
104
|
rubyforge_project:
|
101
|
-
rubygems_version: 2.1.
|
105
|
+
rubygems_version: 2.1.4
|
102
106
|
signing_key:
|
103
107
|
specification_version: 4
|
104
|
-
summary: Serialize your objects
|
108
|
+
summary: Serialize your objects into Hashes
|
105
109
|
test_files:
|
106
|
-
- test/
|
110
|
+
- test/hashing/hasher_test.rb
|
107
111
|
- test/hashing/ivar_test.rb
|
108
112
|
- test/hashing/nested_test.rb
|
109
113
|
- test/hashing_test.rb
|
110
114
|
- test/test_helper.rb
|
115
|
+
has_rdoc:
|
data/lib/hasherize.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'hashing'
|
2
|
-
|
3
|
-
# Provides some sugar syntax to declare which `ivars` should be used to
|
4
|
-
# represent an object as a `Hash`.
|
5
|
-
#
|
6
|
-
# It respects all the behavior you will get by including {Hashing}. In fact,
|
7
|
-
# using this constructor is a shortcut to `include Hashing`, and call
|
8
|
-
# `.hasherize`
|
9
|
-
#
|
10
|
-
# @since 0.0.1
|
11
|
-
#
|
12
|
-
# @example shortcut to `include Hashing` and call `.hasherize`
|
13
|
-
# class File
|
14
|
-
# include Hasherize.new :path, :commit
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# @example configuring :to_hash and :from_hash strategies
|
18
|
-
# class File
|
19
|
-
# include Hasherize.new :content,
|
20
|
-
# to_hash: ->(content) { Base64.encode64 content },
|
21
|
-
# from_hash: ->(content_string) { Base64.decode64 content_string }
|
22
|
-
# end
|
23
|
-
class Hasherize < Module
|
24
|
-
# Stores the ivars and options to be repassed to `Hashing.serialize` by the
|
25
|
-
# hook {#included}
|
26
|
-
#
|
27
|
-
# @param ivars_and_options [*args] ivar names and options (`:to_hash` and `:from_hash`)
|
28
|
-
def initialize(*ivars_and_options)
|
29
|
-
@ivars = ivars_and_options
|
30
|
-
end
|
31
|
-
|
32
|
-
# Includes the `Hashing` module and calls {Hashing.hasherize}, repassing the
|
33
|
-
# ivar names an the options received in the constructor
|
34
|
-
def included(serializable_class)
|
35
|
-
serializable_class.module_eval do
|
36
|
-
include Hashing
|
37
|
-
end
|
38
|
-
serializable_class.send :hasherize, *@ivars
|
39
|
-
end
|
40
|
-
end
|
data/test/hasherize_test.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
describe Hasherize do
|
2
|
-
let(:hasherized) do
|
3
|
-
Class.new do
|
4
|
-
attr_reader :file, :commit
|
5
|
-
|
6
|
-
include Hasherize.new :file, :commit,
|
7
|
-
to_hash: ->(value) { "X-#{value}" },
|
8
|
-
from_hash: ->(value) { "#{value}-X" }
|
9
|
-
|
10
|
-
loading ->(params) { new params[:file], params[:commit] }
|
11
|
-
|
12
|
-
def initialize(file, commit)
|
13
|
-
@file, @commit = file, commit
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
it "just sugar to `include Hashing` and call `hasherize`" do
|
19
|
-
hasherized.ancestors.include?(Hashing).must_be :==, true
|
20
|
-
end
|
21
|
-
|
22
|
-
it "configures the ivars correctly (so I can recreate instances by Hash)" do
|
23
|
-
object = hasherized.from_hash file: 'README.md', commit: 'omglolbbq123'
|
24
|
-
object.file.must_be :==, 'README.md-X'
|
25
|
-
object.commit.must_be :==, 'omglolbbq123-X'
|
26
|
-
end
|
27
|
-
|
28
|
-
it "configure `:to_hash` e `:from_hash` serialization strategies" do
|
29
|
-
object = hasherized.new 'README.md', 'omglolbbq123'
|
30
|
-
object.to_h.must_be :==, { file: 'X-README.md', commit: 'X-omglolbbq123' }
|
31
|
-
end
|
32
|
-
end
|