traitorous 0.3.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1dcf2e9d0afef212d428c6140582dd2d14b66e6a
4
- data.tar.gz: 129a2a34983b5ed2b65908f227a2f81ef0ec1d95
3
+ metadata.gz: ce24a9926d5a1a1473d4ecce829f5ae67753747e
4
+ data.tar.gz: 935ffb3bb8ace183357d11db17ed1b9a45a07cd8
5
5
  SHA512:
6
- metadata.gz: 84c658572a8ae35cc9e38e7bb8732e15eb8e2c55cf3d2cafc996896324fa67de78d5cbb87048c795967edd569fd3d04bdbe831b042ace881e9c580b3bb4a18c8
7
- data.tar.gz: 460429463388d6aaa6a2e88f8622da871451baa62cccd1fb29318e37d9eeb3048ff3bfabe30f77973fa80fc30a73c118f0e40aa447b6fa053c7c1bd1d7ef25a1
6
+ metadata.gz: e4d983bdef166b6f527e01ab327d8e1ecdade88fec74f372bb0d534a31833ddbdf1921a23457cfe2b9fb16136f1547fe32dd1b92cf623c0853b6cd82906d5705
7
+ data.tar.gz: 5df97df341451212529e99b632aa18267c4a74648aa4f074e6be9ee2184617b8a830c5d71b40b76151907ff18014e600a7aea30873aa8ba5c06e0839b2a73a38
data/.gitignore CHANGED
@@ -6,5 +6,90 @@
6
6
  /doc/
7
7
  /pkg/
8
8
  /spec/reports/
9
+ ### Ruby template
10
+ *.gem
11
+ *.rbc
12
+ /.config
13
+ /coverage/
14
+ /InstalledFiles
15
+ /pkg/
16
+ /spec/reports/
17
+ /spec/examples.txt
18
+ /test/tmp/
19
+ /test/version_tmp/
20
+ /tmp/
21
+
22
+ ## Specific to RubyMotion:
23
+ .dat*
24
+ .repl_history
25
+ build/
26
+
27
+ ## Documentation cache and generated files:
28
+ /.yardoc/
29
+ /_yardoc/
30
+ /doc/
31
+ /rdoc/
32
+
33
+ ## Environment normalisation:
34
+ /.bundle/
35
+ /vendor/bundle
36
+ /lib/bundler/man/
37
+
38
+ # for a library or gem, you might want to ignore these files since the code is
39
+ # intended to run in multiple environments; otherwise, check them in:
40
+ # Gemfile.lock
41
+ # .ruby-version
42
+ # .ruby-gemset
43
+
44
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
45
+ .rvmrc
46
+ ### JetBrains template
47
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
48
+
49
+ *.iml
50
+
51
+ ## Directory-based project format:
52
+ .idea/
53
+ # if you remove the above rule, at least ignore the following:
54
+
55
+ # User-specific stuff:
56
+ # .idea/workspace.xml
57
+ # .idea/tasks.xml
58
+ # .idea/dictionaries
59
+
60
+ # Sensitive or high-churn files:
61
+ # .idea/dataSources.ids
62
+ # .idea/dataSources.xml
63
+ # .idea/sqlDataSources.xml
64
+ # .idea/dynamic.xml
65
+ # .idea/uiDesigner.xml
66
+
67
+ # Gradle:
68
+ # .idea/gradle.xml
69
+ # .idea/libraries
70
+
71
+ # Mongo Explorer plugin:
72
+ # .idea/mongoSettings.xml
73
+
74
+ ## File-based project format:
75
+ *.ipr
76
+ *.iws
77
+
78
+ ## Plugin-specific files:
79
+
80
+ # IntelliJ
81
+ /out/
82
+
83
+ # mpeltonen/sbt-idea plugin
84
+ .idea_modules/
85
+
86
+ # JIRA plugin
87
+ atlassian-ide-plugin.xml
88
+
89
+ # Crashlytics plugin (for Android Studio and IntelliJ)
90
+ com_crashlytics_export_strings.xml
91
+ crashlytics.properties
92
+ crashlytics-build.properties
93
+
9
94
  /tmp/
10
95
  .env
data/README.md CHANGED
@@ -39,51 +39,52 @@ Or install it yourself as:
39
39
 
40
40
  ```ruby
41
41
 
42
- # see spec/
43
- require 'traitorous'
44
-
45
- class Ruin
46
- include Traitorous
47
- include Traitorous::Equality
48
- trait :name
49
- trait :danger
50
- end
51
-
52
- r = Ruin.new(name: 'Skull Mountain', danger: 'The Devil of')
53
- #
54
- puts r.name
55
- # Skull Mountain
56
-
57
- puts r.danger
58
- # The Devil of
59
-
60
- puts r.export
61
- # {'name' => 'Skull Mountain, 'danger' => 'The Devil of'}
62
-
63
- puts Ruin.new(r.export) == r
64
- # true
65
-
66
- class Area
67
- include Traitorous
68
- include Traitorous::Equality
69
- trait :name
70
- trait :size, Converter::DefaultValueStatic.new('sub-continent')
71
- trait :ruins, Converter::UniformArray.new(Ruin)
72
- end
73
-
74
- area = Area.new(
75
- name: 'Western Marches',
76
- size: 'Region',
77
- ruins: [{name: 'Skull Mountain', danger: 'The Devil of'},
78
- {name: 'Dire Swamp', danger: 'The Devil of'}
79
- ]
80
- )
81
- puts area.ruins.length
82
- # 2
83
- puts area.export
84
- # {:name=>"Western Slope", :size=>"region", :ruins=>[{:name=>"Dire Swamp", :danger=>"The Creature of"}, {:name=>"Skull Mountain", :danger=>"The Devil of"}]}
85
- puts Area.new(area.export) == area
86
- # true
42
+ # see spec/
43
+ require 'traitorous'
44
+
45
+ class Ruin
46
+ include Traitorous
47
+ include Traitorous::Equality
48
+ trait :name
49
+ trait :danger
50
+ end
51
+
52
+ r = Ruin.new(name: 'Skull Mountain', danger: 'The Devil of')
53
+ #
54
+ puts r.name
55
+ # Skull Mountain
56
+
57
+ puts r.danger
58
+ # The Devil of
59
+
60
+ puts r.export
61
+ # {"name"=>"Skull Mountain", "danger"=>"The Devil of"}
62
+
63
+ puts Ruin.new(r.export) == r
64
+ # true
65
+
66
+ class Area
67
+ include Traitorous
68
+ include Traitorous::Equality
69
+ trait :name
70
+ trait :size, Converter::DefaultValueStatic.new('sub-continent')
71
+ trait :ruins, Converter::UniformArray.new(Ruin)
72
+ end
73
+
74
+ area = Area.new(
75
+ name: 'Western Marches',
76
+ ruins: [{name: 'Skull Mountain', danger: 'The Devil of'},
77
+ {name: 'Dire Swamp', danger: 'The Devil of'}
78
+ ]
79
+ )
80
+ puts area.size
81
+ # 'sub-continent'
82
+ puts area.ruins.length
83
+ # 2
84
+ puts area.export
85
+ # {:name=>"Western Slope", :size=> "sub-continent", :ruins=>[{:name=>"Dire Swamp", :danger=>"The Creature of"}, {:name=>"Skull Mountain", :danger=>"The Devil of"}]}
86
+ puts Area.new(area.export) == area
87
+ # true
87
88
  ```
88
89
 
89
90
  ## Converters
@@ -95,30 +96,114 @@ in a simple form ready to save.
95
96
  This system should be flexible enough to account for an large variety of data
96
97
  structures that can be read in and out of storage easily and in 1 tree.
97
98
 
98
- #### Traitorous::Converter::Identity
99
- This converter is meant as a pass through converter that doesn't alter the
100
- incoming value on either do_import or do_export.
99
+ The 5 that are in the system should cover a lot of ground, anything trickier
100
+ should just be implemented as it's own Converter, ensuring that it responds to
101
+ `#do_import(data)` and `#do_export(data)`.
101
102
 
102
- #### Traitorous::Converter::DefaultValueStatic
103
- THis converter is similar to the Traitorous::Converter::Identity except that
104
- when doing do_import it will return a default value if the opts are nil or
105
- false.
103
+ Most of the converters wil have a pair of optional arguments: import_method
104
+ (defaults to :new) and export_method (defaults to :export). Export is simple,
105
+ it's the method called on the object, objects created using Traitorous
106
+ automatically respond_to export. set export_method to :to_s in order to convert
107
+ your Pathname object into a string. Set import_method to :parse, to create your
108
+ object with URI.parse. This should provide a tremendous amount of versatility.
106
109
 
107
- #### Traitorous::Converter::Model
108
- This converter takes a model_klass argument and on do_import, will instantiate
109
- a new object of that class passing in the opts as params. do_export calls
110
- .export on the object and returns the result.
110
+ ```ruby
111
+ # Example of using the defaults for IMport_method and export_method
112
+ # import_method defaults to :new
113
+ # export_method defaults to :export
114
+ batman_data = {name: 'Batman', secret_identity: 'Bruce Wayne'}
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
142
+ ```
111
143
 
112
- #### Traitorous::Converter::UniformArray
113
- This converter takes a uniform_klass argument. It expects an array as input for
114
- .do_import and will instantiate an uniform_klass object for each element of the
115
- array and return the resulting array.
144
+ ##### export
145
+ My current design kind of places a lot of the responsibilities for the export
146
+ sequence of the objects themselves. And I'm both satisied with this design and
147
+ not at all sure that it's the right design. The importers gets the combination
148
+ of the klass and the import_method which makes for a near perfect amount of
149
+ coverage, they cover everything from URI.parse to File.read to Animal.new to
150
+ YAML.load_file. But the exporters are expected to get with just the
151
+ export_method setting to be called on the object itself.
152
+
153
+ THe concepts of the converters could easily be isolated into a single type of
154
+ converter, and assigning both an import and export converter. But think the
155
+ concept has 3 points against it, and they take the day over the advantages.
156
+ 1. Maintaining the import and export parts of the converter in a single object
157
+ provides a semantic and symbolic relationship between the import and export
158
+ functions for a single trait. This should be encouraged to maintain a focus
159
+ upon synchronicity between import and export that is central to Traitorous.
160
+ 2. having to define both an import converter and an export converter makes the
161
+ trait api less attractive.
162
+ 3. It is really easy to write a custom converter in which you can implement
163
+ whatever logic you need to meet your needs.
164
+ 4. The domain knowledge on how to export belongs in the class or object of the
165
+ model.
166
+
167
+ #### Traitorous::Converter::Value
168
+ The Value converter leaves an existing value alone, but may insert a default
169
+ value if no value exists. The ability to maintain nil vs false values is
170
+ unimportant to me and thus unaddressed. I highly recommend a small custom class
171
+ instead. Or submit a PR.
172
+
173
+ #### MISSING - Traitorous::Converter::Proc
174
+ The Proc converter would take a block in the initialize `that thes a value gets applied to
116
175
 
117
- #### Traitorous::Converter::MethodKeyedUniformHash
118
- This converter takes key_method and uniform_class arguments. It expects an
119
- array as input for .do_import and will instantiate an uniform_klass object for
120
- each element of the array, then call key_method on that object and add them
121
- to a hash as a key instance pair.
176
+
177
+ #### Traitorous::Converter::Model
178
+ This converter calls an import_method on a klass and passes the data as
179
+ attributes. Villain.send(:new, data), URI.send(:parse, data). This is meant to
180
+ provide class instantiation, complex conversion behavior, and service object
181
+ handling. On export the export_method is called om the data. data.send(:export),
182
+ data.send(:to_s).
183
+
184
+ #### Traitorous::Converter::Array
185
+ This converter imports an array of data and then uses klass and import_method as
186
+ with the Converter::Model. On export, the each element has the export_method
187
+ called on it and returns the transformed array.
188
+ `data_arr.map{|e| e.send(export_method)}`
189
+
190
+ #### Traitorous::Converter::ArrayToHash
191
+ This converter takes a key_method in addition to klass, import_method, and export
192
+ methods. It takes an array of data, imports them as the Array converter does.
193
+ But the sends the key_method to the imported object for use in a key/value pair.
194
+
195
+ ### MISSING - Traitorous::Converter::Hash
196
+ A converter that takes a hash to import, and export. The tricky thing is how
197
+ the keys of the incoming hash are treated, and how the keys for the exported
198
+ hash are created. One way would be to discard the keys of the incoming hash, and
199
+ treat the array of values as the ArrayToHash converter does. A second way would
200
+ be to maintain the incoming keys as the outgoing keys. A third way would be to
201
+ expect to pass both the key and the value around as a pair making the import
202
+ look like klass.send(import_method, key, value), and expecting (k,v) as output
203
+ for export.
204
+
205
+ I haven't come up with a use case that has compelled me to solve this problem
206
+ yet, my needs are provided for by the ArrayToHash converter.
122
207
 
123
208
  ### More Converters
124
209
 
@@ -128,6 +213,9 @@ usage. This especially would be important if you wanted to import a list of
128
213
  object that represents different klasses that are given with a sub_type or
129
214
  sub_class attributes that are part of the do_import data.
130
215
 
216
+ Maybe also set traits to accept both import and export settings? Not sure if
217
+ this is necessary yet.
218
+
131
219
  ## Roadmap
132
220
 
133
221
  1. Add better documentation
@@ -136,10 +224,9 @@ sub_class attributes that are part of the do_import data.
136
224
  a. DefaultValueDynamic that stores code to run upon input or output
137
225
  b. VariableArray that uses a sub-type in the opts to location the correct
138
226
  class to instantiate instead of always using a uniform class
139
- c. Validations?
140
- d. translations?
141
- e. class specific (Date, CreditCard)
142
-
227
+ 4. Validations?
228
+ 5. translations?
229
+ 6. HashWithIndifferentAccess
143
230
  ## Development
144
231
 
145
232
  I use Guard to automate testing. It won't affect anything execpt the disk space
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.6.1
@@ -0,0 +1,106 @@
1
+ module Traitorous
2
+ # Convert provides a selection of helpers that will build converters for
3
+ # common situations.
4
+ module Convert
5
+ class << self
6
+ # .skip provides the simplest converter. It does nothing to the value
7
+ # except pass it through unchanged on both import and export
8
+ # @return [Traitorous::Converter]
9
+ def skip
10
+ Converter.new(
11
+ StandardProcs.noop,
12
+ StandardProcs.noop
13
+ )
14
+ end
15
+
16
+ # .default(default_value = nil) creates a StandardProc.default importer
17
+ # and a StandardProcs.noop exporter
18
+ # TODO add :export_with for consistency
19
+ # @param default_value [Object] value to set in cases that data is falsey
20
+ # @return [Traitorous::Converter]
21
+ def default(default_value = nil)
22
+ Converter.new(
23
+ StandardProcs.default(default_value),
24
+ StandardProcs.noop
25
+ )
26
+ end
27
+
28
+
29
+ # .call_on creates a StandardProc.call_on proc for the importer, and a
30
+ # StandardProc.call_on_self proc for the exporter.
31
+ # @param method_name [Symbol, String] method to send data
32
+ # @param with: [Symbol, String] method to send klass with data as params
33
+ # @param export_with: [Symbol, String] method to send klass with data as params
34
+ # @return [Traitorous::Converter]
35
+ def call_on(klass, with: :new, export_with: :export)
36
+ Converter.new(
37
+ StandardProcs.call_on(klass, with_method: with),
38
+ StandardProcs.call_on_self(export_with)
39
+ )
40
+ end
41
+
42
+ # .call_on_self creates a StandardProc.call_on_self proc for the importer
43
+ # with the with_method param, and a StandardProc.call_on_self with the
44
+ # export_with param
45
+ # @param with_method [Symbol, String] method to send data on import
46
+ # @param export_with [Symbol, String] method to send data on export
47
+ # @return [Traitorous::Converter]
48
+ def call_on_self(with_method, export_with: :export)
49
+ Converter.new(
50
+ StandardProcs.call_on_self(with_method),
51
+ StandardProcs.call_on_self(export_with)
52
+ )
53
+ end
54
+
55
+
56
+ # Creates an importer that runs the converter importer or exporter for
57
+ # each element of an array, return the resulting array
58
+ # @param converter [Traitorous::Converter] the converter to use for both
59
+ # import and export.
60
+ # @return [Traitorous::Converter]
61
+ def array(converter)
62
+ Converter.new(
63
+ proc { |data| Array(data).map { |entry| converter.importer.call(entry) } },
64
+ proc { |data| Array(data).map { |entry| converter.exporter.call(entry) } }
65
+ )
66
+ end
67
+
68
+ # TODO better to extract the inject proc into it's own StandardProc?
69
+ # @param value_converter [Traitorous::Converter] the converter to use for both
70
+ # import and export.
71
+ # @return [Traitorous::Converter]
72
+ def hash(value_converter)
73
+ Converter.new(
74
+ proc { |data| data.inject({}) { |h, (k, v)| h[k] = value_converter.importer.call(v); h } },
75
+ proc { |data| data.inject({}) { |h, (k, v)| h[k] = value_converter.exporter.call(v); h } },
76
+ )
77
+ end
78
+
79
+ # TODO better expecting key, value pairs as the output of the converter?
80
+ # Expects an incoming array, uses the param value_converter for each
81
+ # element, then uses the key_converter on the resulting value and stores
82
+ # the key, value pair in the resulting hash.
83
+ # @note. The value_converter is used before the key_converter, and the
84
+ # key_converter receives the output of the value_converter as it's data
85
+ # param.
86
+ # @param key_converter [Traitorous::Converter] the converter to use for to
87
+ # generate the hash key for a given value.
88
+ # @param value_converter [Traitorous::Converter] the converter to use for
89
+ # converting the value.
90
+ # @return [Traitorous::Converter]
91
+ def array_to_hash(key_converter, value_converter)
92
+ Converter.new(
93
+ proc do |data_arr|
94
+ data_arr.inject({}) do |h, d|
95
+ v = value_converter.importer.call(d)
96
+ k = key_converter.importer.call(v)
97
+ h[k] = v
98
+ h
99
+ end
100
+ end,
101
+ proc { |data_hsh| data_hsh.values.map { |value| value_converter.exporter.call(value) } }
102
+ )
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,30 @@
1
+ module Traitorous
2
+ # The purpose of the converters are to facilitate the importation of simple
3
+ # JSON or YAML, CSV data, and import that data into an arbitrarily nested
4
+ # tree of objects. And then to take those object and be able to export
5
+ # that data in a simple form ready to save, that is functionally identical to
6
+ #
7
+ # opts = {some: 'deep', data: ['structures']}
8
+ # r = Mission.new(opts)
9
+ # Mission.new(r.export) == r
10
+ class Converter
11
+ # if no other converter is declared, use the identity converter which
12
+ # merely pass through the data passed to it.
13
+ # DEFAULT_PROC is a default pass through proc
14
+
15
+ attr_accessor :converters, :importer, :exporter
16
+
17
+ def initialize(importer, exporter = StandardProcs.noop)
18
+ @importer = importer
19
+ @exporter = exporter
20
+ end
21
+
22
+ def import(data)
23
+ importer.call(data)
24
+ end
25
+
26
+ def export(data)
27
+ exporter.call(data)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ module Traitorous
2
+ module StandardProcs
3
+ class << self
4
+ NOOP = proc { |data| data }
5
+
6
+ # a proc that simply passes through it's input as output
7
+ def noop
8
+ NOOP
9
+ end
10
+
11
+ # a proc that simply passes through it's input if the input is truthy,
12
+ # otherwise it returns it's default value
13
+ # @param default_value [Object] the default value to use with data is
14
+ # falsey
15
+ # @return [Object] returns data || default_value
16
+ def default(default_value)
17
+ proc { |data| data || default_value }
18
+ end
19
+
20
+ # This proc calls method_name on data.
21
+ # @param method_name [Symbol, String] method to send data
22
+ # @return [Object] result of calling method_name on data
23
+ def call_on_self(method_name)
24
+ proc { |data| data.send(method_name) }
25
+ end
26
+
27
+ # This proc calls klass with :with and passes data as params
28
+ # @param klass [Class, Module, Object] obj to call
29
+ # @param with_method: [Symbol, String] method to send klass with data as params
30
+ def call_on(klass, with_method: :new)
31
+ proc {|data| klass.send(with_method, data)}
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/traitorous.rb CHANGED
@@ -1,5 +1,14 @@
1
1
  require 'active_support/core_ext/hash/indifferent_access'
2
2
 
3
+ # Traitorous is a model trait library whose primary goal is to be able to
4
+ # import from a hash and have it initialize an object graph. It also exports
5
+ # back to a similar hash.
6
+ # The exported hash will be almost identical to the import hash with the sole
7
+ # exception being that any trait values that are left out of the initial hash
8
+ # will end up being in the export hash with a nil value.
9
+ # Similar libraries include https://github.com/solnic/virtus,
10
+ # https://github.com/cgriego/active_attr, and https://github.com/jetrockets/attrio
11
+
3
12
  module Traitorous
4
13
 
5
14
  # Use the HashWithIndifferentAccess from the activesupport gem.
@@ -13,57 +22,56 @@ module Traitorous
13
22
  # values to be defined, and 2 methods: :do_import and :do_export.
14
23
  # do_import is meant to take the data, and use the initial data to combine to
15
24
  # build objects or other data structures.
16
- module Converter
17
- # The purpose of the converters are to facilitate the importation of simple
18
- # JSON or YAML data and import that data into an arbitrarily nested tree
19
- # of objects. And then to take those object and be able to export that data
20
- # in a simple form ready to save.
21
- # opts = {some: 'deep', data: ['structures']}
22
- # r = Mission.new(opts)
23
- # Mission.new(r.export) == r
24
- require 'traitorous/converter/identity'
25
- require 'traitorous/converter/default_value_static'
26
- require 'traitorous/converter/model'
27
- require 'traitorous/converter/uniform_array'
28
- require 'traitorous/converter/method_keyed_uniform_hash'
29
- # if no other converter is declared, use the identity converter which
30
- # merely pass through the data passed to it.
31
- DEFAULT_CONVERTER = Converter::Identity.new
32
- end
25
+ require 'traitorous/standard_procs'
26
+ require 'traitorous/converter'
33
27
 
28
+ # The convert module provides simple front ends to the Converter object
29
+ # constructor
30
+ require 'traitorous/convert'
34
31
  require 'traitorous/equality'
35
32
 
36
33
  module ClassMethods
34
+ # @!attribute [rw] traits
35
+ # @return [HashWithIndifferntAccess]
36
+ # There's no reason you can't manually alter the .traits hash, but be
37
+ # aware of the potential consequences upon your data when doing so.
37
38
  attr_accessor :traits
38
39
 
39
- def trait(attr_name, converter = Traitorous::Converter::DEFAULT_CONVERTER)
40
+ # @param attr_name [Symbol,String] name of the attribute, follows normal
41
+ # method name rules
42
+ # @param converter [Traitorous::Converter,#do_import,#do_export] the converter
43
+ # this trait will use during import and export operations. A converter
44
+ # must implement #do_import(data) and #do_export(data) but can otherwise
45
+ # be any object or module.
46
+ #
47
+ # defaults to Traitorous::Converter::DEFAULT
48
+ def trait(attr_name, converter = Convert.skip)
40
49
  self.traits ||= HASH.new
41
- # sub hash used here, {} chosen for expansion possibility, but only
42
- # :converter is in use
43
- self.traits[attr_name] = {converter: converter}
50
+ self.traits[attr_name.intern] = converter
44
51
  attr_accessor attr_name
45
52
  end
46
53
  end
47
54
 
55
+ # :nodoc:
48
56
  def self.included(base)
49
57
  base.extend(ClassMethods)
50
58
  end
51
59
 
52
- def initialize(opts = {})
60
+ # use the key/value pairs of
61
+ def initialize(data = {})
53
62
  raise "No traits have been defined for #{self.class}" unless self.class.traits
54
- @opts = HASH.new(opts)
55
- traits.each_pair do |new_trait, trait_opts|
56
- converter = trait_opts[:converter]
57
- val = converter.do_import(@opts.fetch(new_trait, nil))
63
+ @data = HASH.new(data)
64
+ traits.each_pair do |new_trait, converter|
65
+ val = converter.import(@data.fetch(new_trait, nil))
58
66
  self.send("#{new_trait}=".intern, val)
59
67
  end
60
68
  end
61
69
 
62
70
  def export
63
- exported = HASH.new
64
- traits.each_pair do |trait_name, trait_opts|
71
+ exported = {}
72
+ traits.each_pair do |trait_name, converter|
65
73
  attr_value = self.public_send(trait_name.intern)
66
- exported[trait_name] = trait_opts[:converter].do_export(attr_value)
74
+ exported[trait_name] = converter.export(attr_value)
67
75
  end
68
76
  exported
69
77
  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.3.0
4
+ version: 0.6.1
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-09-20 00:00:00.000000000 Z
11
+ date: 2015-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -102,12 +102,10 @@ files:
102
102
  - bin/console
103
103
  - bin/setup
104
104
  - lib/traitorous.rb
105
- - lib/traitorous/converter/default_value_static.rb
106
- - lib/traitorous/converter/identity.rb
107
- - lib/traitorous/converter/method_keyed_uniform_hash.rb
108
- - lib/traitorous/converter/model.rb
109
- - lib/traitorous/converter/uniform_array.rb
105
+ - lib/traitorous/convert.rb
106
+ - lib/traitorous/converter.rb
110
107
  - lib/traitorous/equality.rb
108
+ - lib/traitorous/standard_procs.rb
111
109
  - traitorius.gemspec
112
110
  homepage: https://github.com/anithri/traitorous
113
111
  licenses:
@@ -130,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
128
  version: '0'
131
129
  requirements: []
132
130
  rubyforge_project:
133
- rubygems_version: 2.4.6
131
+ rubygems_version: 2.4.8
134
132
  signing_key:
135
133
  specification_version: 4
136
134
  summary: A simple attribute, import, export system for PORO,
@@ -1,15 +0,0 @@
1
- module Traitorous
2
- module Converter
3
- class DefaultValueStatic
4
- def initialize(default_value)
5
- @default_value = default_value
6
- end
7
- def do_import(opts)
8
- opts || @default_value
9
- end
10
- def do_export(data)
11
- data
12
- end
13
- end
14
- end
15
- end
@@ -1,35 +0,0 @@
1
- module Traitorous
2
- module Converter
3
- # The Identity converter is used as a simple default which performs the
4
- # jobs of a Converter without changing the inputs at all.
5
- #
6
- # This converter can be used when you have simple data (strings, dates,
7
- # numbers, hashes and arrays of the same) that doesn't need to be inflated
8
- # into an object and needs no changes to be safely stored as the same.
9
- class Identity
10
- # do_export is called in order to take an existing piece of data and
11
- # prepare it to be written to a simpler data structure.
12
- #
13
- # The Identity converter exports the same data it is passed without
14
- # changing them at all.
15
- #
16
- # @params data [Object] the data object passed in to be exported
17
- # @return [Object] exports data to a form simple enough to save as JSON
18
- def do_export(data)
19
- data
20
- end
21
-
22
- # do_import is called in order to take some opts and to turn them into
23
- # instantiated objects, arrays, or other types of transformation
24
- #
25
- # The Identity converter imports the same opts it is passed without
26
- # changing them at all.
27
- #
28
- # @params opts [Object] the options to be imported
29
- # @return [Object] opts
30
- def do_import(opts)
31
- opts
32
- end
33
- end
34
- end
35
- end
@@ -1,43 +0,0 @@
1
- module Traitorous
2
- module Converter
3
- # MethodKeyedUniformedHash is meant to take an array of a simple data
4
- # structures and convert each into a uniform class, and then will call a
5
- # key_method on that class and use it as the key in the returned hash.
6
- #
7
- # Exported data will be converted into an array calling do_export on each
8
- # element
9
- class MethodKeyedUniformHash
10
- attr_accessor :key_method, :uniform_klass
11
- # @param key_method [Symbol] the method to call on the uniform_klass instance
12
- # to generate the key in the returned hash
13
- # @param uniform_klass [Class, #new] the class to instantiate with each
14
- # element of the do_import array
15
- def initialize(key_method, uniform_klass)
16
- @key_method = key_method
17
- @uniform_klass = uniform_klass
18
- end
19
-
20
- # The import instantiates each element of the array as an instance of
21
- # the uniform_klass, the key is determined by calling key_method on the
22
- # instance and then they are joined to the result hash as a key,
23
- # instance pair.
24
- # @param arr_data [Array] the array of data to instantiate
25
- # @return [Hash] hash containing key, instance pairs
26
- def do_import(arr_data)
27
- out = {}
28
- Array(arr_data).each do |elem_data|
29
- obj = uniform_klass.new(elem_data)
30
- out[obj.send(key_method)] = obj
31
- end
32
- out
33
- end
34
-
35
- # @param hsh_data [Hash<obj,#export>] keys are ignored
36
- # @return Array each element of the values of hsh_data has #export called
37
- # on it.
38
- def do_export(hsh_data)
39
- hsh_data.values.map{|instance| instance.export }
40
- end
41
- end
42
- end
43
- end
@@ -1,39 +0,0 @@
1
- module Traitorous
2
- module Converter
3
- # The Model converter is used a a simple way to instantiate an object from
4
- # the model_class using the imported opts, and which will call .export on
5
- # the data.
6
- #
7
- # This converter can be used when you have a set of opts that you can pass
8
- # to a classes .new command in order to instantiate an object.
9
- class Model
10
- # @param model_klass [Class,#new] class to instantiate when importing
11
- def initialize(model_klass)
12
- @model_klass = model_klass
13
- end
14
-
15
- # do_export is called in order to take an existing piece of data and
16
- # prepare it to be written to a simpler data structure.
17
- #
18
- # The model converter exports the data by calling .export on it
19
- #
20
- # @params data [Object] the data object passed in to be exported
21
- # @return [Object] result of data.export
22
- def do_export(data)
23
- data.export
24
- end
25
-
26
- # do_import is called in order to take some opts and to turn them into
27
- # instantiated objects, arrays, or other types of transformation
28
- #
29
- # The model converter imports the opts by instantiating a model_class
30
- # object with it.
31
- #
32
- # @params opts [Object] the options to be exported
33
- # @return [Object] result of model_klass.new(opts)
34
- def do_import(opts)
35
- @model_klass.new(opts)
36
- end
37
- end
38
- end
39
- end
@@ -1,17 +0,0 @@
1
- module Traitorous
2
- module Converter
3
- class UniformArray
4
- def initialize(uniform_klass)
5
- @uniform_klass ||= uniform_klass
6
- end
7
- def do_export(data_arr)
8
- Array(data_arr).map(&:export)
9
- end
10
-
11
- def do_import(opts_arr)
12
- Array(opts_arr).map{|d| @uniform_klass.new(d)}
13
- end
14
-
15
- end
16
- end
17
- end