deco_lite 1.1.0 → 1.2.0

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
  SHA256:
3
- metadata.gz: 67309b0dd1feaa0f5022e255841a5f9d86c7362facce49e78fac594b74c3fd4e
4
- data.tar.gz: 5e7fe616878edf0872ffac7fb292248b3292d4be88f6e9b4f6848f46aacf04e6
3
+ metadata.gz: 3025f7b914542580dcaaa18ab1666d0cabb4890e845b7155b1d98f5f917a1da5
4
+ data.tar.gz: 597aa821f2edb7683cbd65b48561a64c2cdbbdb50457dae07f8d33e61aeeef1b
5
5
  SHA512:
6
- metadata.gz: 5626e25b3a24d52c934ee9e538d13b71f7709a991d6042604a56d65f6a2a77392fa48ed16d71d93551e155b0307f11773f612038e344bb6d0e87b9bb2fa9782e
7
- data.tar.gz: a876c92d0e679377113a1cf0d96af3aa2e3aef90da8f92a3e3ca693983e614911c9c1c599680c29efe9373d60c5ac5f2deeb5ba4210ad8d1a9e0dfbc8612438d
6
+ metadata.gz: bd1786683a6f7a7ceaca9a9cd8bef861edd2d66e40416cb1bf67c75eda7e152f9d7d58ce96c0b4cdf6ae240c353cc9cbc1a5e0421379afb8d92b169a196527e5
7
+ data.tar.gz: dbef72b83f45454fd2fa74e9bb94ad6079f45b9759a6729bb6b65e0b33ab657a8d9ea72545c0ffb9a2497310e68515b6bde8b25cc208876a5b0347ca9d65b3db
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 1.2.0
2
+ * Changes
3
+ * Update the README.md file with better explainations and examples.
4
+
1
5
  ### 1.1.0
2
6
  * Changes
3
7
  * Update mad_flatter gem to v2.0.0.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- deco_lite (1.1.0)
4
+ deco_lite (1.2.0)
5
5
  activemodel (~> 7.0, >= 7.0.3.1)
6
6
  activesupport (~> 7.0, >= 7.0.3.1)
7
7
  immutable_struct_ex (~> 0.2.0)
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DecoLite
2
2
 
3
- [![GitHub version](http://badge.fury.io/gh/gangelo%2Fdeco.svg)](https://badge.fury.io/gh/gangelo%2Fdeco)
3
+ [![GitHub version](http://badge.fury.io/gh/gangelo%2Fdeco.svg)](https://badge.fury.io/gh/gangelo%2Fdeco_lite)
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/deco_lite.svg)](https://badge.fury.io/rb/deco_lite)
6
6
 
@@ -13,93 +13,77 @@
13
13
 
14
14
  ## Introduction
15
15
 
16
- _Deco_ is a little gem that allows you to use the provided `DecoLite::Model` class (`include ActiveModel::Model`) to dynamically create Decorator class objects. Inherit from `DecoLite::Model` to create your own unique classes with custom functionality. A `DecoLite::Model` includes `ActiveModel::Model`, so validation can be applied using [ActiveModel validation helpers](https://api.rubyonrails.org/v6.1.3/classes/ActiveModel/Validations/HelperMethods.html) you are familiar with; or, you can roll your own - just like any other ActiveModel.
16
+ *DecoLite* is a little gem that allows you to use the provided `DecoLite::Model` class to dynamically create Decorator class objects. Use the `DecoLite::Model` class directly, or inherit from the `DecoLite::Model` class to create your own unique subclasses with custom functionality. `DecoLite::Model` includes `ActiveModel::Model`, so validation can be applied using [ActiveModel validation helpers](https://api.rubyonrails.org/v6.1.3/classes/ActiveModel/Validations/HelperMethods.html) you're familiar with; or, you can roll your own - just like any other `ActiveModel`.
17
17
 
18
- A `DecoLite::Model` will allow you to consume a Ruby Hash that you supply via the initializer (`DecoLite::Model#new`) or via the `DecoLite::Model#load!` method. Your supplied Ruby Hashes are used to create `attr_accessor` attributes (_"fields"_) on the model. Each attribute created, is then assigned its value from the Hash loaded. Any number of hashes can be consumed using the `DecoLite::Model#load!` method.
18
+ `DecoLite::Model` allows you to consume a Ruby `Hash` that you supply via the initializer (`DecoLite::Model#new`) or via the `DecoLite::Model#load!` method. Any number of Ruby `Hashes` can be consumed. Your supplied Ruby Hashes are used to create `attr_accessor` attributes (or *"fields"*) on the model. Each attribute created is then assigned the value from the Hash that was loaded. Again, any number of hashes can be consumed using the `DecoLite::Model#load!` method.
19
19
 
20
- `attr_accessor` names created are _mangled_ to include namespacing. This creates unique attribute names for nested Hashes that may include non-unique keys. For example:
20
+ `attr_accessors` created during initialization, or by calling `DecoLite::Model#load!`, are *mangled* to include namespacing. This allows `DecoLite` to create *unique* attribute names for nested Hashes that may have non-unique key names. For example:
21
21
 
22
22
  ```ruby
23
23
  # NOTE: keys :name and :age are not unique across this Hash.
24
24
  family = {
25
+ # :name and :age are not unique
25
26
  name: 'John Doe',
26
27
  age: 35,
27
28
  wife: {
29
+ # :name and :age are not unique
28
30
  name: 'Mary Doe',
29
31
  age: 30,
30
32
  }
31
33
  }
32
34
  ```
33
- Given the above example, DecoLite will produce the following `attr_accessors` on the `DecoLite::Model` object and assign the values:
35
+ Given the above example, `DecoLite` will produce the following *unique* `attr_accessors` on the `DecoLite::Model` object, and assign the values:
34
36
 
35
37
  ```ruby
36
- # Or DecoLite::Model.new.load!(hash: family)
38
+ # Instead of the below, you can also use DecoLite::Model.new.load!(hash: family)
37
39
  model = DecoLite::Model.new(hash: family)
38
40
 
39
41
  model.name #=> 'John Doe'
40
- model.respond_to? :name= #=> true
41
-
42
42
  model.age #=> 35
43
- model.respond_to? :age= #=> true
44
43
 
45
44
  model.wife_name #=> 'Mary Doe'
46
- model.respond_to? :wife_name= #=> true
47
-
48
45
  model.wife_age #=> 30
49
- model.respond_to? :wife_age= #=> true
50
46
  ```
51
47
 
52
- `DecoLite::Model#load!` can be called _multiple times_, on the same model, with different Hashes. This could potentially cause `attr_accessor` name clashes. In order to ensure unique `attr_accessor` names, a _"namespace"_ may be _explicitly_ provided to ensure uniqueness.
48
+ In the above example, notice how `DecoLite` *mangles* attributes `:wife_name` and `:wife_age` using the `:wife` `Hash` key name to make them unique.
53
49
 
54
- For example, **continuing from the previous example;** if we were to call `DecoLite::Model#load!` a _second time_ with the following Hash, this would potentially produce `attr_accessor` name clashes:
50
+ `DecoLite::Model#load!` can be called *multiple times*, on the same model using different `Hashes`. This could potentially cause `attr_accessor` name clashes. In order to ensure unique `attr_accessor` names, a *"namespace"* may be *explicitly* provided to ensure attribute name uniqueness.
51
+
52
+ For example, **continuing from the previous example,** if we were to call `DecoLite::Model#load!` a *second time* with the following `Hash`, this would produce `attr_accessor` name clashes which would raise errors, because `:name` and `:age` attributes already exist on the `DecoLite::Model` in question:
55
53
 
56
54
  ```ruby
57
55
  grandpa = {
58
56
  name: 'Henry Doe',
59
57
  age: 85,
60
58
  }
61
- # The :name and :age Hash keys above will produce :name/:name= and :age/:age= attr_accessors
62
- # and clash because these were already added to the model when "John Doe" was loaded with
63
- # the first call to DecoLite::Model.new(hash: family).
64
59
  ```
65
60
 
66
- However, passing a `:namespace` option (for example `namespace: :grandpa`) to the `DecoLite::Model#load!` method, would produce the following `attr_accessors`, ensuring their uniqueness:
61
+ To handle the above scenario, `DecoLite::Model` allows you to pass a `:namespace` option (for example `namespace: :grandpa`) to the `DecoLite::Model#load!` method; this would produce the following `attr_accessors`, ensuring their uniqueness:
67
62
 
68
63
  ```ruby
69
64
  model.load!(hash: grandpa, options: { namespace: :grandpa })
70
65
 
71
- # Unique now that the namespace: :grandpa has been applied:
72
66
  model.grandpa_name #=> 'Henry Doe'
73
- model.respond_to? :grandpa_name= #=> true
74
-
75
67
  model.grandpa_age #=> 85
76
- model.respond_to? :grandpa_age= #=> true
77
68
 
78
- # All the other attributes on the model remain the same, and unique:
79
69
  model.name #=> 'John Doe'
80
- model.respond_to? :name= #=> true
81
-
82
70
  model.age #=> 35
83
- model.respond_to? :age= #=> true
84
71
 
85
72
  model.wife_name #=> 'Mary Doe'
86
- model.respond_to? :wife_name= #=> true
87
-
88
73
  model.wife_age #=> 30
89
- model.respond_to? :wife_age= #=> true
90
74
  ```
91
75
 
92
- ### For more examples and usage
76
+ ### More examples and usage
93
77
 
94
- For more examples and usage, see the [Examples and usage](#examples-and-usage) and [Mode examples and usage](#more-examples-and-usage) sections; there is also an "I want to..." section with examples you might encounter when using `DecoLite`.
78
+ For more examples and usage, see the [Examples and usage](#examples-and-usage) and [More examples and usage](#more-examples-and-usage) sections; there is also an "I want to..." section with examples of things you may want to accomplish when using `DecoLite`.
95
79
 
96
80
  ## Use cases
97
81
 
98
- ### General
99
- _Deco_ would _most likely_ thrive where the structure of the Hashe(s) consumed by the `DecoLite::Model#load!` method is known. This is because of the way _Deco_ mangles loaded Hash key names to create unique `attr_accessor` names (see the Introduction section); although, I'm sure there are some metaprogramming geniuses out there that might prove me wrong. Assuming this is the case, _Deco_ would be ideal to handle Model attributes, Webservice JSON results (converted to Ruby Hash), JSON Web Token (JWT) payload, etc..
82
+ ### Generally Speaking
83
+ `DecoLite` would *most likely* thrive where the structure of the `Hashe(s)` consumed are (of course) known, relatively small to moderate in size, and not *terribly* deep nested-hash-wise. This is because of the way `DecoLite` mangles loaded Hash key names to create unique `attr_accessors` on the model (see the Introduction section). However, I'm sure there are some geniuses out there that would find other contexts where `DecoLite` may thrive. Assuming the former is the case, `DecoLite` would be ideal to consume Model attributes, Webservice JSON results (converted to Ruby `Hash`), JSON Web Token (JWT) payloads, etc. to create a cohesive data model to be used in any scenario.
100
84
 
101
85
  ### Rails
102
- Because `DecoLite::Model` includes `ActiveModel::Model`, it could also be ideal for use as a model in Rails applications, where a _decorator pattern_ might be used, and decorator methods provided for use in Rails views; for example:
86
+ Because `DecoLite::Model` includes `ActiveModel::Model`, it could also be ideal for use as a model in Rails applications, where a *decorator pattern* might be used, and decorator methods provided for use in Rails views; for example:
103
87
 
104
88
  ```ruby
105
89
  class ViewModel < DecoLite::Model
@@ -127,7 +111,7 @@ view_model.salutation
127
111
  ```
128
112
  ### Etc., etc., etc.
129
113
 
130
- Get creative. Please pop me an email and let me know how _you're_ using _Deco_.
114
+ Get creative. Please pop me an email and let me know how *you're* using `DecoLite`.
131
115
 
132
116
  ## Examples and usage
133
117
 
@@ -157,7 +141,7 @@ class Couple < DecoLite::Model)
157
141
  husband_info_address == wife_info_address
158
142
  end
159
143
 
160
- def breadwinner
144
+ def bread_winner
161
145
  case
162
146
  when husband_info_salary > wife_info_salary
163
147
  husband_name
@@ -176,7 +160,7 @@ couple.load!(hash: wife, options: { namespace: :wife })
176
160
 
177
161
  # Will produce the following:
178
162
  model.live_together? #=> true
179
- model.breadwinner #=> John Doe
163
+ model.bread_winner #=> John Doe
180
164
 
181
165
  model.husband_name #=> John Doe
182
166
  model.husband_info_age #=> 21
@@ -192,7 +176,7 @@ model.wife_info_address #=> 1 street, boonton, nj 07005
192
176
 
193
177
  #### Add validators to my model
194
178
 
195
- Simply add your `ActiveModel` validators just like you would any other `ActiveModel::Model` validator. However, be aware that (currently), any attribute (field) having an _explicit validation_ associated with it, will automatically cause an `attr_accessor` to be created for that field; this is to avoid `NoMethodErrors` when calling a validation method on the model (e.g. `#valid?`, `#validate`, etc.) *before* the data is loaded to create the associated `attr_accessors`:
179
+ Simply add your `ActiveModel` validators just like you would any other `ActiveModel::Model` validator. However, be aware that any attribute (field) having an *explicit validation* associated with it, will automatically cause an `attr_accessor` to be created for that field; this is to avoid `NoMethodErrors` when validating the model (e.g. `#valid?`, `#validate`, etc.) *before* the data is loaded that would prompt the creation of the associated `attr_accessors` on the model:
196
180
 
197
181
  ```ruby
198
182
  class Model < DecoLite::Model
@@ -200,7 +184,7 @@ class Model < DecoLite::Model
200
184
  validates :age, numericality: true
201
185
  end
202
186
 
203
- # No :address
187
+ # :address is missing
204
188
  model = Model.new(hash: { first: 'John', last: 'Doe', age: 25 })
205
189
  model.respond_to? :address
206
190
  #=> true
@@ -211,17 +195,18 @@ model.errors.full_messages
211
195
  #=> ["Address can't be blank"]
212
196
 
213
197
  model.load!(hash: { address: '123 park road, anytown, nj 01234' })
214
- model.validate
198
+ model.valid?
215
199
  #=> true
216
200
  ```
217
201
 
218
202
  #### Validate whether or not certain fields were loaded
219
203
 
220
- To be clear, this example does not validate the _data_ associated with the fields loaded; rather, this example validates whether or not the _fields themselves_ were loaded into your model, and as a result, `attr_accessors` created. If you only want to validate the _data_ loaded into your model, simply add `ActiveModel` validation, just like you would any other `ActiveModel` model, see the [Add validators to my model](#add-validators-to-my-model) section.
204
+ To be clear, this example does not validate the *data* associated with the fields loaded; rather, this example validates whether or not the *fields themselves* were loaded into your model, and as a result, `attr_accessors` created *for* them on the model. If you only want to validate the *data* loaded into your model, simply use `ActiveModel` validations, just like you would any other `ActiveModel` model (see the [Add validators to my model](#add-validators-to-my-model) section).
221
205
 
222
- If you want to validate whether or not particular _fields_ were added to your model as attributes (`attr_accessor`), as a result of `#load!`ing data into your model, you need to add the required field names to the `DecoLite::Model#required_fields` attribute, or use inheritance:
206
+ If you want to validate whether or not particular *fields* were loaded into your model, as a result of `#load!`ing data into your model, you need to add the required field names to the `DecoLite::Model#required_fields` attribute, or use inheritance:
223
207
  - Create a `DecoLite::Model` subclass.
224
- - Override the `DecoLite::Model#required_fields` method and return an Array of field names represented by `Symbols` you want to validate.
208
+ - Override the `DecoLite::Model#required_fields` method.
209
+ - Return an Array of `Symbols` that represent the fields you want to validate (e.g. `%i[first last ssn]`).
225
210
 
226
211
  For example:
227
212
 
@@ -231,7 +216,7 @@ class Model < DecoLite::Model
231
216
  validates :age, numericality: { only_integer: true }, allow_blank: true
232
217
 
233
218
  def required_fields
234
- # We want to ensure these fields were included as Hash keys during loading.
219
+ # We want to ensure these fields were loaded.
235
220
  %i[first last address]
236
221
  end
237
222
  end
@@ -240,16 +225,19 @@ model = Model.new
240
225
 
241
226
  model.validate
242
227
  #=> false
228
+
243
229
  model.errors.full_messages
244
230
  #=> ["First field is missing", "Last field is missing", "Address field is missing"]
231
+ ```
232
+
233
+ If we load data that includes :first, :last, and :address Hash keys, even with nil data, our `":<field> field is missing"` errors would go away; in this scenario, we only wish to validate the *presence of the FIELDS,* not the data associated with these fields!
245
234
 
246
- # If we load data that includes :first, :last, and :address Hash keys even with
247
- # nil data, our ":<field> field is missing" errors go away; in this scenario,
248
- # we're validating the presence of the FIELDS, not the data associated with
249
- # these fields!
235
+ ```ruby
250
236
  model.load!(hash: { first: nil, last: nil, address: nil })
237
+
251
238
  model.validate
252
239
  #=> true
240
+
253
241
  model.errors.full_messages
254
242
  #=> []
255
243
 
@@ -259,19 +247,20 @@ user = {
259
247
  address: '123 anystreet, anytown, nj 01234',
260
248
  age: 'x'
261
249
  }
250
+
262
251
  model.load!(hash: user)
252
+
263
253
  model.validate
264
254
  #=> false
255
+
265
256
  model.errors.full_messages
266
257
  #=> ["Age is not a number"]
267
258
  ```
268
- #### Validate whether or not certain fields were loaded _and_ validate the data associated with these same fields
259
+ #### Validate whether or not certain fields were loaded *and* validate the data associated with these same fields
269
260
 
270
- If you simply want to validate the _data_ loaded into your model, simply add `ActiveModel` validation, just like you would any other `ActiveModel` model, see the [Add validators to my model](#add-validators-to-my-model) section.
261
+ If you simply want to validate the *data* loaded into your model, simply add `ActiveModel` validation, just like you would any other `ActiveModel` model (see the [Add validators to my model](#add-validators-to-my-model) section).
271
262
 
272
- If you want to validate whether or not particular fields were loaded _and_ field data associated with these same fields, you simply need to add the required fields and any other validation(s).
273
-
274
- For example:
263
+ If you want to validate whether or not particular fields were loaded *and* the field data associated with those same fields, you simply need to return the required fields from the `DecoLite#required_fields` method and add the appropriate validation(s); for example:
275
264
 
276
265
  ```ruby
277
266
  class Model < DecoLite::Model
@@ -281,6 +270,7 @@ class Model < DecoLite::Model
281
270
  %i[first last address age]
282
271
  end
283
272
  end
273
+
284
274
  model = Model.new
285
275
 
286
276
  model.validate
@@ -300,20 +290,27 @@ model.errors.full_messages
300
290
 
301
291
  #### Manually define attributes (fields) on my model
302
292
 
303
- Manually defining attributes on your subclass is possible, although there doesn't seem a valid reason to do so, since you can just use `DecoLite::Model#load!` to wire all this up for you automatically. However, if there _were_ a need to do this, you must add your `attr_reader` to the `DecoLite::Model@field_names` array, or an error will be raised _provided_ there are any conflicting field names being loaded using `DecoLite::Model#load!`. Note that the aforementioned error will be raised regardless of whether or not you set `options: { fields: :merge }`. This is because DecoLite considers any existing model attributes _not_ added to the model via `load!`to be native to the model object, and therefore will not allow you to create attr_accessors for existing model attributes because this can potentially be dangerous.
293
+ Manually defining attributes on your subclass is possible, although there doesn't seem a valid reason to do so, since you can just use `DecoLite::Model#load!` to wire all this up for you automatically. However, if there *were* a need to do this, you must add your `attr_reader` to the `DecoLite::Model@field_names` array, or an error will be raised _provided_ there are any conflicting field names being loaded using `DecoLite::Model#load!`. Note that the aforementioned error will be raised regardless of whether or not you set `options: { fields: :merge }`. This is because `DecoLite` considers any existing model attributes *not* added to the model via `load!`*to be native to the model object,* and therefore will not allow you to create `attr_accessors` and assign values to existing model attributes because this can potentially be dangerous.
304
294
 
305
- To avoid errors when manually defining model attributes that could potentially conflict with fields loaded using `DecoLite::Model#load!`, do the following:
295
+ To avoid errors when manually defining model attributes that could potentially conflict with fields loaded using `DecoLite::Model#load!`, you could do the following:
306
296
 
307
297
  ```ruby
308
298
  class JustBecauseYouCanDoesntMeanYouShould < DecoLite::Model
309
299
  attr_accessor :existing_field
310
300
 
311
- def initialize(options: {})
312
- super
313
-
301
+ def initialize(hash: {}, options: {})
302
+ # Make sure we add existing_field to @field_names before we call
303
+ # the base class initializer.
314
304
  @field_names = %i[existing_field]
305
+
306
+ super
315
307
  end
316
308
  end
309
+
310
+ model = JustBecauseYouCanDoesntMeanYouShould.new(hash: { existing_field: :value })
311
+
312
+ model.existing_field
313
+ #=> :value
317
314
  ```
318
315
 
319
316
  However, the above is unnecessary as this can be easily accomplished by passing a `Hash` to the initializer or by using `DecoLite::Model#load!`:
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Defines the version of this gem.
4
4
  module DecoLite
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deco_lite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gene M. Angelo, Jr.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-09-30 00:00:00.000000000 Z
11
+ date: 2022-10-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel