hashing 0.0.1.beta.2 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/hashing.svg)](http://badge.fury.io/rb/hashing)
|
9
|
+
[![Build Status](https://travis-ci.org/ricardovaleriano/hashing.png?branch=master)](http://travis-ci.org/ricardovaleriano/hashing?branch=master)
|
10
|
+
[![Code Climate](https://codeclimate.com/github/ricardovaleriano/hashing.png)](https://codeclimate.com/github/ricardovaleriano/hashing)
|
11
|
+
[![Inline docs](http://inch-pages.github.io/github/ricardovaleriano/hashing.png)](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
|