traitorous 0.6.1 → 0.6.2
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/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
|