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.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +2 -0
  3. data/.rubocop_todo.yml +1 -1
  4. data/CHANGELOG.md +9 -66
  5. data/README.md +53 -51
  6. data/Rakefile +4 -0
  7. data/granite-form.gemspec +1 -0
  8. data/lib/granite/form/config.rb +10 -10
  9. data/lib/granite/form/model/associations/nested_attributes.rb +3 -3
  10. data/lib/granite/form/model/associations/reflections/references_many.rb +3 -1
  11. data/lib/granite/form/model/associations/reflections/references_one.rb +3 -1
  12. data/lib/granite/form/model/attributes/attribute.rb +1 -1
  13. data/lib/granite/form/model/attributes/base.rb +13 -16
  14. data/lib/granite/form/model/attributes/collection.rb +1 -1
  15. data/lib/granite/form/model/attributes/dictionary.rb +1 -1
  16. data/lib/granite/form/model/attributes/localized.rb +1 -1
  17. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  18. data/lib/granite/form/model/attributes/reference_one.rb +1 -9
  19. data/lib/granite/form/model/attributes/reflections/base.rb +1 -5
  20. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -4
  21. data/lib/granite/form/model/representation.rb +1 -1
  22. data/lib/granite/form/model/validations/nested.rb +1 -1
  23. data/lib/granite/form/types/active_support/time_zone.rb +22 -0
  24. data/lib/granite/form/types/array.rb +17 -0
  25. data/lib/granite/form/types/big_decimal.rb +15 -0
  26. data/lib/granite/form/types/boolean.rb +38 -0
  27. data/lib/granite/form/types/date.rb +15 -0
  28. data/lib/granite/form/types/date_time.rb +15 -0
  29. data/lib/granite/form/types/float.rb +15 -0
  30. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +18 -0
  31. data/lib/granite/form/types/integer.rb +13 -0
  32. data/lib/granite/form/types/object.rb +30 -0
  33. data/lib/granite/form/types/string.rb +13 -0
  34. data/lib/granite/form/types/time.rb +15 -0
  35. data/lib/granite/form/types/uuid.rb +22 -0
  36. data/lib/granite/form/types.rb +15 -0
  37. data/lib/granite/form/version.rb +1 -1
  38. data/lib/granite/form.rb +19 -118
  39. data/spec/{lib/granite → granite}/form/active_record/associations_spec.rb +0 -0
  40. data/spec/{lib/granite → granite}/form/active_record/nested_attributes_spec.rb +0 -1
  41. data/spec/{lib/granite → granite}/form/config_spec.rb +22 -10
  42. data/spec/granite/form/extensions_spec.rb +12 -0
  43. data/spec/{lib/granite → granite}/form/model/associations/embeds_many_spec.rb +0 -0
  44. data/spec/{lib/granite → granite}/form/model/associations/embeds_one_spec.rb +0 -0
  45. data/spec/{lib/granite → granite}/form/model/associations/nested_attributes_spec.rb +0 -1
  46. data/spec/{lib/granite → granite}/form/model/associations/persistence_adapters/active_record_spec.rb +0 -0
  47. data/spec/{lib/granite → granite}/form/model/associations/references_many_spec.rb +0 -0
  48. data/spec/{lib/granite → granite}/form/model/associations/references_one_spec.rb +0 -0
  49. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_any_spec.rb +0 -0
  50. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_many_spec.rb +0 -0
  51. data/spec/{lib/granite → granite}/form/model/associations/reflections/embeds_one_spec.rb +0 -0
  52. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_many_spec.rb +0 -0
  53. data/spec/{lib/granite → granite}/form/model/associations/reflections/references_one_spec.rb +0 -0
  54. data/spec/{lib/granite → granite}/form/model/associations/validations_spec.rb +0 -0
  55. data/spec/{lib/granite → granite}/form/model/associations_spec.rb +0 -0
  56. data/spec/{lib/granite → granite}/form/model/attributes/attribute_spec.rb +4 -46
  57. data/spec/{lib/granite → granite}/form/model/attributes/base_spec.rb +11 -2
  58. data/spec/{lib/granite → granite}/form/model/attributes/collection_spec.rb +0 -0
  59. data/spec/{lib/granite → granite}/form/model/attributes/dictionary_spec.rb +0 -0
  60. data/spec/{lib/granite → granite}/form/model/attributes/localized_spec.rb +1 -1
  61. data/spec/{lib/granite → granite}/form/model/attributes/reflections/attribute_spec.rb +0 -12
  62. data/spec/{lib/granite → granite}/form/model/attributes/reflections/base_spec.rb +1 -1
  63. data/spec/{lib/granite → granite}/form/model/attributes/reflections/collection_spec.rb +0 -0
  64. data/spec/{lib/granite → granite}/form/model/attributes/reflections/dictionary_spec.rb +0 -0
  65. data/spec/{lib/granite → granite}/form/model/attributes/reflections/localized_spec.rb +0 -0
  66. data/spec/{lib/granite → granite}/form/model/attributes/reflections/represents_spec.rb +0 -0
  67. data/spec/{lib/granite → granite}/form/model/attributes/represents_spec.rb +0 -0
  68. data/spec/{lib/granite → granite}/form/model/attributes_spec.rb +0 -0
  69. data/spec/{lib/granite → granite}/form/model/callbacks_spec.rb +0 -0
  70. data/spec/{lib/granite → granite}/form/model/conventions_spec.rb +0 -0
  71. data/spec/{lib/granite → granite}/form/model/dirty_spec.rb +0 -0
  72. data/spec/{lib/granite → granite}/form/model/lifecycle_spec.rb +0 -0
  73. data/spec/{lib/granite → granite}/form/model/persistence_spec.rb +0 -0
  74. data/spec/{lib/granite → granite}/form/model/primary_spec.rb +1 -1
  75. data/spec/{lib/granite → granite}/form/model/representation_spec.rb +14 -2
  76. data/spec/{lib/granite → granite}/form/model/scopes_spec.rb +0 -0
  77. data/spec/{lib/granite → granite}/form/model/validations/associated_spec.rb +0 -0
  78. data/spec/{lib/granite → granite}/form/model/validations/nested_spec.rb +55 -11
  79. data/spec/{lib/granite → granite}/form/model/validations_spec.rb +0 -0
  80. data/spec/{lib/granite → granite}/form/model_spec.rb +0 -0
  81. data/spec/granite/form/types/active_support/time_zone_spec.rb +24 -0
  82. data/spec/granite/form/types/array_spec.rb +13 -0
  83. data/spec/granite/form/types/big_decimal_spec.rb +19 -0
  84. data/spec/granite/form/types/boolean_spec.rb +21 -0
  85. data/spec/granite/form/types/date_spec.rb +18 -0
  86. data/spec/granite/form/types/date_time_spec.rb +20 -0
  87. data/spec/granite/form/types/float_spec.rb +19 -0
  88. data/spec/granite/form/types/hash_with_action_controller_parameters_spec.rb +22 -0
  89. data/spec/granite/form/types/integer_spec.rb +18 -0
  90. data/spec/granite/form/types/object_spec.rb +40 -0
  91. data/spec/granite/form/types/string_spec.rb +13 -0
  92. data/spec/granite/form/types/time_spec.rb +31 -0
  93. data/spec/granite/form/types/uuid_spec.rb +21 -0
  94. data/spec/{lib/granite → granite}/form_spec.rb +0 -0
  95. data/spec/spec_helper.rb +1 -2
  96. data/spec/{shared → support/shared}/nested_attribute_examples.rb +0 -0
  97. data/spec/support/shared/type_examples.rb +7 -0
  98. data/spec/support/translations.rb +6 -0
  99. metadata +150 -91
  100. 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: cb26aa530c266a54d01fe3723d4ee44a95bedbefb140a5358403ebcdf78ced5c
4
- data.tar.gz: 1c63d7e31c87a934a6c858c24c396f0547e16268997dcd2c2e8dbeac16ba98d5
3
+ metadata.gz: ebac9979a786e2a3638b1dd7b21648320c779770c10f7fe63707b38a58c0164c
4
+ data.tar.gz: 604dcf0abda92863e1503b0d977a53ca1de7a39823bfc8a4aabab8d9c6a27045
5
5
  SHA512:
6
- metadata.gz: 2b28feea4881f8c4f6635f1e626345b4ea534306b8596deb8a61f2a38ce428950eb957bd9b1c936699fbe049a22e1c41d36dce4ebd695c769b3fee39c3ecf716
7
- data.tar.gz: e0fdbd61872e007edaf0cf4ffed388b382f14e53e2637a04979eae1f4e2f1b67aa0bad71569b9ab4b2ce8cd2363820e380661f402edec70584c480962aa7c0e1
6
+ metadata.gz: cfc2c624834fe5d0ad4fadb903e6b9a38eed17778cea3c0cf9430437fb095436429c1a0941fd418e143805b064c1a74c0c959fb57493d96fb9602cdca625f36d
7
+ data.tar.gz: 4937e3d02a378edf48daa770799206b3ecdb91ee71ed1025169e6e3bf4a25bc157c01aea9db54dcb70645728eeb0055ca42be7db23258f0b7f2744d8f7415dd9
@@ -0,0 +1,2 @@
1
+ .github/workflows/ci.yml @toptal/coresmiths-team
2
+ .github/workflows/main.yml @toptal/coresmiths-team
data/.rubocop_todo.yml CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  # Offense count: 19
10
10
  Metrics/AbcSize:
11
- Max: 54
11
+ Max: 55
12
12
 
13
13
  # Offense count: 2
14
14
  # Configuration parameters: CountComments.
data/CHANGELOG.md CHANGED
@@ -1,73 +1,16 @@
1
1
  # master
2
2
 
3
- # Version 1.2.0
3
+ ## Next
4
+ ## v0.2.0
4
5
 
5
- * Rails 6.1 and 7 support (#80). Thanks to @ojab and @rewritten
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
- # Version 1.1.7
10
+ ## v0.1.1
8
11
 
9
- * Add typecasting from `ActionController::Parameters` to `Hash` (#73)
12
+ - Fixed represented error message copying when represented model uses symbols for `message`.
10
13
 
11
- # Version 1.1.6
14
+ ## v0.1.0
12
15
 
13
- * Fix Ruby 2.6 deprecations (#72)
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 a ActiveModel-based front-end for your data. You might need to use it in the following cases:
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-storage in ActiveRecord style with
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 implement embedded objects for ActiveRecord models
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
- q = Quiz.new
98
- q.answers.build(question_id: 42, content: 'blabla')
99
- q.save
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 abilities:
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 your ActiveRecord entities. Quite useful with PG JSON capabilities.
108
+ * Embedding objects into ActiveRecord entities. Quite useful with PG JSON capabilities.
109
109
 
110
110
  Key features:
111
111
 
112
- * Complete objects lifecycle support: saving, updating, destroying.
112
+ * Complete object lifecycle support: saving, updating, destroying.
113
113
  * Embedded and referenced associations.
114
- * Backend-agnostic named scopes functionality.
115
- * Callbacks, validations and dirty attributes inside.
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
- By default, if type for attribute is not set, it is defined with `Object` type, so it would be a great idea to specify type for every attribute explicitly.
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
- Type is necessary for attribute typecasting. Here is the list of pre-defined basic typecasters:
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 the attribute, but in that case you will be able to only assign instances of that specific class or value nil:
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 AR or Mongoid default values:
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 - then it turns to nil:
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, they will be applied in the order. It is possible to pre-define normalizers to DRY code:
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
- Collection is simply an array of equally-typed values:
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
- Collection typecasts each value to specified type and also no matter what are you going to pass - it will be an array.
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 to every value, normalizer will be applied to the whole array.
231
+ Default and enum modifiers are applied on each value, normalizers are applied on the array.
232
232
 
233
233
  #### Dictionary
234
234
 
235
- Dictionary field is a hash of specified type values with string keys:
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
- Keys list might be restricted with `:keys` option, defaults and enums are applied to every value, normalizers are applied to the whole hash.
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
- Localized is similar to how Globalize 3 attributes work.
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
- Represents provides an easy way to expose model attributes through an interface.
265
- It will automatically set 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
- A type of an attribute will be taken from it.
268
- If there is no type, it will be `Object` by default. You can set the type explicitly by passing the `type: TypeClass` option.
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 of them: referenced and embedded. The closest example of referenced association is AR `belongs_to` and as for embedded ones - Mongoid's embedded. Also these associations support `accepts_nested_attributes` call.
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
- Possible options:
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` - true or false
337
- * `:default` - default value for association: attributes hash collection or instances of defined class
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
- This will provide several methods to the object: `#user`, `#user=`, `#user_id` and `#user_id=`, just as would occur with an ActiveRecord association.
347
+ Provides several methods to the object: `#user`, `#user=`, `#user_id` and `#user_id=`, similarly to an ActiveRecord association.
346
348
 
347
- Possible options:
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
- This will create the following methods: `#user`, `#user=`, `#user_name` and `#user_name=`
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
- This will provide several methods to the object: `#users`, `#users=`, `#user_ids` and `#user_ids=` just as an ActiveRecord relation does.
370
+ Provides several methods to the object: `#users`, `#users=`, `#user_ids` and `#user_ids=`, similarly to an ActiveRecord association.
369
371
 
370
- Possible options:
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
- This will create the following methods: `#users`, `#users=`, `#user_names` and `#user_names=`
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
- Where
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 required interface for adapters described in `Granite::Form::Model::Associations::PersistenceAdapters::Base`.
407
+ All requirements for the adapter interfaces are described in `Granite::Form::Model::Associations::PersistenceAdapters::Base`.
406
408
 
407
- Adapter for ActiveRecord is `Granite::Form::Model::Associations::PersistenceAdapters::ActiveRecord`. So, all AR models will use `PersistenceAdapters::ActiveRecord` by default.
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
@@ -1,6 +1,10 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rspec/core/rake_task'
3
+ require 'bump/tasks'
3
4
 
4
5
  RSpec::Core::RakeTask.new(:spec)
5
6
 
6
7
  task default: :spec
8
+
9
+ Bump.tag_by_default = true
10
+ Bump.changelog = true
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'
@@ -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, :_typecasters
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
- @_typecasters = {}
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(*classes, &block)
31
- classes = classes.flatten
32
- if block
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.typecast(attributes[primary_attribute_name]) if 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
- .typecast(attributes[primary_attribute_name])
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
- Granite::Form.typecaster(Boolean).call(hash[DESTROY_ATTRIBUTE])
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, association: name
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, association: name
17
+ reflection.reference_key,
18
+ type: reflection.persistence_adapter.primary_key_type,
19
+ association: name
18
20
  )
19
21
 
20
22
  reflection
@@ -14,7 +14,7 @@ module Granite
14
14
 
15
15
  def read
16
16
  variable_cache(:value) do
17
- normalize(enumerize(typecast(read_before_type_cast)))
17
+ normalize(enumerize(type_definition.ensure_type(read_before_type_cast)))
18
18
  end
19
19
  end
20
20
 
@@ -3,19 +3,16 @@ module Granite
3
3
  module Model
4
4
  module Attributes
5
5
  class Base
6
- attr_reader :name, :owner
7
- delegate :type, :typecaster, :readonly, to: :reflection
6
+ attr_reader :owner, :reflection
7
+ delegate :name, :readonly, to: :reflection
8
+ delegate :type, to: :type_definition
8
9
 
9
- def initialize(name, owner)
10
- @name = name
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)
@@ -5,7 +5,7 @@ module Granite
5
5
  class Collection < Attribute
6
6
  def read
7
7
  @value ||= normalize(read_before_type_cast.map do |value|
8
- enumerize(typecast(value))
8
+ enumerize(type_definition.ensure_type(value))
9
9
  end)
10
10
  end
11
11
 
@@ -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(typecast(value))]
14
+ [key, enumerize(type_definition.ensure_type(value))]
15
15
  end].with_indifferent_access).with_indifferent_access
16
16
  end
17
17
  end
@@ -5,7 +5,7 @@ module Granite
5
5
  class Localized < Attribute
6
6
  def read
7
7
  @value ||= Hash[read_before_type_cast.map do |locale, value|
8
- [locale.to_s, normalize(enumerize(typecast(value)))]
8
+ [locale.to_s, normalize(enumerize(type_definition.ensure_type(value)))]
9
9
  end]
10
10
  end
11
11
 
@@ -5,7 +5,7 @@ module Granite
5
5
  class ReferenceMany < ReferenceOne
6
6
  def type_casted_value
7
7
  variable_cache(:value) do
8
- read_before_type_cast.map { |id| typecast(id) }
8
+ read_before_type_cast.map { |id| type_definition.ensure_type(id) }
9
9
  end
10
10
  end
11
11
 
@@ -24,7 +24,7 @@ module Granite
24
24
 
25
25
  def type_casted_value
26
26
  variable_cache(:value) do
27
- typecast(read_before_type_cast)
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