traitorous 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +114 -44
- data/VERSION +1 -1
- data/lib/traitorous.rb +1 -1
- data/lib/traitorous/convert.rb +27 -14
- data/lib/traitorous/standard_procs.rb +27 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96b54f11016e53adedd46e355dda6e15df91ba0f
|
4
|
+
data.tar.gz: 16d6ae6e551d2a8a636667e05dae8a0187347539
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e167111bfa664852ed9a3ca79742196f3744b02464912dea3aa079c0468f8a5ef5b59a4caaab112760129eb03b8833aa47ef9b6c4fe22e33b8962cac444ab5fd
|
7
|
+
data.tar.gz: 28f02d34b627d998df66e5b01923b5925ce5f2a546c7b3f16f2018ab0183b3f5ec7c8b5aefb9f849dd5b894ee336f9b322dbb992fe42989c4a15bd4298e682e6
|
data/README.md
CHANGED
@@ -16,8 +16,10 @@ The converters can be used to help (de)serialize, set default values, do
|
|
16
16
|
validations and translations. The converter's job is to be able to import a
|
17
17
|
value, instantiating into classes and assigning values.
|
18
18
|
|
19
|
-
|
19
|
+
No type information is included in a trait. Only the Converter that will
|
20
|
+
import and export it.
|
20
21
|
|
22
|
+
I took a lot of inspiration from [virtus](https://github.com/solnic/virtus).
|
21
23
|
|
22
24
|
## Installation
|
23
25
|
|
@@ -41,7 +43,9 @@ Or install it yourself as:
|
|
41
43
|
|
42
44
|
# see spec/
|
43
45
|
require 'traitorous'
|
44
|
-
|
46
|
+
|
47
|
+
# trait converters default to Convert.skip which is a converter that just
|
48
|
+
# passes through it's data unchanged.
|
45
49
|
class Ruin
|
46
50
|
include Traitorous
|
47
51
|
include Traitorous::Equality
|
@@ -63,12 +67,16 @@ Or install it yourself as:
|
|
63
67
|
puts Ruin.new(r.export) == r
|
64
68
|
# true
|
65
69
|
|
70
|
+
# the Convert.default converter provides a default value if data is falsey
|
71
|
+
# the Convert.model converter instantiates the class given as a new object
|
72
|
+
# using data, if the 2nd arg == :array or [], each element of the array is
|
73
|
+
# instantiated.
|
66
74
|
class Area
|
67
75
|
include Traitorous
|
68
76
|
include Traitorous::Equality
|
69
77
|
trait :name
|
70
|
-
trait :size,
|
71
|
-
trait :ruins,
|
78
|
+
trait :size, Convert.default('sub-continent')
|
79
|
+
trait :ruins, Convert.model(Ruin, :array) # or Convert.array(Convert.call_on(Ruin))
|
72
80
|
end
|
73
81
|
|
74
82
|
area = Area.new(
|
@@ -96,51 +104,113 @@ in a simple form ready to save.
|
|
96
104
|
This system should be flexible enough to account for an large variety of data
|
97
105
|
structures that can be read in and out of storage easily and in 1 tree.
|
98
106
|
|
99
|
-
|
100
|
-
|
101
|
-
|
107
|
+
### Basic Procs
|
108
|
+
Each converter is really just a thin wrapper around a pair of procs (or other
|
109
|
+
object that responds to .call(data).
|
110
|
+
|
111
|
+
Each proc takes data, and when called, converts that data into something else
|
112
|
+
|
113
|
+
#### StandardProcs.noop
|
114
|
+
The noop proc simply returns the value passed to it.
|
115
|
+
`# proc{|data| data}`
|
116
|
+
|
117
|
+
#### StandardProcs.default(default_value)
|
118
|
+
The default proc provides a default value if data is falsey.
|
119
|
+
`StandardProcs.default(:default_value) # proc{|data| data || :default_value }
|
120
|
+
|
121
|
+
#### StandardProcs.call_on_self(method_name)
|
122
|
+
The call_on_self proc calls method_name on data.
|
123
|
+
`StandardProcs.call_on_self(:intern) # proc{|data| data.send(:intern) }`
|
124
|
+
`StandardProcs.call_on_self(:intern) # proc{|data| data.send(:to_s) }`
|
125
|
+
|
126
|
+
#### StandardProcs.call_on(klass, with_method: :new)
|
127
|
+
The call_on proc uses data as params for a method call.
|
128
|
+
`StandardProcs.call_on(Pathname) # proc{|data| Pathname.send(:new,data)}`
|
129
|
+
`StandardProcs.call_on(URI, :parse) # proc{|data| URI.send(:parse,data)}`
|
130
|
+
|
131
|
+
#### StandardProcs.map(&block)
|
132
|
+
The map proc converts each element of Array(data) using block.
|
133
|
+
`StandardProcs.map{|data| URI.send(:parse,data)}`
|
134
|
+
|
135
|
+
#### StandardProcs.inject(memo_obj)(&block)
|
136
|
+
The map proc converts each element of Array(data) uses a memo_obj using block.
|
137
|
+
`StandardProcs.inject({}){|memo,data| memo[data.name] = data}`
|
138
|
+
|
139
|
+
#### Custom
|
140
|
+
A proc is very easy to create, creating simple procs to do various tasks can
|
141
|
+
simply many complex tasks
|
142
|
+
```ruby
|
143
|
+
dt_proc = proc{|data| Time.now()}
|
144
|
+
uri_proc = proc{|data| URI.parse(data)}
|
145
|
+
uri_out_proc = proc{|data| data.to_s }
|
146
|
+
tag_proc = proc{|data| data.split(/,/)
|
147
|
+
tag_join_proc = proc{|data| data.join(',')}
|
148
|
+
```
|
149
|
+
|
150
|
+
## Basic Converters
|
151
|
+
The converters provided by the Convert module use various of the StandardProcs
|
152
|
+
to achieve commonly used patterns.
|
102
153
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
154
|
+
The Converter itself is a simple object that takes an importer, and an optional
|
155
|
+
exporter and the provides a thin wrapper for `.import(data)` and `.export(data)`
|
156
|
+
|
157
|
+
### Convert.noop
|
158
|
+
This is the default converter for trait, both importer and exporter are
|
159
|
+
`StandardProcs.noop`. The primary purpose of this is so that data that doesn't
|
160
|
+
need conversion have a simple converter that conforms to the same API as other
|
161
|
+
converters.
|
162
|
+
|
163
|
+
### Convert.default(default_value)
|
164
|
+
This provides a `StandardProcs.default(default_value)` importer, and a
|
165
|
+
`StandardProcs.noop` exporter.
|
166
|
+
|
167
|
+
Currently there isn't a way to skip nil or empty values on export, and so I've
|
168
|
+
never had to make a decision about a more complex `.export(data)` proc for this
|
169
|
+
converter. Changing the signature to `StandardProcs.default(default_value, :include_on_export)`
|
170
|
+
or it's logical opposite. But I'm not sure which default behaviour is better.
|
171
|
+
|
172
|
+
### Convert.model(model_name, container = :scalar)
|
173
|
+
This is a pure convenience method that provides simple Object instantiaion
|
174
|
+
for single objects, arrays, and hash values. container may be :scaler, :array,
|
175
|
+
:hash. The container simply does a `proc{|data| model_name.new(data)}` on
|
176
|
+
the single scalar, on each element of an array, or on each value of a hash.
|
109
177
|
|
110
178
|
```ruby
|
111
|
-
#
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
converter = Model.new(Superhero)
|
116
|
-
superhero = converter.do_import(batman_data)
|
117
|
-
# does this
|
118
|
-
Superhero.send(import_method, data)
|
119
|
-
# and is the same result as
|
120
|
-
Superhero.new(batman_data)
|
121
|
-
# exports by
|
122
|
-
converter.do_export(superhero)
|
123
|
-
# does this
|
124
|
-
superhero.send(export_method)
|
125
|
-
# and is the same result as
|
126
|
-
superhero.export
|
127
|
-
|
128
|
-
# Example of using a custom import_method and export_method
|
129
|
-
batman_data_uri = '/batman/current'
|
130
|
-
converter = Model.new(Superhero, import_method: :parse, export_method: :to_s)
|
131
|
-
superhero = converter.do_import(batman_data)
|
132
|
-
# does this
|
133
|
-
uri = URI.send(import_method, batman_data)
|
134
|
-
# and is the same result as
|
135
|
-
URI.parse(batman_data)
|
136
|
-
# exports by
|
137
|
-
converter.do_export(uri)
|
138
|
-
# does this
|
139
|
-
uri.send(export_method)
|
140
|
-
# and is the same result as
|
141
|
-
uri.to_s
|
179
|
+
# common uses
|
180
|
+
Convert.model(Pathname) # Pathname.new(data)
|
181
|
+
Convert.model(CustomClass, :array) # [CustomClass<#...>,CustomClass<#...>,...]
|
182
|
+
Convert.model(CustomClass, :hash) # {'orig_key_1' => CustomClass<#...>,'orig_key_2' => CustomClass<#...>...}
|
142
183
|
```
|
143
184
|
|
185
|
+
### Convert.call_on_self(with_method, export_with: :itself)
|
186
|
+
This provides an `StandardProcs.call_on_self(with_method)` importer and
|
187
|
+
`StandardProcs.call_on_self(export_with)` exporter
|
188
|
+
|
189
|
+
Currently there is no way to pass additional arguments on the method call. It
|
190
|
+
should be easy enough to create a custom Converter if you need to do that.
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
# common uses
|
194
|
+
Convert.call_on_self(:intern, export_with: :to_s)
|
195
|
+
Convert.call_on_self(:to_i)
|
196
|
+
```
|
197
|
+
|
198
|
+
### Convert.call_on(klass, with: :new, export_with: :export)
|
199
|
+
This provides a `StandardProcs.call_on(klass, with_method: with)` importer
|
200
|
+
and a `StandardProcs.call_on_self(:export)` exporter.
|
201
|
+
|
202
|
+
Currently there is no way to pass additional arguments on the method call. It
|
203
|
+
should be easy enough to create a custom Converter if you need to do that.
|
204
|
+
|
205
|
+
```
|
206
|
+
# common uses
|
207
|
+
c = Convert.call_on(Pathname) # Pathname.new(data)
|
208
|
+
c = Convert.call_on(URI, :parse) # URI.parse(data)
|
209
|
+
c = Convert.call_on(CustomClass, with: :import) # CustomClass.import(data)
|
210
|
+
```
|
211
|
+
|
212
|
+
### Convert.array(converter)
|
213
|
+
|
144
214
|
##### export
|
145
215
|
My current design kind of places a lot of the responsibilities for the export
|
146
216
|
sequence of the objects themselves. And I'm both satisied with this design and
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.6.
|
1
|
+
0.6.2
|
data/lib/traitorous.rb
CHANGED
@@ -45,7 +45,7 @@ module Traitorous
|
|
45
45
|
# be any object or module.
|
46
46
|
#
|
47
47
|
# defaults to Traitorous::Converter::DEFAULT
|
48
|
-
def trait(attr_name, converter = Convert.
|
48
|
+
def trait(attr_name, converter = Convert.noop)
|
49
49
|
self.traits ||= HASH.new
|
50
50
|
self.traits[attr_name.intern] = converter
|
51
51
|
attr_accessor attr_name
|
data/lib/traitorous/convert.rb
CHANGED
@@ -6,7 +6,7 @@ module Traitorous
|
|
6
6
|
# .skip provides the simplest converter. It does nothing to the value
|
7
7
|
# except pass it through unchanged on both import and export
|
8
8
|
# @return [Traitorous::Converter]
|
9
|
-
def
|
9
|
+
def noop
|
10
10
|
Converter.new(
|
11
11
|
StandardProcs.noop,
|
12
12
|
StandardProcs.noop
|
@@ -25,7 +25,6 @@ module Traitorous
|
|
25
25
|
)
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
28
|
# .call_on creates a StandardProc.call_on proc for the importer, and a
|
30
29
|
# StandardProc.call_on_self proc for the exporter.
|
31
30
|
# @param method_name [Symbol, String] method to send data
|
@@ -39,13 +38,29 @@ module Traitorous
|
|
39
38
|
)
|
40
39
|
end
|
41
40
|
|
41
|
+
# .model(model_name) instantiates a model_name using model_name.new(data)
|
42
|
+
# @param model_name [Class,#new] the name of the model to be instantiated
|
43
|
+
# @return [Traitorous::Converter]
|
44
|
+
def model(model_name, container = :scalar)
|
45
|
+
case container
|
46
|
+
when :scalar
|
47
|
+
call_on(model_name)
|
48
|
+
when :array, []
|
49
|
+
self.array(self.call_on(model_name))
|
50
|
+
when :hash, {}
|
51
|
+
self.hash(self.call_on(model_name))
|
52
|
+
else
|
53
|
+
raise ArgumentError.new("No container of type: #{container}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
42
57
|
# .call_on_self creates a StandardProc.call_on_self proc for the importer
|
43
58
|
# with the with_method param, and a StandardProc.call_on_self with the
|
44
59
|
# export_with param
|
45
60
|
# @param with_method [Symbol, String] method to send data on import
|
46
61
|
# @param export_with [Symbol, String] method to send data on export
|
47
62
|
# @return [Traitorous::Converter]
|
48
|
-
def call_on_self(with_method, export_with: :
|
63
|
+
def call_on_self(with_method, export_with: :itself)
|
49
64
|
Converter.new(
|
50
65
|
StandardProcs.call_on_self(with_method),
|
51
66
|
StandardProcs.call_on_self(export_with)
|
@@ -60,8 +75,8 @@ module Traitorous
|
|
60
75
|
# @return [Traitorous::Converter]
|
61
76
|
def array(converter)
|
62
77
|
Converter.new(
|
63
|
-
|
64
|
-
|
78
|
+
StandardProcs.map { |entry| converter.importer.call(entry) },
|
79
|
+
StandardProcs.map { |entry| converter.exporter.call(entry) }
|
65
80
|
)
|
66
81
|
end
|
67
82
|
|
@@ -71,8 +86,8 @@ module Traitorous
|
|
71
86
|
# @return [Traitorous::Converter]
|
72
87
|
def hash(value_converter)
|
73
88
|
Converter.new(
|
74
|
-
|
75
|
-
|
89
|
+
StandardProcs.inject({}) { |h, (k, v)| h[k] = value_converter.importer.call(v) },
|
90
|
+
StandardProcs.inject({}) { |h, (k, v)| h[k] = value_converter.exporter.call(v) },
|
76
91
|
)
|
77
92
|
end
|
78
93
|
|
@@ -90,13 +105,11 @@ module Traitorous
|
|
90
105
|
# @return [Traitorous::Converter]
|
91
106
|
def array_to_hash(key_converter, value_converter)
|
92
107
|
Converter.new(
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
h
|
99
|
-
end
|
108
|
+
StandardProcs.inject({}) do |memo_obj, data|
|
109
|
+
value = value_converter.importer.call(data)
|
110
|
+
key = key_converter.importer.call(value, data)
|
111
|
+
memo_obj[key] = value
|
112
|
+
memo_obj
|
100
113
|
end,
|
101
114
|
proc { |data_hsh| data_hsh.values.map { |value| value_converter.exporter.call(value) } }
|
102
115
|
)
|
@@ -12,14 +12,14 @@ module Traitorous
|
|
12
12
|
# otherwise it returns it's default value
|
13
13
|
# @param default_value [Object] the default value to use with data is
|
14
14
|
# falsey
|
15
|
-
# @return [
|
15
|
+
# @return [Proc]
|
16
16
|
def default(default_value)
|
17
17
|
proc { |data| data || default_value }
|
18
18
|
end
|
19
19
|
|
20
20
|
# This proc calls method_name on data.
|
21
21
|
# @param method_name [Symbol, String] method to send data
|
22
|
-
# @return [
|
22
|
+
# @return [Proc]
|
23
23
|
def call_on_self(method_name)
|
24
24
|
proc { |data| data.send(method_name) }
|
25
25
|
end
|
@@ -27,8 +27,32 @@ module Traitorous
|
|
27
27
|
# This proc calls klass with :with and passes data as params
|
28
28
|
# @param klass [Class, Module, Object] obj to call
|
29
29
|
# @param with_method: [Symbol, String] method to send klass with data as params
|
30
|
+
# @return [Proc]
|
30
31
|
def call_on(klass, with_method: :new)
|
31
|
-
proc {|data| klass.send(with_method, data)}
|
32
|
+
proc { |data| klass.send(with_method, data) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# This proc calls map on data, and calls the block for each element.
|
36
|
+
# @param block [Class, Module, Object] obj to call
|
37
|
+
# @return [Proc]
|
38
|
+
def map(&block)
|
39
|
+
raise ArgumentError.new("Must be called with block") unless block_given?
|
40
|
+
proc { |data_arr| Array(data_arr).map { |data| block.call(data) } }
|
41
|
+
end
|
42
|
+
|
43
|
+
# This proc calls map on data, and calls the block for each element.
|
44
|
+
# @param memo_obj [Array,Hash,#dup] dup called on this and used as the
|
45
|
+
# seed object for the inject call.
|
46
|
+
# @param block [Class, Module, Object] obj to call
|
47
|
+
# @return [Proc]
|
48
|
+
def inject(memo_obj, &block)
|
49
|
+
raise ArgumentError.new("Must be called with block") unless block_given?
|
50
|
+
proc do |data_arr|
|
51
|
+
Array(data_arr).inject(memo_obj.dup) do |memo_obj, data|
|
52
|
+
block.call(memo_obj, data)
|
53
|
+
memo_obj
|
54
|
+
end
|
55
|
+
end
|
32
56
|
end
|
33
57
|
end
|
34
58
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: traitorous
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- scott m parrish
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-10-
|
11
|
+
date: 2015-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|