granite-form 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +2 -0
- data/.rubocop_todo.yml +1 -1
- data/CHANGELOG.md +9 -66
- data/README.md +53 -51
- data/Rakefile +4 -0
- data/granite-form.gemspec +1 -0
- data/lib/granite/form/config.rb +10 -10
- data/lib/granite/form/model/associations/nested_attributes.rb +3 -3
- data/lib/granite/form/model/associations/reflections/references_many.rb +3 -1
- data/lib/granite/form/model/associations/reflections/references_one.rb +3 -1
- data/lib/granite/form/model/attributes/attribute.rb +1 -1
- data/lib/granite/form/model/attributes/base.rb +13 -16
- data/lib/granite/form/model/attributes/collection.rb +1 -1
- data/lib/granite/form/model/attributes/dictionary.rb +1 -1
- data/lib/granite/form/model/attributes/localized.rb +1 -1
- data/lib/granite/form/model/attributes/reference_many.rb +1 -1
- data/lib/granite/form/model/attributes/reference_one.rb +1 -9
- data/lib/granite/form/model/attributes/reflections/base.rb +1 -5
- data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -4
- data/lib/granite/form/model/representation.rb +1 -1
- data/lib/granite/form/model/validations/nested.rb +1 -1
- data/lib/granite/form/types/active_support/time_zone.rb +22 -0
- data/lib/granite/form/types/array.rb +17 -0
- data/lib/granite/form/types/big_decimal.rb +15 -0
- data/lib/granite/form/types/boolean.rb +38 -0
- data/lib/granite/form/types/date.rb +15 -0
- data/lib/granite/form/types/date_time.rb +15 -0
- data/lib/granite/form/types/float.rb +15 -0
- data/lib/granite/form/types/hash_with_action_controller_parameters.rb +18 -0
- data/lib/granite/form/types/integer.rb +13 -0
- data/lib/granite/form/types/object.rb +30 -0
- data/lib/granite/form/types/string.rb +13 -0
- data/lib/granite/form/types/time.rb +15 -0
- data/lib/granite/form/types/uuid.rb +22 -0
- data/lib/granite/form/types.rb +15 -0
- data/lib/granite/form/version.rb +1 -1
- data/lib/granite/form.rb +19 -118
- data/spec/{lib/granite → granite}/form/active_record/associations_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/active_record/nested_attributes_spec.rb +0 -1
- data/spec/{lib/granite → granite}/form/config_spec.rb +22 -10
- data/spec/granite/form/extensions_spec.rb +12 -0
- data/spec/{lib/granite → granite}/form/model/associations/embeds_many_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/embeds_one_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/nested_attributes_spec.rb +0 -1
- data/spec/{lib/granite → granite}/form/model/associations/persistence_adapters/active_record_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/references_many_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/references_one_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_any_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_many_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_one_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/reflections/references_many_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/reflections/references_one_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations/validations_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/associations_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/attribute_spec.rb +4 -46
- data/spec/{lib/granite → granite}/form/model/attributes/base_spec.rb +11 -2
- data/spec/{lib/granite → granite}/form/model/attributes/collection_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/dictionary_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/localized_spec.rb +1 -1
- data/spec/{lib/granite → granite}/form/model/attributes/reflections/attribute_spec.rb +0 -12
- data/spec/{lib/granite → granite}/form/model/attributes/reflections/base_spec.rb +1 -1
- data/spec/{lib/granite → granite}/form/model/attributes/reflections/collection_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/reflections/dictionary_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/reflections/localized_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/reflections/represents_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes/represents_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/attributes_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/callbacks_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/conventions_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/dirty_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/lifecycle_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/persistence_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/primary_spec.rb +1 -1
- data/spec/{lib/granite → granite}/form/model/representation_spec.rb +14 -2
- data/spec/{lib/granite → granite}/form/model/scopes_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/validations/associated_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model/validations/nested_spec.rb +55 -11
- data/spec/{lib/granite → granite}/form/model/validations_spec.rb +0 -0
- data/spec/{lib/granite → granite}/form/model_spec.rb +0 -0
- data/spec/granite/form/types/active_support/time_zone_spec.rb +24 -0
- data/spec/granite/form/types/array_spec.rb +13 -0
- data/spec/granite/form/types/big_decimal_spec.rb +19 -0
- data/spec/granite/form/types/boolean_spec.rb +21 -0
- data/spec/granite/form/types/date_spec.rb +18 -0
- data/spec/granite/form/types/date_time_spec.rb +20 -0
- data/spec/granite/form/types/float_spec.rb +19 -0
- data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +22 -0
- data/spec/granite/form/types/integer_spec.rb +18 -0
- data/spec/granite/form/types/object_spec.rb +40 -0
- data/spec/granite/form/types/string_spec.rb +13 -0
- data/spec/granite/form/types/time_spec.rb +31 -0
- data/spec/granite/form/types/uuid_spec.rb +21 -0
- data/spec/{lib/granite → granite}/form_spec.rb +0 -0
- data/spec/spec_helper.rb +1 -2
- data/spec/{shared → support/shared}/nested_attribute_examples.rb +0 -0
- data/spec/support/shared/type_examples.rb +7 -0
- data/spec/support/translations.rb +6 -0
- metadata +150 -91
- data/spec/lib/granite/form/model/typecasting_spec.rb +0 -193
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebac9979a786e2a3638b1dd7b21648320c779770c10f7fe63707b38a58c0164c
|
4
|
+
data.tar.gz: 604dcf0abda92863e1503b0d977a53ca1de7a39823bfc8a4aabab8d9c6a27045
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfc2c624834fe5d0ad4fadb903e6b9a38eed17778cea3c0cf9430437fb095436429c1a0941fd418e143805b064c1a74c0c959fb57493d96fb9602cdca625f36d
|
7
|
+
data.tar.gz: 4937e3d02a378edf48daa770799206b3ecdb91ee71ed1025169e6e3bf4a25bc157c01aea9db54dcb70645728eeb0055ca42be7db23258f0b7f2744d8f7415dd9
|
data/.github/CODEOWNERS
ADDED
data/.rubocop_todo.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,73 +1,16 @@
|
|
1
1
|
# master
|
2
2
|
|
3
|
-
|
3
|
+
## Next
|
4
|
+
## v0.2.0
|
4
5
|
|
5
|
-
|
6
|
+
- Replace typecasters with proper type definitions.
|
7
|
+
- Instead of `typecaster(type) { |value, _| ... }` you'll have to use `typecaster(type) { |value| ... }`.
|
8
|
+
- Consequently you can access type definition in typecaster, e.g. `typecaster('Object') { |value| value if value.class < type }`, here `type` comes from type definition.
|
6
9
|
|
7
|
-
|
10
|
+
## v0.1.1
|
8
11
|
|
9
|
-
|
12
|
+
- Fixed represented error message copying when represented model uses symbols for `message`.
|
10
13
|
|
11
|
-
|
14
|
+
## v0.1.0
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
# Version 1.1.5
|
16
|
-
|
17
|
-
* Rails 6 support (#70, #71)
|
18
|
-
|
19
|
-
# Version 1.1.4
|
20
|
-
|
21
|
-
## Changes
|
22
|
-
|
23
|
-
* `came_from_default?` attribute method (#66)
|
24
|
-
|
25
|
-
# Version 1.1.3
|
26
|
-
|
27
|
-
## Changes
|
28
|
-
|
29
|
-
* `came_from_user?` attribute method (#65)
|
30
|
-
|
31
|
-
# Version 1.1.2
|
32
|
-
|
33
|
-
## Changes
|
34
|
-
|
35
|
-
* `ActiveData.base_concern` config option in addition to `ActiveData.base_class` (#64)
|
36
|
-
|
37
|
-
# Version 1.1.1
|
38
|
-
|
39
|
-
## Changes
|
40
|
-
|
41
|
-
* `ActiveData.base_class` config option (#63)
|
42
|
-
|
43
|
-
* ActiveModel 5.2 compatibility (#62)
|
44
|
-
|
45
|
-
# Version 1.1.0
|
46
|
-
|
47
|
-
## Incompatible changes
|
48
|
-
|
49
|
-
* Represented attributes are not provided by default, to add them, `include ActiveData::Model::Representation` (#46)
|
50
|
-
|
51
|
-
* `include ActiveData::Model::Associations::Validations` is not included by default anymore, to get `validate_ancestry!`, `valid_ancestry?` and `invalid_ancestry?` methods back you need to include this module manually
|
52
|
-
|
53
|
-
## Changes
|
54
|
-
|
55
|
-
* Introduce persistence adapters for associations (#24, #51)
|
56
|
-
|
57
|
-
* `ActionController::Parameters` support (#43)
|
58
|
-
|
59
|
-
* Nested attributes simple method overriding (#41)
|
60
|
-
|
61
|
-
* Persistence for `references` associations (#28, #32)
|
62
|
-
|
63
|
-
* Support `update_only` option on collection nested attributes (#30)
|
64
|
-
|
65
|
-
* `embedder` accessor for embedded associations
|
66
|
-
|
67
|
-
* Dynamic scopes for `references` associations (#27)
|
68
|
-
|
69
|
-
## Bugfixes
|
70
|
-
|
71
|
-
* Fixed multiple validations on represented attributes and associations (#44)
|
72
|
-
|
73
|
-
* Proper boolean attributes defaults (#31, #33)
|
16
|
+
- Forked from ActiveData, see https://github.com/pyromaniac/active_data/blob/v1.2.0/CHANGELOG.md for changes before this
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
# Granite::Form
|
5
5
|
|
6
|
-
Granite::Form is
|
6
|
+
`Granite::Form` is an `ActiveModel`-based front-end for your data. It is useful in the following cases:
|
7
7
|
|
8
8
|
* When you need a form objects pattern.
|
9
9
|
|
@@ -43,7 +43,7 @@ class ProfileController < ApplicationController
|
|
43
43
|
end
|
44
44
|
```
|
45
45
|
|
46
|
-
* When you need to work with data
|
46
|
+
* When you need to work with data storage à la `ActiveRecord`.
|
47
47
|
|
48
48
|
```ruby
|
49
49
|
class Flight
|
@@ -75,7 +75,7 @@ class Flight
|
|
75
75
|
end
|
76
76
|
```
|
77
77
|
|
78
|
-
* When you need to
|
78
|
+
* When you need to embed objects in `ActiveRecord` models.
|
79
79
|
|
80
80
|
```ruby
|
81
81
|
class Answer
|
@@ -94,25 +94,25 @@ class Quiz < ActiveRecord::Base
|
|
94
94
|
validates :answers, associated: true
|
95
95
|
end
|
96
96
|
|
97
|
-
|
98
|
-
|
99
|
-
|
97
|
+
quiz = Quiz.new
|
98
|
+
quiz.answers.build(question_id: 42, content: 'blabla')
|
99
|
+
quiz.save
|
100
100
|
```
|
101
101
|
|
102
102
|
## Why?
|
103
103
|
|
104
|
-
Granite::Form is an ActiveModel-based library that provides the following
|
104
|
+
`Granite::Form` is an `ActiveModel-based library that provides the following functionalities:
|
105
105
|
|
106
106
|
* Standard form objects building toolkit: attributes with typecasting, validations, etc.
|
107
107
|
* High-level universal ORM/ODM library using any data source (DB, http, redis, text files).
|
108
|
-
* Embedding objects into
|
108
|
+
* Embedding objects into ActiveRecord entities. Quite useful with PG JSON capabilities.
|
109
109
|
|
110
110
|
Key features:
|
111
111
|
|
112
|
-
* Complete
|
112
|
+
* Complete object lifecycle support: saving, updating, destroying.
|
113
113
|
* Embedded and referenced associations.
|
114
|
-
* Backend-agnostic named scopes
|
115
|
-
* Callbacks, validations and dirty attributes
|
114
|
+
* Backend-agnostic named scopes.
|
115
|
+
* Callbacks, validations and dirty attributes.
|
116
116
|
|
117
117
|
## Installation
|
118
118
|
|
@@ -130,11 +130,11 @@ Or install it yourself as:
|
|
130
130
|
|
131
131
|
## Usage
|
132
132
|
|
133
|
-
Granite::Form has modular architecture, so it is required to include modules to obtain additional features. By default Granite::Form supports attributes definition and validations.
|
133
|
+
`Granite::Form` has modular architecture, so it is required to include modules to obtain additional features. By default `Granite::Form` supports attributes definition and validations.
|
134
134
|
|
135
135
|
### Attributes
|
136
136
|
|
137
|
-
Granite::Form provides several types of attributes and typecasts each attribute to its defined type upon initialization.
|
137
|
+
`Granite::Form` provides several types of attributes and typecasts each attribute to its defined type upon initialization.
|
138
138
|
|
139
139
|
```ruby
|
140
140
|
class Book
|
@@ -151,16 +151,16 @@ end
|
|
151
151
|
attribute :full_name, String, default: 'John Talbot'
|
152
152
|
```
|
153
153
|
|
154
|
-
|
154
|
+
If type for an attribute is not set, it defaults to `Object`. It is therefore recommended to specify the type for every attribute explicitly.
|
155
155
|
|
156
|
-
|
156
|
+
The type is necessary for attribute typecasting. Here is the list of pre-defined basic typecasters:
|
157
157
|
|
158
158
|
```irb
|
159
159
|
[1] pry(main)> Granite::Form._typecasters.keys
|
160
160
|
=> ["Object", "String", "Array", "Hash", "Date", "DateTime", "Time", "ActiveSupport::TimeZone", "BigDecimal", "Float", "Integer", "Boolean", "Granite::Form::UUID"]
|
161
161
|
```
|
162
162
|
|
163
|
-
In addition, you can provide any class type when defining
|
163
|
+
In addition, you can provide any class type when defining an attribute, but in that case you will be able to only assign instances of that specific class or `nil`:
|
164
164
|
|
165
165
|
```ruby
|
166
166
|
attribute :template, MyCustomTemplateType
|
@@ -168,7 +168,7 @@ attribute :template, MyCustomTemplateType
|
|
168
168
|
|
169
169
|
##### Defaults
|
170
170
|
|
171
|
-
It is possible to provide default values for attributes and they will act in the same way as
|
171
|
+
It is possible to provide default values for attributes and they will act in the same way as `ActiveRecord` or `Mongoid` default values:
|
172
172
|
|
173
173
|
```ruby
|
174
174
|
attribute :check, Boolean, default: false # Simply false by default
|
@@ -179,7 +179,7 @@ attribute :today_wday, Integer, default: ->(instance) { instance.today.wday } #
|
|
179
179
|
|
180
180
|
##### Enums
|
181
181
|
|
182
|
-
Enums restrict the scope of possible values for attribute. If assigned value is not included in provided list
|
182
|
+
Enums restrict the scope of possible values for an attribute. If the assigned value is not included in the provided list, the attribute value is set to `nil`:
|
183
183
|
|
184
184
|
```ruby
|
185
185
|
attribute :direction, String, enum: %w[north south east west]
|
@@ -187,7 +187,7 @@ attribute :direction, String, enum: %w[north south east west]
|
|
187
187
|
|
188
188
|
##### Normalizers
|
189
189
|
|
190
|
-
Normalizers are applied last, modifying typecast value. It is possible to provide a list of normalizers
|
190
|
+
Normalizers are applied last, modifying a typecast value. It is possible to provide a list of normalizers. They will be applied in the provided order. It is possible to pre-define normalizers to DRY code:
|
191
191
|
|
192
192
|
```ruby
|
193
193
|
Granite::Form.normalizer(:trim) do |value, options, _attribute|
|
@@ -201,13 +201,13 @@ attribute :title, String, normalizers: [->(value) { value.strip }, trim: {length
|
|
201
201
|
|
202
202
|
```ruby
|
203
203
|
attribute :name, String, readonly: true # Readonly forever
|
204
|
-
attribute :name, String, readonly: ->{ true } # Conditionally readonly
|
204
|
+
attribute :name, String, readonly: -> { true } # Conditionally readonly
|
205
205
|
attribute :name, String, readonly: ->(instance) { instance.subject.present? } # Explicit instance
|
206
206
|
```
|
207
207
|
|
208
208
|
#### Collection
|
209
209
|
|
210
|
-
|
210
|
+
A collection is simply an array of equally-typed values:
|
211
211
|
|
212
212
|
```ruby
|
213
213
|
class Panda
|
@@ -217,7 +217,7 @@ class Panda
|
|
217
217
|
end
|
218
218
|
```
|
219
219
|
|
220
|
-
|
220
|
+
A collection typecasts each value to the specified type. Also, it normalizes any given value to an array.
|
221
221
|
|
222
222
|
```irb
|
223
223
|
[1] pry(main)> Panda.new
|
@@ -228,11 +228,11 @@ Collection typecasts each value to specified type and also no matter what are yo
|
|
228
228
|
=> #<Panda ids: [42, 33]>
|
229
229
|
```
|
230
230
|
|
231
|
-
Default and enum modifiers are applied
|
231
|
+
Default and enum modifiers are applied on each value, normalizers are applied on the array.
|
232
232
|
|
233
233
|
#### Dictionary
|
234
234
|
|
235
|
-
|
235
|
+
A dictionary field is a hash of specified type values with string keys:
|
236
236
|
|
237
237
|
```ruby
|
238
238
|
class Foo
|
@@ -249,11 +249,11 @@ end
|
|
249
249
|
=> #<Foo ordering: {"name"=>"desc"}>
|
250
250
|
```
|
251
251
|
|
252
|
-
|
252
|
+
The keys list might be restricted with the `:keys` option. Default and enum modifiers are applied on each value, normalizers are applied on the hash.
|
253
253
|
|
254
254
|
#### Localized
|
255
255
|
|
256
|
-
|
256
|
+
`localized` is similar to how `Globalize 3` attributes work.
|
257
257
|
|
258
258
|
```ruby
|
259
259
|
localized :title, String
|
@@ -261,11 +261,11 @@ localized :title, String
|
|
261
261
|
|
262
262
|
#### Represents
|
263
263
|
|
264
|
-
|
265
|
-
It will automatically set passed value to the represented object **before validation**.
|
266
|
-
You can use any ActiveRecord
|
267
|
-
|
268
|
-
If
|
264
|
+
`represents` provides an easy way to expose model attributes through an interface.
|
265
|
+
It will automatically set the passed value to the represented object **before validation**.
|
266
|
+
You can use any `ActiveRecord`, `ActiveModel` or `Granite::Form` object as a target of representation.
|
267
|
+
The type of an attribute will be taken from it.
|
268
|
+
If no type is defined, it will be `Object` by default. You can set the type explicitly by passing the `type: TypeClass` option.
|
269
269
|
Represents will also add automatic validation of the target object.
|
270
270
|
|
271
271
|
```ruby
|
@@ -295,7 +295,7 @@ person.name
|
|
295
295
|
|
296
296
|
### Associations
|
297
297
|
|
298
|
-
Granite::Form provides a set of associations. There are two types
|
298
|
+
`Granite::Form` provides a set of associations. There are two types: referenced and embedded. The closest example of referenced association is `AcitveRecord`'s `belongs_to`. For embedded ones - Mongoid's embedded. Also these associations support `accepts_nested_attributes` calls.
|
299
299
|
|
300
300
|
#### EmbedsOne
|
301
301
|
|
@@ -312,11 +312,11 @@ embeds_one :profile do
|
|
312
312
|
end
|
313
313
|
```
|
314
314
|
|
315
|
-
|
315
|
+
Оptions:
|
316
316
|
|
317
317
|
* `:class_name` - association class name
|
318
|
-
* `:validate` - true or false
|
319
|
-
* `:default` - default value for association: attributes hash or instance of defined class
|
318
|
+
* `:validate` - `true` or `false`
|
319
|
+
* `:default` - default value for the association: an attributes hash or an instance of the defined class
|
320
320
|
|
321
321
|
#### EmbedsMany
|
322
322
|
|
@@ -324,7 +324,7 @@ Possible options:
|
|
324
324
|
embeds_many :tags
|
325
325
|
```
|
326
326
|
|
327
|
-
Defines collection of embedded objects. Might be defined inline:
|
327
|
+
Defines a collection of embedded objects. Might be defined inline:
|
328
328
|
|
329
329
|
```ruby
|
330
330
|
embeds_many :tags do
|
@@ -332,9 +332,11 @@ embeds_many :tags do
|
|
332
332
|
end
|
333
333
|
```
|
334
334
|
|
335
|
+
Оptions:
|
336
|
+
|
335
337
|
* `:class_name` - association class name
|
336
|
-
* `:validate` -
|
337
|
-
* `:default` - default value for association: attributes hash
|
338
|
+
* `:validate` - `true` or `false`
|
339
|
+
* `:default` - default value for the association: an attributes hash or an instance of the defined class
|
338
340
|
|
339
341
|
#### ReferencesOne
|
340
342
|
|
@@ -342,22 +344,22 @@ end
|
|
342
344
|
references_one :user
|
343
345
|
```
|
344
346
|
|
345
|
-
|
347
|
+
Provides several methods to the object: `#user`, `#user=`, `#user_id` and `#user_id=`, similarly to an ActiveRecord association.
|
346
348
|
|
347
|
-
|
349
|
+
Оptions:
|
348
350
|
|
349
351
|
* `:class_name` - association class name
|
350
|
-
* `:primary_key` - associated object primary key (`:id` by default):
|
352
|
+
* `:primary_key` - the associated object's primary key name (`:id` by default):
|
351
353
|
|
352
354
|
```ruby
|
353
355
|
references_one :user, primary_key: :name
|
354
356
|
```
|
355
357
|
|
356
|
-
|
358
|
+
Creates the following methods: `#user`, `#user=`, `#user_name` and `#user_name=`.
|
357
359
|
|
358
360
|
* `:reference_key` - redefines `#user_id` and `#user_id=` method names completely.
|
359
|
-
* `:validate` - true or false
|
360
|
-
* `:default` - default value for association: reference or object itself
|
361
|
+
* `:validate` - `true` or `false`
|
362
|
+
* `:default` - default value for the association: reference or the object itself
|
361
363
|
|
362
364
|
#### ReferencesMany
|
363
365
|
|
@@ -365,18 +367,18 @@ Possible options:
|
|
365
367
|
references_many :users
|
366
368
|
```
|
367
369
|
|
368
|
-
|
370
|
+
Provides several methods to the object: `#users`, `#users=`, `#user_ids` and `#user_ids=`, similarly to an ActiveRecord association.
|
369
371
|
|
370
|
-
|
372
|
+
Options:
|
371
373
|
|
372
374
|
* `:class_name` - association class name
|
373
|
-
* `:primary_key` - associated object primary key (`:id` by default):
|
375
|
+
* `:primary_key` - the associated object's primary key name (`:id` by default):
|
374
376
|
|
375
377
|
```ruby
|
376
378
|
references_many :users, primary_key: :name
|
377
379
|
```
|
378
380
|
|
379
|
-
|
381
|
+
Creates the following methods: `#users`, `#users=`, `#user_names` and `#user_names=`.
|
380
382
|
|
381
383
|
* `:reference_key` - redefines `#user_ids` and `#user_ids=` method names completely.
|
382
384
|
* `:validate` - true or false
|
@@ -396,15 +398,15 @@ class Mongoid::Document
|
|
396
398
|
end
|
397
399
|
end
|
398
400
|
```
|
399
|
-
|
401
|
+
where
|
400
402
|
`ClassName` - name of model class or one of ancestors
|
401
403
|
`data_source` - name of data source class
|
402
404
|
`primary_key` - key to search data
|
403
405
|
`scope_proc` - additional proc for filtering
|
404
406
|
|
405
|
-
All
|
407
|
+
All requirements for the adapter interfaces are described in `Granite::Form::Model::Associations::PersistenceAdapters::Base`.
|
406
408
|
|
407
|
-
|
409
|
+
The adapter for `ActiveRecord` is `Granite::Form::Model::Associations::PersistenceAdapters::ActiveRecord`. All `ActiveRecord` models use `PersistenceAdapters::ActiveRecord` by default.
|
408
410
|
|
409
411
|
### Primary
|
410
412
|
|
data/Rakefile
CHANGED
data/granite-form.gemspec
CHANGED
@@ -17,6 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.add_development_dependency 'actionpack', '>= 4.0'
|
18
18
|
gem.add_development_dependency 'activerecord', '>= 4.0'
|
19
19
|
gem.add_development_dependency 'appraisal'
|
20
|
+
gem.add_development_dependency 'bump'
|
20
21
|
gem.add_development_dependency 'database_cleaner'
|
21
22
|
gem.add_development_dependency 'rake'
|
22
23
|
gem.add_development_dependency 'rspec', '~> 3.7.0'
|
data/lib/granite/form/config.rb
CHANGED
@@ -4,7 +4,7 @@ module Granite
|
|
4
4
|
include Singleton
|
5
5
|
|
6
6
|
attr_accessor :include_root_in_json, :i18n_scope, :logger, :primary_attribute, :base_class, :base_concern,
|
7
|
-
:_normalizers, :
|
7
|
+
:_normalizers, :types
|
8
8
|
|
9
9
|
def self.delegated
|
10
10
|
public_instance_methods - superclass.public_instance_methods - Singleton.public_instance_methods
|
@@ -16,7 +16,7 @@ module Granite
|
|
16
16
|
@logger = Logger.new(STDERR)
|
17
17
|
@primary_attribute = :id
|
18
18
|
@_normalizers = {}
|
19
|
-
@
|
19
|
+
@types = {}
|
20
20
|
end
|
21
21
|
|
22
22
|
def normalizer(name, &block)
|
@@ -27,16 +27,16 @@ module Granite
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def typecaster(
|
31
|
-
|
32
|
-
|
33
|
-
_typecasters[classes.first.to_s.camelize] = block
|
34
|
-
else
|
35
|
-
_typecasters[classes.detect do |klass|
|
36
|
-
_typecasters[klass.to_s.camelize]
|
37
|
-
end.to_s.camelize] or raise TypecasterMissing, classes
|
30
|
+
def typecaster(class_name, &block)
|
31
|
+
types[class_name.to_s.camelize] = Class.new(Types::Object) do
|
32
|
+
define_method(:typecast, &block)
|
38
33
|
end
|
39
34
|
end
|
35
|
+
|
36
|
+
def type_for(klass)
|
37
|
+
key = klass.ancestors.grep(Class).map(&:to_s).find(&types) or raise TypecasterMissing, klass
|
38
|
+
types.fetch(key)
|
39
|
+
end
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
@@ -74,7 +74,7 @@ module Granite
|
|
74
74
|
primary_attribute_name = primary_name_for(association.reflection.klass)
|
75
75
|
if existing_record
|
76
76
|
primary_attribute = existing_record.attribute(primary_attribute_name)
|
77
|
-
primary_attribute_value = primary_attribute.
|
77
|
+
primary_attribute_value = primary_attribute.type_definition.ensure_type(attributes[primary_attribute_name]) if primary_attribute
|
78
78
|
end
|
79
79
|
|
80
80
|
if existing_record && (!primary_attribute || options[:update_only] || existing_record.primary_attribute == primary_attribute_value)
|
@@ -123,7 +123,7 @@ module Granite
|
|
123
123
|
else
|
124
124
|
existing_record = association.target.detect do |record|
|
125
125
|
primary_attribute_value = record.attribute(primary_attribute_name)
|
126
|
-
.
|
126
|
+
.type_definition.ensure_type(attributes[primary_attribute_name])
|
127
127
|
record.primary_attribute == primary_attribute_value
|
128
128
|
end
|
129
129
|
if existing_record
|
@@ -162,7 +162,7 @@ module Granite
|
|
162
162
|
end
|
163
163
|
|
164
164
|
def self.destroy_flag?(hash)
|
165
|
-
|
165
|
+
Types::Boolean.typecast(hash[DESTROY_ATTRIBUTE])
|
166
166
|
end
|
167
167
|
|
168
168
|
def self.reject_new_object?(object, association_name, attributes, options)
|
@@ -12,7 +12,9 @@ module Granite
|
|
12
12
|
|
13
13
|
target.add_attribute(
|
14
14
|
Granite::Form::Model::Attributes::Reflections::ReferenceMany,
|
15
|
-
reflection.reference_key,
|
15
|
+
reflection.reference_key,
|
16
|
+
type: reflection.persistence_adapter.primary_key_type,
|
17
|
+
association: name
|
16
18
|
)
|
17
19
|
|
18
20
|
reflection
|
@@ -14,7 +14,9 @@ module Granite
|
|
14
14
|
|
15
15
|
target.add_attribute(
|
16
16
|
Granite::Form::Model::Attributes::Reflections::ReferenceOne,
|
17
|
-
reflection.reference_key,
|
17
|
+
reflection.reference_key,
|
18
|
+
type: reflection.persistence_adapter.primary_key_type,
|
19
|
+
association: name
|
18
20
|
)
|
19
21
|
|
20
22
|
reflection
|
@@ -3,19 +3,16 @@ module Granite
|
|
3
3
|
module Model
|
4
4
|
module Attributes
|
5
5
|
class Base
|
6
|
-
attr_reader :
|
7
|
-
delegate :
|
6
|
+
attr_reader :owner, :reflection
|
7
|
+
delegate :name, :readonly, to: :reflection
|
8
|
+
delegate :type, to: :type_definition
|
8
9
|
|
9
|
-
def initialize(
|
10
|
-
@
|
10
|
+
def initialize(reflection, owner)
|
11
|
+
@reflection = reflection
|
11
12
|
@owner = owner
|
12
13
|
@origin = :default
|
13
14
|
end
|
14
15
|
|
15
|
-
def reflection
|
16
|
-
@owner.class._attributes[name]
|
17
|
-
end
|
18
|
-
|
19
16
|
def write_value(value, origin: :user)
|
20
17
|
reset
|
21
18
|
@origin = origin
|
@@ -55,18 +52,14 @@ module Granite
|
|
55
52
|
!(read.respond_to?(:zero?) ? read.zero? : read.blank?)
|
56
53
|
end
|
57
54
|
|
58
|
-
def typecast(value)
|
59
|
-
if value.instance_of?(type)
|
60
|
-
value
|
61
|
-
else
|
62
|
-
typecaster.call(value, self) unless value.nil?
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
55
|
def readonly?
|
67
56
|
!!(readonly.is_a?(Proc) ? evaluate(&readonly) : readonly)
|
68
57
|
end
|
69
58
|
|
59
|
+
def type_definition
|
60
|
+
@type_definition ||= build_type_definition(reflection.type)
|
61
|
+
end
|
62
|
+
|
70
63
|
def inspect_attribute
|
71
64
|
value = case read
|
72
65
|
when Date, Time, DateTime
|
@@ -103,6 +96,10 @@ module Granite
|
|
103
96
|
|
104
97
|
private
|
105
98
|
|
99
|
+
def build_type_definition(type)
|
100
|
+
Granite::Form.type_for(type).new(type, reflection, owner)
|
101
|
+
end
|
102
|
+
|
106
103
|
def evaluate(*args, &block)
|
107
104
|
if block.arity >= 0 && block.arity <= args.length
|
108
105
|
owner.instance_exec(*args.first(block.arity), &block)
|
@@ -11,7 +11,7 @@ module Granite
|
|
11
11
|
hash = hash.stringify_keys.slice(*keys) if keys.present?
|
12
12
|
|
13
13
|
normalize(Hash[hash.map do |key, value|
|
14
|
-
[key, enumerize(
|
14
|
+
[key, enumerize(type_definition.ensure_type(value))]
|
15
15
|
end].with_indifferent_access).with_indifferent_access
|
16
16
|
end
|
17
17
|
end
|
@@ -24,7 +24,7 @@ module Granite
|
|
24
24
|
|
25
25
|
def type_casted_value
|
26
26
|
variable_cache(:value) do
|
27
|
-
|
27
|
+
type_definition.ensure_type(read_before_type_cast)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
@@ -32,14 +32,6 @@ module Granite
|
|
32
32
|
@value_cache
|
33
33
|
end
|
34
34
|
|
35
|
-
def type
|
36
|
-
@type ||= association.reflection.persistence_adapter.primary_key_type
|
37
|
-
end
|
38
|
-
|
39
|
-
def typecaster
|
40
|
-
@typecaster ||= Granite::Form.typecaster(type.ancestors.grep(Class))
|
41
|
-
end
|
42
|
-
|
43
35
|
private
|
44
36
|
|
45
37
|
def association
|