hashing 0.0.1.beta.2 → 0.0.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.
- checksums.yaml +4 -4
- data/.travis.yml +7 -0
- data/Gemfile +0 -2
- data/README.md +34 -7
- data/Rakefile +11 -0
- data/hashing.gemspec +3 -2
- data/lib/hasherize.rb +40 -0
- data/lib/hashing/ivar.rb +94 -0
- data/lib/hashing/version.rb +1 -1
- data/lib/hashing.rb +167 -2
- data/tasks/test.rake +26 -0
- data/test/hasherize_test.rb +32 -0
- data/test/hashing/ivar_test.rb +32 -0
- data/test/hashing/nested_test.rb +76 -0
- data/test/hashing_test.rb +123 -0
- data/test/test_helper.rb +5 -0
- metadata +38 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58fbbbe8dddf5ac8299796af0123035abba17b68
|
4
|
+
data.tar.gz: 142748d477848bc270bdd7fc4c74be5bcd6335ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5488323cd2dc9d7d53b2f2c53eeb091b7283b7f3d37be183d2311e67fd2cc5484ce7c02a070051d40ae7e30db87c718ee97f276dd332f9034321ae9f3600dbb
|
7
|
+
data.tar.gz: bbfa91cdfcacc250f95b9a9a1ff66986cb281db6007b8f95dac0198641f446c3fd10a7ad8681a07c455e4f3299952aadea8332cf2b06eb30e4878715dec8e7ab
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# Hashing
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
Gives you an easy way to specify which instances vars of your objects should be
|
4
|
+
used as `key => value` to serialize it into a hash returned by the `#to_h`
|
5
|
+
method. Also gives you a `YourClass::from_hash` to reconstruct the instances.
|
6
|
+
|
7
|
+
## Status
|
8
|
+
[](http://badge.fury.io/rb/hashing)
|
9
|
+
[](http://travis-ci.org/ricardovaleriano/hashing?branch=master)
|
10
|
+
[](https://codeclimate.com/github/ricardovaleriano/hashing)
|
11
|
+
[](http://inch-pages.github.io/github/ricardovaleriano/hashing)
|
6
12
|
|
7
13
|
## Installation
|
8
14
|
|
@@ -78,7 +84,7 @@ class File
|
|
78
84
|
include Hashing
|
79
85
|
hasherize :path, :commit, :content
|
80
86
|
|
81
|
-
|
87
|
+
loading ->(hash) {
|
82
88
|
new hash[:path], hash[:commit], hash[:content]
|
83
89
|
}
|
84
90
|
|
@@ -105,7 +111,7 @@ The previous example can be writen like this:
|
|
105
111
|
class File
|
106
112
|
include Hasherize.new :path, :commit, :content
|
107
113
|
|
108
|
-
|
114
|
+
loading ->(hash) {
|
109
115
|
new hash[:path], hash[:commit], hash[:content]
|
110
116
|
}
|
111
117
|
|
@@ -138,7 +144,7 @@ class File
|
|
138
144
|
to_hash: ->(content) { Base64.encode64 content },
|
139
145
|
from_hash: ->(content_string) { Base64.decode64 content_string }
|
140
146
|
|
141
|
-
|
147
|
+
loading ->(hash) {
|
142
148
|
new hash[:path], hash[:commit], hash[:content]
|
143
149
|
}
|
144
150
|
|
@@ -146,6 +152,20 @@ class File
|
|
146
152
|
end
|
147
153
|
```
|
148
154
|
|
155
|
+
#### Custom hasherizing and loading strategies for multiple ivars
|
156
|
+
|
157
|
+
You can indicate the same strategies for hasherzing and load from hashes for
|
158
|
+
multiple `ivars` if it makes sense to your program:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class File
|
162
|
+
include Hashing
|
163
|
+
|
164
|
+
hasherize :path, :commit,
|
165
|
+
to_hash: ->(value) { value.downcase },
|
166
|
+
from_hash: ->(value) { value.downcase }
|
167
|
+
end
|
168
|
+
```
|
149
169
|
|
150
170
|
#### Nested hasherized objects
|
151
171
|
|
@@ -203,7 +223,14 @@ this is all that `Hashing` needs to build a `File` instances from a valid `Hash`
|
|
203
223
|
|
204
224
|
## Contributing
|
205
225
|
|
206
|
-
|
226
|
+
This was a rapid "scratch your own itch" kind of project. It will make me real
|
227
|
+
happy if it can be used used in your software anyhow. If you need something
|
228
|
+
different than what is in it, or can solve us some bugs or add documentation, it
|
229
|
+
will be very well received!
|
230
|
+
|
231
|
+
Here is how you can help this gem:
|
232
|
+
|
233
|
+
1. Fork it ( https://github.com/ricardovaleriano/hashing/fork )
|
207
234
|
2. Create your feature branch (`git checkout -b my-new-feature`)
|
208
235
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
209
236
|
4. Push to the branch (`git push origin my-new-feature`)
|
data/Rakefile
CHANGED
@@ -1,2 +1,13 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
|
3
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/*.rake')].each { |rake| load rake }
|
4
|
+
task default: "test"
|
5
|
+
|
6
|
+
desc 'line number statistics'
|
7
|
+
task :lines do
|
8
|
+
libdir = File.join File.dirname(__FILE__), 'lib'
|
9
|
+
Dir["lib/**/*.rb"].each { |file|
|
10
|
+
lines = File.readlines(file).reject { |line| line =~ /^(\s*)#/ }.count
|
11
|
+
puts "#{lines.to_s.rjust(3)} in #{file}"
|
12
|
+
}
|
13
|
+
end
|
data/hashing.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Ricardo Valeriano"]
|
10
10
|
spec.email = ["ricardo.valeriano@gmail.com"]
|
11
11
|
spec.summary = %q{Serialize your objects as Hashes}
|
12
|
-
|
12
|
+
spec.description = %q{Gives you an easy way to specify which instances vars of your objects should be used as `key => value` to serialize it into 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
|
|
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
|
-
spec.add_development_dependency "bundler", "~> 1.
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "minitest-reporters", "~> 1.0"
|
23
24
|
end
|
data/lib/hasherize.rb
ADDED
@@ -0,0 +1,40 @@
|
|
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/lib/hashing/ivar.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Hashing
|
2
|
+
# Represents each one of the instance variables in a class that should be used
|
3
|
+
# to represent an object in a `Hash` form (serialization).
|
4
|
+
class Ivar
|
5
|
+
attr_reader :name
|
6
|
+
|
7
|
+
# Configure the name of an `ivar` and the 'callable' objects thath will be
|
8
|
+
# used to prepare the `ivar` value for serialization, and to load the object
|
9
|
+
# from a `Hash`.
|
10
|
+
#
|
11
|
+
# @param name [Symbol] name of a class `ivar`
|
12
|
+
# @param to_h [#call] callable to transform the value when serializing
|
13
|
+
# @param from_hash [#call] callable to transform the value from a `Hash`
|
14
|
+
def initialize(name, to_h = nil, from_hash = nil)
|
15
|
+
@name = name.to_sym
|
16
|
+
@to_h = to_h
|
17
|
+
@from_hash = from_hash
|
18
|
+
end
|
19
|
+
|
20
|
+
# Processes the parameter acordingly to the configuration made in the
|
21
|
+
# constructor. If some was made, return the value after being processed or
|
22
|
+
# else return the value as it is.
|
23
|
+
#
|
24
|
+
# Also guarantee that if a value is a #map, every item with `Hashing` in his
|
25
|
+
# method lookup will be sent the message `#to_h`.
|
26
|
+
#
|
27
|
+
# @param value [Object] object to be processed before being stored in a `Hash`
|
28
|
+
# @return the value that will be stored in the `Hash`
|
29
|
+
def to_h(value)
|
30
|
+
return value unless @to_h
|
31
|
+
|
32
|
+
if value.respond_to? :map
|
33
|
+
value = hasherize value
|
34
|
+
end
|
35
|
+
@to_h.call value
|
36
|
+
end
|
37
|
+
|
38
|
+
# Processes the Object provinient from a `Hash` so it can be used to
|
39
|
+
# reconstruct an instance.
|
40
|
+
#
|
41
|
+
# @param value [Object] object provinient from a hash
|
42
|
+
# @return the value that will be used to reconstruct the original instance
|
43
|
+
def from_hash(value, metadata = {})
|
44
|
+
return value unless @from_hash
|
45
|
+
|
46
|
+
if value.respond_to? :map
|
47
|
+
value = normalize(value, metadata)
|
48
|
+
end
|
49
|
+
@from_hash.call value
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_sym
|
53
|
+
@name.to_sym
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_s
|
57
|
+
@name.to_s
|
58
|
+
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
|
+
end
|
94
|
+
end
|
data/lib/hashing/version.rb
CHANGED
data/lib/hashing.rb
CHANGED
@@ -1,5 +1,170 @@
|
|
1
|
-
require
|
1
|
+
require 'hashing/version'
|
2
|
+
require 'hashing/ivar'
|
3
|
+
require 'hasherize'
|
2
4
|
|
3
5
|
module Hashing
|
4
|
-
#
|
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
|
+
def self.included(client_class)
|
37
|
+
client_class.extend Hasherizer
|
38
|
+
end
|
39
|
+
|
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
|
+
# The `Hash` returned by `#to_h` will be formed by keys based on the ivars
|
48
|
+
# names passed to `hasherize` method.
|
49
|
+
#
|
50
|
+
# @example File hahserized (which include `Hashing`)
|
51
|
+
#
|
52
|
+
# file = File.new 'README.md', 'cfe9aacbc02528b'
|
53
|
+
# file.to_h
|
54
|
+
# # => { path: 'README.md', commit: 'cfe9aacbc02528b' }
|
55
|
+
def to_h
|
56
|
+
hash_pairs = self.class.ivars.map { |ivar|
|
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
|
169
|
+
end
|
5
170
|
end
|
data/tasks/test.rake
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
desc "run all tests"
|
2
|
+
|
3
|
+
args = ARGV
|
4
|
+
args.shift
|
5
|
+
files = args
|
6
|
+
|
7
|
+
desc "run all the tests"
|
8
|
+
|
9
|
+
task :test do
|
10
|
+
lib = File.expand_path("../lib", __FILE__)
|
11
|
+
$: << lib
|
12
|
+
|
13
|
+
loader = ->(files_list){
|
14
|
+
files_list.each do |f|
|
15
|
+
if File.directory?(f)
|
16
|
+
loader.call(Dir.glob(f + "/**/*_test.rb"))
|
17
|
+
else
|
18
|
+
load f
|
19
|
+
end
|
20
|
+
end
|
21
|
+
}
|
22
|
+
|
23
|
+
require_relative "../test/test_helper"
|
24
|
+
files = Dir.glob("test/**/*_test.rb") unless files.size > 0
|
25
|
+
loader.call(files)
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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
|
@@ -0,0 +1,32 @@
|
|
1
|
+
describe Hashing::Ivar do
|
2
|
+
it "knows it's ivar name" do
|
3
|
+
ivar = Hashing::Ivar.new :file
|
4
|
+
ivar.name.must_be :==, :file
|
5
|
+
end
|
6
|
+
|
7
|
+
describe '#to_h' do
|
8
|
+
it "uses the lambda passed as parameter to transform the value" do
|
9
|
+
called = false
|
10
|
+
ivar = Hashing::Ivar.new :file, ->(value) { called = value }
|
11
|
+
ivar.to_h 'some value'
|
12
|
+
called.must_be :==, 'some value'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "by default just returns the value as is" do
|
16
|
+
Hashing::Ivar.new(:file).to_h('x').must_be :==, 'x'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#from_hash' do
|
21
|
+
it "uses the lambda passed as parameter to transform the value" do
|
22
|
+
called = false
|
23
|
+
ivar = Hashing::Ivar.new :file, nil, ->(value) { called = value }
|
24
|
+
ivar.from_hash 'loading hash'
|
25
|
+
called.must_be :==, 'loading hash'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "by default just returns the value as is" do
|
29
|
+
Hashing::Ivar.new(:file).from_hash('x').must_be :==, 'x'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
describe Hashing do
|
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
|
+
class HashingCollectionMember
|
15
|
+
attr_reader :annotation
|
16
|
+
include Hasherize.new :annotation,
|
17
|
+
to_hash: ->(value) { "--#{value}" },
|
18
|
+
from_hash: ->(value) { "#{value}--" }
|
19
|
+
|
20
|
+
loading ->(hash) { new hash[:annotation] }
|
21
|
+
|
22
|
+
def initialize(annotation)
|
23
|
+
@annotation = annotation
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#to_h' do
|
28
|
+
it 'calls #to_h for each array item when hashifying the object' do
|
29
|
+
owner = HashingCollectionOwner.new 'README.md', 'cfe9aacbc02528b', [
|
30
|
+
HashingCollectionMember.new('first'),
|
31
|
+
HashingCollectionMember.new('second'),
|
32
|
+
]
|
33
|
+
owner.to_h.must_be :==, {
|
34
|
+
file: 'README.md',
|
35
|
+
commit: 'cfe9aacbc02528b',
|
36
|
+
annotations: [
|
37
|
+
{ annotation: '--first' },
|
38
|
+
{ annotation: '--second' },
|
39
|
+
],
|
40
|
+
__hashing__: {
|
41
|
+
types: {
|
42
|
+
annotations: HashingCollectionMember
|
43
|
+
}
|
44
|
+
}
|
45
|
+
}
|
46
|
+
p owner.to_h
|
47
|
+
end
|
48
|
+
|
49
|
+
it "don't call the #to_h on inner object that don't include `Hashing`"
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#from_h' do
|
53
|
+
let(:hash_values) do
|
54
|
+
{
|
55
|
+
file: "README.md",
|
56
|
+
commit: "cfe9aacbc02528b",
|
57
|
+
annotations: [
|
58
|
+
{annotation: "first"},
|
59
|
+
{annotation: "second"}
|
60
|
+
],
|
61
|
+
__hashing__: {
|
62
|
+
types: {
|
63
|
+
annotations: HashingCollectionMember
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'calls #from_h for each on an yaml array that contains hasherized objects' do
|
70
|
+
owner = HashingCollectionOwner.from_hash hash_values
|
71
|
+
owner.annotations.first.annotation.must_be :==, 'first--'
|
72
|
+
owner.annotations.last.annotation.must_be :==, 'second--'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
describe Hashing do
|
4
|
+
describe 'interface' do
|
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
|
+
Class.new do
|
11
|
+
include Hashing
|
12
|
+
hasherize :ivar
|
13
|
+
loading ->() {}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'adds the ::from_hash method' do
|
18
|
+
hasherized.respond_to?(:from_hash).must_be :==, true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'adds the #to_h method' do
|
22
|
+
hasherized.new.respond_to?(:to_h).must_be :==, true
|
23
|
+
end
|
24
|
+
end# interface
|
25
|
+
|
26
|
+
describe 'Recreating a `hasherized` class instance' do
|
27
|
+
let(:hasherized) do
|
28
|
+
Class.new do
|
29
|
+
attr_reader :h
|
30
|
+
|
31
|
+
include Hashing
|
32
|
+
hasherize :h
|
33
|
+
|
34
|
+
def initialize(h)
|
35
|
+
@h = h
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '.loading' do
|
41
|
+
it 'uses (`#call`) the strategy defined by `.loading`' do
|
42
|
+
called = false
|
43
|
+
my_strategy = ->(h) { called = true }
|
44
|
+
hasherized.send :loading, my_strategy
|
45
|
+
hasherized.from_hash Hash.new
|
46
|
+
called.must_be :==, true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#from_hash' do
|
51
|
+
it 'just calls .new if none strategy was defined by .loading' do
|
52
|
+
new_object = hasherized.from_hash h: 'hasherizing'
|
53
|
+
new_object.h.must_be :==, { h: 'hasherizing' }
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'give an informative message in case the Hash is malformed' do
|
57
|
+
OmgLolBBQ = hasherized
|
58
|
+
message = nil
|
59
|
+
proc {
|
60
|
+
begin
|
61
|
+
OmgLolBBQ.from_hash xpto: 'JUST NO!'
|
62
|
+
rescue => e
|
63
|
+
message = e.message
|
64
|
+
raise e
|
65
|
+
end
|
66
|
+
}.must_raise Hashing::UnconfiguredIvar
|
67
|
+
message.must_be :==, 'The Hash has a :xpto key, but no @xpto '+
|
68
|
+
'was configured in OmgLolBBQ'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '.hasherize' do
|
74
|
+
let(:hasherized) do
|
75
|
+
Class.new do
|
76
|
+
include Hashing
|
77
|
+
|
78
|
+
attr_reader :content
|
79
|
+
|
80
|
+
hasherize :file, :commit
|
81
|
+
hasherize :content,
|
82
|
+
to_hash: ->(content) { Base64.encode64(content) },
|
83
|
+
from_hash: ->(hash_string) { Base64.decode64(hash_string) }
|
84
|
+
loading ->(hash) { new hash[:file], hash[:commit], hash[:content] }
|
85
|
+
|
86
|
+
def initialize(file, commit, content)
|
87
|
+
@file, @commit, @content = file, commit, content
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'allows configure how to serialize a specific `ivar`' do
|
93
|
+
file = hasherized.new 'README.md', 'cfe9aacbc02528b', '#Hashing\n\nWow. Such code...'
|
94
|
+
file.to_h[:content].must_be :==, Base64.encode64('#Hashing\n\nWow. Such code...')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'allows configure how to load a value for a specific `ivar`' do
|
98
|
+
file = hasherized.from_hash file: 'README.md',
|
99
|
+
commit: 'cfe9aacbc02528b',
|
100
|
+
content: Base64.encode64('#Hashing\n\nWow. Such code...')
|
101
|
+
file.content.must_be :==, '#Hashing\n\nWow. Such code...'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#to_h' do
|
106
|
+
let(:hasherized) do
|
107
|
+
Class.new do
|
108
|
+
include Hashing
|
109
|
+
hasherize :file, :commit
|
110
|
+
loading ->(hash) { new hash[:file], hash[:commit] }
|
111
|
+
|
112
|
+
def initialize(file, commit)
|
113
|
+
@file, @commit = file, commit
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'creates a hash using the ivars based on `.hasherize`' do
|
119
|
+
object = hasherized.new 'README.md', 'cfe9aacbc02528b'
|
120
|
+
object.to_h.must_be :==, { file: 'README.md', commit: 'cfe9aacbc02528b' }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/test/test_helper.rb
ADDED
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.1
|
4
|
+
version: 0.0.1
|
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-
|
11
|
+
date: 2014-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.5'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.5'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,7 +38,23 @@ dependencies:
|
|
38
38
|
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
-
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest-reporters
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
description: Gives you an easy way to specify which instances vars of your objects
|
56
|
+
should be used as `key => value` to serialize it into a hash returned by the `#to_h`
|
57
|
+
method. Also gives you a `YourClass::from_hash` to reconstruct the instances.
|
42
58
|
email:
|
43
59
|
- ricardo.valeriano@gmail.com
|
44
60
|
executables: []
|
@@ -46,13 +62,22 @@ extensions: []
|
|
46
62
|
extra_rdoc_files: []
|
47
63
|
files:
|
48
64
|
- .gitignore
|
65
|
+
- .travis.yml
|
49
66
|
- Gemfile
|
50
67
|
- LICENSE.txt
|
51
68
|
- README.md
|
52
69
|
- Rakefile
|
53
70
|
- hashing.gemspec
|
71
|
+
- lib/hasherize.rb
|
54
72
|
- lib/hashing.rb
|
73
|
+
- lib/hashing/ivar.rb
|
55
74
|
- lib/hashing/version.rb
|
75
|
+
- tasks/test.rake
|
76
|
+
- test/hasherize_test.rb
|
77
|
+
- test/hashing/ivar_test.rb
|
78
|
+
- test/hashing/nested_test.rb
|
79
|
+
- test/hashing_test.rb
|
80
|
+
- test/test_helper.rb
|
56
81
|
homepage: http://github.com/ricardovaleriano/hashing
|
57
82
|
licenses:
|
58
83
|
- MIT
|
@@ -68,13 +93,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
68
93
|
version: '0'
|
69
94
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
95
|
requirements:
|
71
|
-
- - '
|
96
|
+
- - '>='
|
72
97
|
- !ruby/object:Gem::Version
|
73
|
-
version:
|
98
|
+
version: '0'
|
74
99
|
requirements: []
|
75
100
|
rubyforge_project:
|
76
101
|
rubygems_version: 2.1.11
|
77
102
|
signing_key:
|
78
103
|
specification_version: 4
|
79
104
|
summary: Serialize your objects as Hashes
|
80
|
-
test_files:
|
105
|
+
test_files:
|
106
|
+
- test/hasherize_test.rb
|
107
|
+
- test/hashing/ivar_test.rb
|
108
|
+
- test/hashing/nested_test.rb
|
109
|
+
- test/hashing_test.rb
|
110
|
+
- test/test_helper.rb
|