granite-form 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/CHANGELOG.md +8 -0
- data/README.md +6 -13
- data/lib/granite/form/model/associations/nested_attributes.rb +2 -2
- data/lib/granite/form/model/associations/reflections/embeds_many.rb +1 -1
- data/lib/granite/form/model/associations/reflections/embeds_one.rb +11 -1
- data/lib/granite/form/model/associations/reflections/references_one.rb +2 -0
- data/lib/granite/form/model/associations/reflections/singular.rb +0 -14
- data/lib/granite/form/model/attributes/attribute.rb +3 -21
- data/lib/granite/form/model/attributes/base.rb +5 -23
- data/lib/granite/form/model/attributes/reference_many.rb +1 -1
- data/lib/granite/form/model/attributes/reference_one.rb +1 -1
- data/lib/granite/form/model/attributes/reflections/attribute.rb +4 -4
- data/lib/granite/form/model/attributes/reflections/base/build_type_definition.rb +38 -0
- data/lib/granite/form/model/attributes/reflections/base.rb +12 -10
- data/lib/granite/form/model/attributes/reflections/collection/build_type_definition.rb +19 -0
- data/lib/granite/form/model/attributes/reflections/dictionary/build_type_definition.rb +19 -0
- data/lib/granite/form/model/attributes/reflections/dictionary.rb +0 -3
- data/lib/granite/form/model/attributes/reflections/represents/build_type_definition.rb +73 -0
- data/lib/granite/form/model/attributes/reflections/represents.rb +10 -2
- data/lib/granite/form/model/attributes/represents.rb +22 -37
- data/lib/granite/form/model/attributes.rb +10 -2
- data/lib/granite/form/model/representation.rb +1 -0
- data/lib/granite/form/model/validations.rb +6 -0
- data/lib/granite/form/model.rb +1 -1
- data/lib/granite/form/types/active_support/time_zone.rb +2 -0
- data/lib/granite/form/types/array.rb +2 -0
- data/lib/granite/form/types/big_decimal.rb +2 -0
- data/lib/granite/form/types/boolean.rb +2 -0
- data/lib/granite/form/types/collection.rb +11 -0
- data/lib/granite/form/types/date.rb +2 -0
- data/lib/granite/form/types/date_time.rb +2 -0
- data/lib/granite/form/types/dictionary.rb +23 -0
- data/lib/granite/form/types/float.rb +2 -0
- data/lib/granite/form/types/has_subtype.rb +18 -0
- data/lib/granite/form/types/hash_with_action_controller_parameters.rb +2 -0
- data/lib/granite/form/types/integer.rb +2 -0
- data/lib/granite/form/types/object.rb +28 -0
- data/lib/granite/form/types/string.rb +2 -0
- data/lib/granite/form/types/time.rb +2 -0
- data/lib/granite/form/types/uuid.rb +2 -0
- data/lib/granite/form/types.rb +3 -0
- data/lib/granite/form/util.rb +55 -0
- data/lib/granite/form/version.rb +1 -1
- data/lib/granite/form.rb +1 -0
- data/spec/granite/form/model/associations/references_many_spec.rb +1 -1
- data/spec/granite/form/model/associations/references_one_spec.rb +4 -4
- data/spec/granite/form/model/attributes/attribute_spec.rb +0 -29
- data/spec/granite/form/model/attributes/reflections/attribute_spec.rb +0 -9
- data/spec/granite/form/model/attributes/reflections/base/build_type_definition_spec.rb +27 -0
- data/spec/granite/form/model/attributes/reflections/base_spec.rb +16 -10
- data/spec/granite/form/model/attributes/reflections/collection/build_type_definition_spec.rb +24 -0
- data/spec/granite/form/model/attributes/reflections/dictionary/build_type_definition_spec.rb +24 -0
- data/spec/granite/form/model/attributes/reflections/dictionary_spec.rb +0 -6
- data/spec/granite/form/model/attributes/reflections/represents/build_type_definition_spec.rb +129 -0
- data/spec/granite/form/model/attributes/reflections/represents_spec.rb +43 -20
- data/spec/granite/form/model/attributes/represents_spec.rb +78 -55
- data/spec/granite/form/model/attributes_spec.rb +84 -23
- data/spec/granite/form/model/dirty_spec.rb +0 -6
- data/spec/granite/form/model/representation_spec.rb +4 -7
- data/spec/granite/form/model/validations_spec.rb +28 -1
- data/spec/granite/form/types/collection_spec.rb +22 -0
- data/spec/granite/form/types/dictionary_spec.rb +32 -0
- data/spec/granite/form/types/has_subtype_spec.rb +20 -0
- data/spec/granite/form/types/object_spec.rb +50 -4
- data/spec/granite/form/util_spec.rb +108 -0
- data/spec/support/active_record.rb +3 -0
- metadata +26 -15
- data/lib/granite/form/model/attributes/collection.rb +0 -19
- data/lib/granite/form/model/attributes/dictionary.rb +0 -28
- data/lib/granite/form/model/attributes/localized.rb +0 -44
- data/lib/granite/form/model/attributes/reflections/localized.rb +0 -45
- data/lib/granite/form/model/localization.rb +0 -26
- data/spec/granite/form/model/attributes/collection_spec.rb +0 -72
- data/spec/granite/form/model/attributes/dictionary_spec.rb +0 -100
- data/spec/granite/form/model/attributes/localized_spec.rb +0 -103
- data/spec/granite/form/model/attributes/reflections/localized_spec.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 908865a58fa79739294c85bb506d878596a28782c02a9a2825ca1fb7fd128972
|
4
|
+
data.tar.gz: ff9b6cbed34774e3dedb0b33c28b75694b1575051adb989248ca5695254e9586
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 92d055b121d30a9644521227782908a0b84ef0847c16ce60fafec47cb17c5c2988e8a071e199ed5506dd0783bba335b6a399a519c095867658e310680dd96f10
|
7
|
+
data.tar.gz: 4b4cf5f23530ad12408f498df6eed11e53438dd0a6f110ae04c10568934f041dbb64b946ee42352b7bfec83119a5827b652c8fddd62e745fa0f2835448674ce6
|
data/.github/CODEOWNERS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* @toptal/
|
1
|
+
* @toptal/portals-experience-be
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# master
|
2
2
|
|
3
3
|
## Next
|
4
|
+
## v0.4.0
|
5
|
+
|
6
|
+
* [BREAKING] Drop support for taking `model` as first argument in default/readonly/enum/normalize. This means that `default: -> (model) { model.other_field}` is no longer supported and should be replaced with `default: -> { other_field }`.
|
7
|
+
* Add support for evaluating `Symbol` for readonly/enum/normalize. If symbol is passed in one of those options, method with that name will be called when evaluating the value.
|
8
|
+
* [BREAKING] Remove `localized` attribute type.
|
9
|
+
* [BREAKING] Change the behavior of `default` and `normalize` for `collection` & `dictionary`. Instead of acting per element they will now act on the attribute as a whole.
|
10
|
+
* E.g. `collection :numbers, default: [1, 2, 3]` will not set the default for the whole collection of `numbers` rather than each element in `numbers`.
|
11
|
+
|
4
12
|
## v0.3.0
|
5
13
|
|
6
14
|
- [BREAKING] Stop automatically saving `references_one`/`references_many` when applying changes.
|
data/README.md
CHANGED
@@ -172,9 +172,10 @@ It is possible to provide default values for attributes and they will act in the
|
|
172
172
|
|
173
173
|
```ruby
|
174
174
|
attribute :check, Boolean, default: false # Simply false by default
|
175
|
-
attribute :
|
176
|
-
|
177
|
-
|
175
|
+
attribute :wday, Integer, default: ->{ today.wday } # Default evaluated in instance context
|
176
|
+
def calculate_today
|
177
|
+
Time.zone.now.today
|
178
|
+
end
|
178
179
|
```
|
179
180
|
|
180
181
|
##### Enums
|
@@ -201,8 +202,8 @@ attribute :title, String, normalizers: [->(value) { value.strip }, trim: {length
|
|
201
202
|
|
202
203
|
```ruby
|
203
204
|
attribute :name, String, readonly: true # Readonly forever
|
204
|
-
attribute :name, String, readonly:
|
205
|
-
attribute :name, String, readonly: ->
|
205
|
+
attribute :name, String, readonly: :name_changed? # Conditional with calling method
|
206
|
+
attribute :name, String, readonly: -> { subject.present? } # Conditional with lambda
|
206
207
|
```
|
207
208
|
|
208
209
|
#### Collection
|
@@ -251,14 +252,6 @@ end
|
|
251
252
|
|
252
253
|
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
254
|
|
254
|
-
#### Localized
|
255
|
-
|
256
|
-
`localized` is similar to how `Globalize 3` attributes work.
|
257
|
-
|
258
|
-
```ruby
|
259
|
-
localized :title, String
|
260
|
-
```
|
261
|
-
|
262
255
|
#### Represents
|
263
256
|
|
264
257
|
`represents` provides an easy way to expose model attributes through an interface.
|
@@ -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.type_definition.
|
77
|
+
primary_attribute_value = primary_attribute.type_definition.prepare(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)
|
@@ -124,7 +124,7 @@ module Granite
|
|
124
124
|
else
|
125
125
|
existing_record = association.target.detect do |record|
|
126
126
|
primary_attribute_value = record.attribute(primary_attribute_name)
|
127
|
-
.type_definition.
|
127
|
+
.type_definition.prepare(attributes[primary_attribute_name])
|
128
128
|
record.primary_attribute == primary_attribute_value
|
129
129
|
end
|
130
130
|
if existing_record
|
@@ -5,7 +5,7 @@ module Granite
|
|
5
5
|
module Reflections
|
6
6
|
class EmbedsMany < EmbedsAny
|
7
7
|
def self.build(target, generated_methods, name, options = {}, &block)
|
8
|
-
target.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, name) if target < Granite::Form::Model::Attributes
|
8
|
+
target.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, name, type: Object) if target < Granite::Form::Model::Attributes
|
9
9
|
options[:validate] = true unless options.key?(:validate)
|
10
10
|
super
|
11
11
|
end
|
@@ -7,10 +7,20 @@ module Granite
|
|
7
7
|
include Singular
|
8
8
|
|
9
9
|
def self.build(target, generated_methods, name, options = {}, &block)
|
10
|
-
target.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, name) if target < Granite::Form::Model::Attributes
|
10
|
+
target.add_attribute(Granite::Form::Model::Attributes::Reflections::Base, name, type: Object) if target < Granite::Form::Model::Attributes
|
11
11
|
options[:validate] = true unless options.key?(:validate)
|
12
12
|
super
|
13
13
|
end
|
14
|
+
|
15
|
+
def self.generate_methods(name, target)
|
16
|
+
super
|
17
|
+
|
18
|
+
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
|
+
def build_#{name} attributes = {}
|
20
|
+
association(:#{name}).build(attributes)
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
end
|
14
24
|
end
|
15
25
|
end
|
16
26
|
end
|
@@ -4,20 +4,6 @@ module Granite
|
|
4
4
|
module Associations
|
5
5
|
module Reflections
|
6
6
|
module Singular
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
module ClassMethods
|
10
|
-
def generate_methods(name, target)
|
11
|
-
super
|
12
|
-
|
13
|
-
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
14
|
-
def build_#{name} attributes = {}
|
15
|
-
association(:#{name}).build(attributes)
|
16
|
-
end
|
17
|
-
RUBY
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
7
|
def collection?
|
22
8
|
false
|
23
9
|
end
|
@@ -14,7 +14,7 @@ module Granite
|
|
14
14
|
|
15
15
|
def read
|
16
16
|
variable_cache(:value) do
|
17
|
-
normalize(
|
17
|
+
normalize(type_definition.prepare(read_before_type_cast))
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -25,31 +25,13 @@ module Granite
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def default
|
28
|
-
|
28
|
+
owner.evaluate_if_proc(defaultizer)
|
29
29
|
end
|
30
30
|
|
31
31
|
def defaultize(value, default_value = nil)
|
32
32
|
!defaultizer.nil? && value.nil? ? default_value || default : value
|
33
33
|
end
|
34
34
|
|
35
|
-
def enum
|
36
|
-
source = enumerizer.is_a?(Proc) ? evaluate(&enumerizer) : enumerizer
|
37
|
-
|
38
|
-
case source
|
39
|
-
when Range
|
40
|
-
source.to_a
|
41
|
-
when Set
|
42
|
-
source
|
43
|
-
else
|
44
|
-
Array.wrap(source)
|
45
|
-
end.to_set
|
46
|
-
end
|
47
|
-
|
48
|
-
def enumerize(value)
|
49
|
-
set = enum if enumerizer
|
50
|
-
value if !set || (set.none? || set.include?(value))
|
51
|
-
end
|
52
|
-
|
53
35
|
def normalize(value)
|
54
36
|
if normalizers.none?
|
55
37
|
value
|
@@ -57,7 +39,7 @@ module Granite
|
|
57
39
|
normalizers.inject(value) do |val, normalizer|
|
58
40
|
case normalizer
|
59
41
|
when Proc
|
60
|
-
evaluate(
|
42
|
+
owner.evaluate(normalizer, val)
|
61
43
|
when Hash
|
62
44
|
normalizer.inject(val) do |v, (name, options)|
|
63
45
|
Granite::Form.normalizer(name).call(v, options, self)
|
@@ -3,13 +3,12 @@ module Granite
|
|
3
3
|
module Model
|
4
4
|
module Attributes
|
5
5
|
class Base
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :type_definition
|
7
|
+
delegate :type, :reflection, :owner, :enum, to: :type_definition
|
7
8
|
delegate :name, :readonly, to: :reflection
|
8
|
-
delegate :type, to: :type_definition
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
12
|
-
@owner = owner
|
10
|
+
def initialize(type_definition)
|
11
|
+
@type_definition = type_definition
|
13
12
|
@origin = :default
|
14
13
|
end
|
15
14
|
|
@@ -53,11 +52,7 @@ module Granite
|
|
53
52
|
end
|
54
53
|
|
55
54
|
def readonly?
|
56
|
-
!!
|
57
|
-
end
|
58
|
-
|
59
|
-
def type_definition
|
60
|
-
@type_definition ||= build_type_definition(reflection.type)
|
55
|
+
!!owner.evaluate(readonly)
|
61
56
|
end
|
62
57
|
|
63
58
|
def inspect_attribute
|
@@ -96,19 +91,6 @@ module Granite
|
|
96
91
|
|
97
92
|
private
|
98
93
|
|
99
|
-
def build_type_definition(type)
|
100
|
-
Granite::Form.type_for(type).new(type, reflection, owner)
|
101
|
-
end
|
102
|
-
|
103
|
-
def evaluate(*args, &block)
|
104
|
-
if block.arity >= 0 && block.arity <= args.length
|
105
|
-
owner.instance_exec(*args.first(block.arity), &block)
|
106
|
-
else
|
107
|
-
args = block.arity.negative? ? args : args.first(block.arity)
|
108
|
-
yield(*args, owner)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
94
|
def remove_variable(*names)
|
113
95
|
names.flatten.each do |name|
|
114
96
|
name = :"@#{name}"
|
@@ -4,6 +4,10 @@ module Granite
|
|
4
4
|
module Attributes
|
5
5
|
module Reflections
|
6
6
|
class Attribute < Base
|
7
|
+
def self.attribute_class
|
8
|
+
Granite::Form::Model::Attributes::Attribute
|
9
|
+
end
|
10
|
+
|
7
11
|
def self.generate_methods(name, target)
|
8
12
|
target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
9
13
|
def #{name}
|
@@ -40,10 +44,6 @@ module Granite
|
|
40
44
|
@defaultizer ||= options[:default]
|
41
45
|
end
|
42
46
|
|
43
|
-
def enumerizer
|
44
|
-
@enumerizer ||= options[:enum] || options[:in]
|
45
|
-
end
|
46
|
-
|
47
47
|
def normalizers
|
48
48
|
@normalizers ||= Array.wrap(options[:normalize] || options[:normalizer] || options[:normalizers])
|
49
49
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Granite
|
2
|
+
module Form
|
3
|
+
module Model
|
4
|
+
module Attributes
|
5
|
+
module Reflections
|
6
|
+
class Base
|
7
|
+
class BuildTypeDefinition
|
8
|
+
attr_reader :owner, :reflection
|
9
|
+
delegate :name, to: :reflection
|
10
|
+
|
11
|
+
def initialize(owner, reflection)
|
12
|
+
@owner = owner
|
13
|
+
@reflection = reflection
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
raise "Type is not specified for `#{name}`" if type.nil?
|
18
|
+
|
19
|
+
type_definition_for(type)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def type
|
25
|
+
reflection.options[:type]
|
26
|
+
end
|
27
|
+
|
28
|
+
def type_definition_for(type)
|
29
|
+
type = type.to_s.camelize.constantize unless type.is_a?(Module)
|
30
|
+
Granite::Form.type_for(type).new(type, reflection, owner)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -28,24 +28,26 @@ module Granite
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def build_attribute(owner, raw_value = Granite::Form::UNDEFINED)
|
31
|
-
|
31
|
+
type_definition = self.class::BuildTypeDefinition.new(owner, self).call
|
32
|
+
attribute = self.class.attribute_class.new(type_definition)
|
32
33
|
attribute.write_value(raw_value, origin: :persistence) unless raw_value == Granite::Form::UNDEFINED
|
33
34
|
attribute
|
34
35
|
end
|
35
36
|
|
36
37
|
def type
|
37
|
-
|
38
|
-
when Class, Module
|
39
|
-
options[:type]
|
40
|
-
when nil
|
41
|
-
raise "Type is not specified for `#{name}`"
|
42
|
-
else
|
43
|
-
options[:type].to_s.camelize.constantize
|
44
|
-
end
|
38
|
+
options[:type]
|
45
39
|
end
|
46
40
|
|
47
41
|
def readonly
|
48
|
-
|
42
|
+
options[:readonly]
|
43
|
+
end
|
44
|
+
|
45
|
+
def enum
|
46
|
+
options[:enum] || options[:in]
|
47
|
+
end
|
48
|
+
|
49
|
+
def keys
|
50
|
+
@keys ||= Array.wrap(options[:keys]).map(&:to_s)
|
49
51
|
end
|
50
52
|
|
51
53
|
def inspect_reflection
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Granite
|
4
|
+
module Form
|
5
|
+
module Model
|
6
|
+
module Attributes
|
7
|
+
module Reflections
|
8
|
+
class Collection
|
9
|
+
class BuildTypeDefinition < Base::BuildTypeDefinition
|
10
|
+
def call
|
11
|
+
Types::Collection.new(super)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Granite
|
4
|
+
module Form
|
5
|
+
module Model
|
6
|
+
module Attributes
|
7
|
+
module Reflections
|
8
|
+
class Dictionary
|
9
|
+
class BuildTypeDefinition < Base::BuildTypeDefinition
|
10
|
+
def call
|
11
|
+
Types::Dictionary.new(super)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Granite
|
2
|
+
module Form
|
3
|
+
module Model
|
4
|
+
module Attributes
|
5
|
+
module Reflections
|
6
|
+
class Represents
|
7
|
+
class BuildTypeDefinition < Base::BuildTypeDefinition
|
8
|
+
GRANITE_COLLECTION_TYPES = [Granite::Form::Model::Attributes::ReferenceMany].freeze
|
9
|
+
TYPES = {
|
10
|
+
'ActiveRecord::Enum::EnumType' => String,
|
11
|
+
'ActiveRecord::Type::Serialized' => Object
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def call
|
15
|
+
if type.present?
|
16
|
+
super
|
17
|
+
else
|
18
|
+
granite_form_type || active_record_type || type_definition_for(Object)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def reference
|
25
|
+
owner.__send__(reflection.reference)
|
26
|
+
end
|
27
|
+
|
28
|
+
def granite_form_type
|
29
|
+
return nil unless reference.is_a?(Model)
|
30
|
+
|
31
|
+
reference_attribute = reference.attribute(name)
|
32
|
+
return nil if reference_attribute.nil?
|
33
|
+
|
34
|
+
type_definition = reference_attribute.type_definition.build_duplicate(reflection, owner)
|
35
|
+
if GRANITE_COLLECTION_TYPES.any? { |klass| reference_attribute.is_a? klass }
|
36
|
+
Types::Collection.new(type_definition)
|
37
|
+
else
|
38
|
+
type_definition
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def active_record_type
|
43
|
+
return nil unless reference.respond_to?(:type_for_attribute)
|
44
|
+
|
45
|
+
attribute_type = reference.type_for_attribute(active_model_attribute_name.to_s)
|
46
|
+
|
47
|
+
attribute_type_name = attribute_type.class.to_s
|
48
|
+
if TYPES.key?(attribute_type_name)
|
49
|
+
type_definition_for(TYPES[attribute_type_name])
|
50
|
+
elsif attribute_type.respond_to?(:subtype)
|
51
|
+
Types::Collection.new(convert_active_model_type_to_definition(attribute_type.subtype))
|
52
|
+
else
|
53
|
+
convert_active_model_type_to_definition(attribute_type)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def active_model_attribute_name
|
58
|
+
aliases = reference.class.try(:attribute_aliases) || {}
|
59
|
+
aliases.fetch(name.to_s, name)
|
60
|
+
end
|
61
|
+
|
62
|
+
def convert_active_model_type_to_definition(attribute_type)
|
63
|
+
type = attribute_type.try(:value_class) ||
|
64
|
+
Associations::PersistenceAdapters::ActiveRecord::TYPES[attribute_type.type&.to_sym]
|
65
|
+
type_definition_for(type) if type
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -4,14 +4,22 @@ module Granite
|
|
4
4
|
module Attributes
|
5
5
|
module Reflections
|
6
6
|
class Represents < Attribute
|
7
|
+
def self.attribute_class
|
8
|
+
Attributes::Represents
|
9
|
+
end
|
10
|
+
|
7
11
|
def self.build(target, generated_methods, name, *args, &block)
|
8
12
|
options = args.extract_options!
|
9
13
|
|
10
14
|
reference = target.reflect_on_association(options[:of]) if target.respond_to?(:reflect_on_association)
|
11
15
|
reference ||= target.reflect_on_attribute(options[:of]) if target.respond_to?(:reflect_on_attribute)
|
12
16
|
options[:of] = reference.name if reference
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
if options.fetch(:validate_reference, true)
|
19
|
+
validates_nested = target.respond_to?(:validates_nested) && !target.validates_nested?(options[:of])
|
20
|
+
target.validates_nested(options[:of]) if validates_nested
|
21
|
+
target.validates_presence_of(options[:of]) unless target.validates_presence?(options[:of])
|
22
|
+
end
|
15
23
|
|
16
24
|
super(target, generated_methods, name, *args, options, &block)
|
17
25
|
end
|
@@ -5,60 +5,45 @@ module Granite
|
|
5
5
|
class Represents < Attribute
|
6
6
|
delegate :reader, :reader_before_type_cast, :writer, to: :reflection
|
7
7
|
|
8
|
-
def
|
9
|
-
return if readonly?
|
10
|
-
pollute do
|
11
|
-
reset
|
12
|
-
reference.send(writer, value)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def reset
|
8
|
+
def initialize(*_args)
|
17
9
|
super
|
18
|
-
|
10
|
+
|
11
|
+
set_default_value
|
12
|
+
set_default_value_before_type_cast
|
19
13
|
end
|
20
14
|
|
21
|
-
def
|
22
|
-
|
23
|
-
variable_cache(:value) do
|
24
|
-
normalize(enumerize(defaultize(cached_value, read_before_type_cast)))
|
25
|
-
end
|
15
|
+
def sync
|
16
|
+
reference.public_send(writer, read) if reference.respond_to?(writer)
|
26
17
|
end
|
27
18
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
def changed?
|
20
|
+
if reflection.options.key?(:default)
|
21
|
+
reference.public_send(reader) != read
|
22
|
+
else
|
23
|
+
owner.public_send("#{name}_changed?")
|
32
24
|
end
|
33
25
|
end
|
34
26
|
|
35
27
|
private
|
36
28
|
|
37
29
|
def reference
|
38
|
-
owner.
|
30
|
+
owner.__send__(reflection.reference)
|
39
31
|
end
|
40
32
|
|
41
|
-
def
|
42
|
-
|
43
|
-
ref.public_send(reader) if ref
|
44
|
-
end
|
45
|
-
|
46
|
-
def cached_value
|
47
|
-
variable_cache(:cached_value) { read_value }
|
48
|
-
end
|
33
|
+
def set_default_value
|
34
|
+
return unless reference.respond_to?(reader)
|
49
35
|
|
50
|
-
|
51
|
-
|
52
|
-
return unless ref
|
53
|
-
if ref.respond_to?(reader_before_type_cast)
|
54
|
-
ref.public_send(reader_before_type_cast)
|
55
|
-
else
|
56
|
-
ref.public_send(reader)
|
36
|
+
variable_cache(:value) do
|
37
|
+
normalize(type_definition.prepare(defaultize(reference.public_send(reader))))
|
57
38
|
end
|
58
39
|
end
|
59
40
|
|
60
|
-
def
|
61
|
-
|
41
|
+
def set_default_value_before_type_cast
|
42
|
+
return unless reference.respond_to?(reader_before_type_cast)
|
43
|
+
|
44
|
+
variable_cache(:value_before_type_cast) do
|
45
|
+
defaultize(reference.public_send(reader_before_type_cast))
|
46
|
+
end
|
62
47
|
end
|
63
48
|
end
|
64
49
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
require 'granite/form/model/attributes/reflections/base'
|
2
|
+
require 'granite/form/model/attributes/reflections/base/build_type_definition'
|
2
3
|
require 'granite/form/model/attributes/reflections/attribute'
|
3
4
|
require 'granite/form/model/attributes/reflections/collection'
|
5
|
+
require 'granite/form/model/attributes/reflections/collection/build_type_definition'
|
4
6
|
require 'granite/form/model/attributes/reflections/dictionary'
|
7
|
+
require 'granite/form/model/attributes/reflections/dictionary/build_type_definition'
|
5
8
|
|
6
9
|
require 'granite/form/model/attributes/base'
|
7
10
|
require 'granite/form/model/attributes/attribute'
|
8
|
-
require 'granite/form/model/attributes/collection'
|
9
|
-
require 'granite/form/model/attributes/dictionary'
|
10
11
|
|
11
12
|
module Granite
|
12
13
|
module Form
|
@@ -178,6 +179,13 @@ module Granite
|
|
178
179
|
|
179
180
|
alias_method :attributes=, :assign_attributes
|
180
181
|
|
182
|
+
def sync_attributes
|
183
|
+
attribute_names.each do |name|
|
184
|
+
attr = attribute(name)
|
185
|
+
attr.try(:sync) if attr.try(:changed?)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
181
189
|
def inspect
|
182
190
|
"#<#{self.class.send(:original_inspect)} #{attributes_for_inspect.presence || '(no attributes)'}>"
|
183
191
|
end
|
@@ -12,6 +12,12 @@ module Granite
|
|
12
12
|
alias_method :validate, :valid?
|
13
13
|
end
|
14
14
|
|
15
|
+
class_methods do
|
16
|
+
def validates_presence?(attr)
|
17
|
+
_validators[attr.to_sym].grep(ActiveModel::Validations::PresenceValidator).present?
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
15
21
|
def validate!(context = nil)
|
16
22
|
valid?(context) || raise_validation_error
|
17
23
|
end
|