deco_lite 0.3.0 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.reek.yml +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +13 -0
- data/Gemfile.lock +3 -3
- data/README.md +138 -19
- data/lib/deco_lite/field_assignable.rb +2 -4
- data/lib/deco_lite/field_conflictable.rb +2 -0
- data/lib/deco_lite/field_requireable.rb +3 -1
- data/lib/deco_lite/hash_loadable.rb +2 -0
- data/lib/deco_lite/hashable.rb +5 -1
- data/lib/deco_lite/model.rb +0 -1
- data/lib/deco_lite/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77c440561731ca11332b76f0ee373e4568d4e84c11930d530d1dde9d5c097eb9
|
4
|
+
data.tar.gz: f771f70f192ae1c2b2d7a935933f7157cde64c6f44e9dc60d50e66d58e8abb05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6aa192d5c0d1c68860a4e41c31c3997307e90560d293ba6d2e1f5d30a82f537358988cfba766f34415c8321041f3844c2ac88d176a818647f13474f86171c3f6
|
7
|
+
data.tar.gz: e7910463ad7ede8f002af8bc0f46c220bcb715dce09d1690c10b3a3af7db66577acd372a607698470b2fd168a8295d3b24c1fd705601064f02e2f54e0f5ef350
|
data/.gitignore
CHANGED
data/.reek.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,16 @@
|
|
1
|
+
### 0.3.2
|
2
|
+
* Changes
|
3
|
+
* Refactor FieldAssignable to remove call to FieldCreatable#create_field_accessor as this breaks single responsibility rule; which, in this case, makes sense to remove. FieldCreatable#create_field_accessor can be called wherever creation of a attr_accessor is needed.
|
4
|
+
* Refactor specs in keeping with above changes.
|
5
|
+
* README.md changes.
|
6
|
+
* Bugs
|
7
|
+
* Fix bug where loading fields with the options: { fields: :strict } option raises an error for field that already exists.
|
8
|
+
|
9
|
+
### 0.3.1
|
10
|
+
* Changes
|
11
|
+
* Added `DecoLite::FieldRequireable::MISSING_REQUIRED_FIELD_ERROR_TYPE` for required field type errors.
|
12
|
+
* Update README.md with more examples.
|
13
|
+
|
1
14
|
### 0.3.0
|
2
15
|
* Changes
|
3
16
|
* `DecoLite::Model#new` how accepts a :hash named parameter that will load the Hash as if calling `DecoLite::Model.new.load!(hash: <hash>)`.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
deco_lite (0.3.
|
4
|
+
deco_lite (0.3.3)
|
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)
|
@@ -25,7 +25,7 @@ GEM
|
|
25
25
|
docile (1.4.0)
|
26
26
|
i18n (1.12.0)
|
27
27
|
concurrent-ruby (~> 1.0)
|
28
|
-
immutable_struct_ex (0.2.
|
28
|
+
immutable_struct_ex (0.2.3)
|
29
29
|
json (2.6.2)
|
30
30
|
kwalify (0.7.2)
|
31
31
|
mad_flatter (1.0.1.pre.beta)
|
@@ -63,7 +63,7 @@ GEM
|
|
63
63
|
diff-lcs (>= 1.2.0, < 2.0)
|
64
64
|
rspec-support (~> 3.11.0)
|
65
65
|
rspec-support (3.11.0)
|
66
|
-
rubocop (1.35.
|
66
|
+
rubocop (1.35.1)
|
67
67
|
json (~> 2.3)
|
68
68
|
parallel (~> 1.10)
|
69
69
|
parser (>= 3.1.2.1)
|
data/README.md
CHANGED
@@ -13,13 +13,13 @@
|
|
13
13
|
|
14
14
|
## Introduction
|
15
15
|
|
16
|
-
DecoLite is in development. I wouldn't expect breaking changes before v1.0.0; however, I can't completely rule this out. Currently, DecoLite only supports Hashes whose keys are `Symbols`, contain no embedded spaces, and conform to Ruby `attr_accessor` naming conventions. However, I
|
16
|
+
DecoLite is in development. I wouldn't expect breaking changes before v1.0.0; however, I can't completely rule this out. Currently, DecoLite only supports Hashes whose keys are `Symbols`, contain no embedded spaces, and conform to Ruby `attr_accessor` naming conventions. However, I might try work out a reasonable solution for all this in future releases if the need is there.
|
17
17
|
|
18
|
-
TBD: Documentation regarding `DecoLite::Model` options, `DecoLite::Model#load!` options: how these work, and how they play together (in the meantime, see the specs).
|
18
|
+
TBD: Documentation regarding `DecoLite::Model` options, `DecoLite::Model#load!` options: how these work, and how they play together (e.g. options `fields: :merge` and `fields: :strict` for example; in the meantime, see the specs).
|
19
19
|
|
20
20
|
_Deco_ is a little gem that allows you to use the provided `DecoLite::Model` class (`include ActiveModel::Model`) to create Decorator classes which can be instantiated and used. 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.
|
21
21
|
|
22
|
-
A `DecoLite::Model` will allow you to consume a Ruby Hash that you supply 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.
|
22
|
+
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.
|
23
23
|
|
24
24
|
`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:
|
25
25
|
|
@@ -34,10 +34,11 @@ family = {
|
|
34
34
|
}
|
35
35
|
}
|
36
36
|
```
|
37
|
-
Given the above example, DecoLite will produce the following `attr_accessors` on the `DecoLite::Model` object
|
37
|
+
Given the above example, DecoLite will produce the following `attr_accessors` on the `DecoLite::Model` object and assign the values:
|
38
38
|
|
39
39
|
```ruby
|
40
|
-
|
40
|
+
# Or DecoLite::Model.new.load!(hash: family)
|
41
|
+
model = DecoLite::Model.new(hash: family)
|
41
42
|
|
42
43
|
model.name #=> 'John Doe'
|
43
44
|
model.respond_to? :name= #=> true
|
@@ -52,17 +53,21 @@ model.wife_age #=> 30
|
|
52
53
|
model.respond_to? :wife_age= #=> true
|
53
54
|
```
|
54
55
|
|
55
|
-
`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.
|
56
|
+
`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.
|
57
|
+
|
58
|
+
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:
|
56
59
|
|
57
60
|
```ruby
|
58
61
|
grandpa = {
|
59
62
|
name: 'Henry Doe',
|
60
63
|
age: 85,
|
61
64
|
}
|
62
|
-
# The :name and :age Hash keys above will produce :name/:name= and :age/:age= attr_accessors
|
65
|
+
# The :name and :age Hash keys above will produce :name/:name= and :age/:age= attr_accessors
|
66
|
+
# and clash because these were already added to the model when "John Doe" was loaded with
|
67
|
+
# the first call to DecoLite::Model.new(hash: family).
|
63
68
|
```
|
64
69
|
|
65
|
-
However, passing a `
|
70
|
+
However, passing a `:namespace` option (for example `namespace: :grandpa`) to the `DecoLite::Model#load!` method, would produce the following `attr_accessors`, ensuring their uniqueness:
|
66
71
|
|
67
72
|
```ruby
|
68
73
|
model.load!(hash: grandpa, options: { namespace: :grandpa })
|
@@ -87,6 +92,11 @@ model.respond_to? :wife_name= #=> true
|
|
87
92
|
model.wife_age #=> 30
|
88
93
|
model.respond_to? :wife_age= #=> true
|
89
94
|
```
|
95
|
+
|
96
|
+
### For more examples and usage
|
97
|
+
|
98
|
+
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`.
|
99
|
+
|
90
100
|
## Use cases
|
91
101
|
|
92
102
|
### General
|
@@ -108,9 +118,7 @@ class ViewModel < DecoLite::Model
|
|
108
118
|
end
|
109
119
|
end
|
110
120
|
|
111
|
-
view_model = ViewModel.new
|
112
|
-
|
113
|
-
view_model.load!(hash: { first: 'John', last: 'Doe' })
|
121
|
+
view_model = ViewModel.new(hash: { first: 'John', last: 'Doe' })
|
114
122
|
|
115
123
|
view_model.valid?
|
116
124
|
#=> true
|
@@ -188,22 +196,131 @@ model.wife_info_address #=> 1 street, boonton, nj 07005
|
|
188
196
|
|
189
197
|
#### Add validators to my model
|
190
198
|
|
191
|
-
Simply add your `ActiveModel` validators just like you would any other `ActiveModel::Model` validator. However, be aware that (currently), any attribute (field)
|
199
|
+
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`:
|
192
200
|
|
193
201
|
```ruby
|
194
202
|
class Model < DecoLite::Model
|
195
203
|
validates :first, :last, :address, presence: true
|
204
|
+
validates :age, numericality: true
|
196
205
|
end
|
197
206
|
|
198
|
-
|
207
|
+
# No :address
|
208
|
+
model = Model.new(hash: { first: 'John', last: 'Doe', age: 25 })
|
209
|
+
model.respond_to? :address
|
210
|
+
#=> true
|
211
|
+
|
212
|
+
model.valid?
|
213
|
+
#=> false
|
214
|
+
model.errors.full_messages
|
215
|
+
#=> ["Address can't be blank"]
|
199
216
|
|
200
|
-
|
217
|
+
model.load!(hash: { address: '123 park road, anytown, nj 01234' })
|
201
218
|
model.validate
|
202
|
-
#=>
|
219
|
+
#=> true
|
220
|
+
```
|
203
221
|
|
204
|
-
|
222
|
+
#### Validate whether or not certain fields were loaded
|
223
|
+
|
224
|
+
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_ (`attr_accessors`) were created on your model as a result of loading data into your model. 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.
|
225
|
+
|
226
|
+
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 do a few things:
|
227
|
+
- Create a `DecoLite::Model` subclass.
|
228
|
+
- Override the `DecoLite::Model#required_fields` method to return the field names you want to validate.
|
229
|
+
- Use the `required_fields: nil` option when instantiating your model object.
|
230
|
+
- DO NOT add `ActiveModel` validators that _explicitly_ reference any field returned from `DecoLite::Model#required_fields`; this will cause `attr_accessors` to be created for these fields; consequently, `DecoLite::FieldRequireable#validate_required_fields` will *never* return any errors because these fields will exist as attributes on your model. In other words, do not add (for example) `validates :first, :last, :address, presence: true` to your model if you need to validate whether or not the data you load into your model includs fields `:first`, `:last` and `:address`.
|
231
|
+
|
232
|
+
For example:
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
class Model < DecoLite::Model
|
236
|
+
# :age field is optional and it's value is optional.
|
237
|
+
validates :age, numericality: { only_integer: true }, allow_blank: true
|
238
|
+
|
239
|
+
def required_fields
|
240
|
+
# We want to ensure these fields were included as Hash keys during loading.
|
241
|
+
%i(first last address)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
```
|
245
|
+
|
246
|
+
Option `required_fields: :auto` is the default which will automatically create `attr_accessors` for any field returned from the `DecoLite::Model#required_fields` method; therefore, we need to set the `:required_fields` option to `nil` (i.e. `required_fields: nil`). This will prohibit `DecoLite::Model` from automatically creating `attr_accessors` for `:first`, `:last` and `:address`, and achieve the results we want:
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
model = Model.new(options: { required_fields: nil })
|
250
|
+
|
251
|
+
model.validate
|
252
|
+
#=> false
|
253
|
+
model.errors.full_messages
|
254
|
+
#=> ["First field is missing", "Last field is missing", "Address field is missing"]
|
255
|
+
|
256
|
+
# If we load data that includes :first, :last, and :address Hash keys even with
|
257
|
+
# nil data, our ":<field> field is missing" errors go away; in this scenario,
|
258
|
+
# we're validating the presence of the FIELDS, not the data associated with
|
259
|
+
# these fields!
|
260
|
+
model.load!(hash: { first: nil, last: nil, address: nil })
|
205
261
|
model.validate
|
206
262
|
#=> true
|
263
|
+
model.errors.full_messages
|
264
|
+
#=> []
|
265
|
+
|
266
|
+
user = {
|
267
|
+
first: 'John',
|
268
|
+
last: 'Doe',
|
269
|
+
address: '123 anystreet, anytown, nj 01234',
|
270
|
+
age: 'x'
|
271
|
+
}
|
272
|
+
model.load!(hash: user)
|
273
|
+
model.validate
|
274
|
+
#=> false
|
275
|
+
model.errors.full_messages
|
276
|
+
#=> ["Age is not a number"]
|
277
|
+
```
|
278
|
+
#### Validate whether or not certain fields were loaded _and_ validate the data associated with these same fields
|
279
|
+
|
280
|
+
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.
|
281
|
+
|
282
|
+
If you want to validate whether or not particular fields were loaded _and_ field data associated with these same fields, you'll have to use custom validation (e.g. override `DecoLite::FieldRequireable#validate_required_fields` and manually add your own validation and errors). This is because `DecoLite::Model#new` will automatically create `attr_accessors` for any attribute (field) that has an _explicit_ `ActiveModel` validation associated with it, and return false positives when you validate your model. In addition to this, you will need to do several other things outlined in the [Validate whether or not certain fields were loaded](#validate-whether-or-not-certain-fields-were-loaded) section.
|
283
|
+
|
284
|
+
For example:
|
285
|
+
|
286
|
+
```ruby
|
287
|
+
class Model < DecoLite::Model
|
288
|
+
def required_fields
|
289
|
+
%i(first last address age)
|
290
|
+
end
|
291
|
+
|
292
|
+
def validate_required_fields
|
293
|
+
super
|
294
|
+
|
295
|
+
first = self.try(:first)
|
296
|
+
errors.add(:first, "can't be blank") if first.nil?
|
297
|
+
|
298
|
+
last = self.try(:last)
|
299
|
+
errors.add(:last, "can't be blank") if last.nil?
|
300
|
+
|
301
|
+
address = self.try(:address)
|
302
|
+
errors.add(:address, "can't be blank") if address.nil?
|
303
|
+
|
304
|
+
age = self.try(:age)
|
305
|
+
errors.add(:age, "can't be blank") if age.nil?
|
306
|
+
errors.add(:age, 'is not a number') unless /\d+/ =~ age
|
307
|
+
end
|
308
|
+
end
|
309
|
+
model = Model.new(options: { required_fields: nil })
|
310
|
+
|
311
|
+
model.validate
|
312
|
+
#=> false
|
313
|
+
|
314
|
+
model.errors.full_messages
|
315
|
+
#=> ["First field is missing",
|
316
|
+
"Last field is missing",
|
317
|
+
"Address field is missing",
|
318
|
+
"Age field is missing",
|
319
|
+
"First can't be blank",
|
320
|
+
"Last can't be blank",
|
321
|
+
"Address can't be blank",
|
322
|
+
"Age can't be blank",
|
323
|
+
"Age is not a number"]
|
207
324
|
```
|
208
325
|
|
209
326
|
#### Manually define attributes (fields) on my model
|
@@ -224,19 +341,21 @@ class JustBecauseYouCanDoesntMeanYouShould < DecoLite::Model
|
|
224
341
|
end
|
225
342
|
```
|
226
343
|
|
227
|
-
However, the above is unnecessary as this can be easily accomplished using `DecoLite::Model#load!`:
|
344
|
+
However, the above is unnecessary as this can be easily accomplished by passing a `Hash` to the initializer or by using `DecoLite::Model#load!`:
|
345
|
+
|
228
346
|
```ruby
|
229
|
-
model = Class.new(DecoLite::Model).new
|
347
|
+
model = Class.new(DecoLite::Model).new(hash:{ existing_field: :value })
|
230
348
|
|
231
349
|
model.field_names
|
232
350
|
#=> [:existing_field]
|
233
351
|
|
234
352
|
model.existing_field
|
235
|
-
#=> :
|
353
|
+
#=> :value
|
236
354
|
|
237
355
|
model.respond_to? :existing_field=
|
238
356
|
#=> true
|
239
357
|
```
|
358
|
+
|
240
359
|
## Installation
|
241
360
|
|
242
361
|
Add this line to your application's Gemfile:
|
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'field_creatable'
|
4
3
|
require_relative 'field_retrievable'
|
5
4
|
|
6
5
|
module DecoLite
|
7
6
|
# Defines methods to assign model field values dynamically.
|
8
7
|
module FieldAssignable
|
9
|
-
include FieldCreatable
|
10
8
|
include FieldRetrievable
|
11
9
|
|
12
10
|
def set_field_values(hash:, field_info:, options:)
|
@@ -16,10 +14,10 @@ module DecoLite
|
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
17
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
19
18
|
def set_field_value(field_name:, value:, options:)
|
20
|
-
# Create our fields before we send.
|
21
|
-
create_field_accessor field_name: field_name, options: options
|
22
19
|
send("#{field_name}=", value)
|
23
20
|
end
|
21
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
24
22
|
end
|
25
23
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'field_name_namespaceable'
|
4
|
+
require_relative 'field_requireable'
|
4
5
|
require_relative 'fields_optionable'
|
5
6
|
|
6
7
|
module DecoLite
|
@@ -9,6 +10,7 @@ module DecoLite
|
|
9
10
|
module FieldConflictable
|
10
11
|
include FieldNameNamespaceable
|
11
12
|
include FieldsOptionable
|
13
|
+
include FieldRequireable
|
12
14
|
|
13
15
|
def validate_field_conflicts!(field_name:, options:)
|
14
16
|
return unless field_conflict?(field_name: field_name, options: options)
|
@@ -4,6 +4,8 @@ module DecoLite
|
|
4
4
|
# Provides methods to manage fields that must be defined from
|
5
5
|
# the dynamically loaded data.
|
6
6
|
module FieldRequireable
|
7
|
+
MISSING_REQUIRED_FIELD_ERROR_TYPE = :missing_required_field
|
8
|
+
|
7
9
|
# Returns field names that will be used to validate the presence of
|
8
10
|
# dynamically created fields from loaded objects.
|
9
11
|
#
|
@@ -23,7 +25,7 @@ module DecoLite
|
|
23
25
|
required_fields.each do |field_name|
|
24
26
|
next if required_field_exist? field_name: field_name
|
25
27
|
|
26
|
-
errors.add(field_name, 'field is missing', type:
|
28
|
+
errors.add(field_name, 'field is missing', type: MISSING_REQUIRED_FIELD_ERROR_TYPE)
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
require 'mad_flatter'
|
4
4
|
require_relative 'field_assignable'
|
5
|
+
require_relative 'field_creatable'
|
5
6
|
|
6
7
|
module DecoLite
|
7
8
|
# Provides methods to load and return information about a given hash.
|
8
9
|
module HashLoadable
|
9
10
|
include FieldAssignable
|
11
|
+
include FieldCreatable
|
10
12
|
|
11
13
|
private
|
12
14
|
|
data/lib/deco_lite/hashable.rb
CHANGED
@@ -5,7 +5,11 @@ module DecoLite
|
|
5
5
|
module Hashable
|
6
6
|
def to_h
|
7
7
|
field_names.each_with_object({}) do |field_name, hash|
|
8
|
-
|
8
|
+
field_value = public_send(field_name)
|
9
|
+
|
10
|
+
field_name, field_value = yield [field_name, field_value] if block_given?
|
11
|
+
|
12
|
+
hash[field_name] = field_value
|
9
13
|
end
|
10
14
|
end
|
11
15
|
end
|
data/lib/deco_lite/model.rb
CHANGED
data/lib/deco_lite/version.rb
CHANGED
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: 0.3.
|
4
|
+
version: 0.3.3
|
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-08-
|
11
|
+
date: 2022-08-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|