granite-form 0.2.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -2
  3. data/.github/workflows/{ci.yml → ruby.yml} +22 -4
  4. data/.rubocop.yml +1 -1
  5. data/.rubocop_todo.yml +3 -3
  6. data/Appraisals +1 -2
  7. data/CHANGELOG.md +15 -0
  8. data/README.md +6 -15
  9. data/docker-compose.yml +14 -0
  10. data/gemfiles/rails.5.0.gemfile +0 -1
  11. data/gemfiles/rails.5.1.gemfile +0 -1
  12. data/gemfiles/rails.5.2.gemfile +0 -1
  13. data/granite-form.gemspec +15 -15
  14. data/lib/granite/form/active_record/associations.rb +1 -1
  15. data/lib/granite/form/base.rb +1 -2
  16. data/lib/granite/form/errors.rb +0 -15
  17. data/lib/granite/form/model/associations/base.rb +0 -4
  18. data/lib/granite/form/model/associations/collection/embedded.rb +2 -1
  19. data/lib/granite/form/model/associations/collection/proxy.rb +1 -1
  20. data/lib/granite/form/model/associations/embeds_any.rb +7 -0
  21. data/lib/granite/form/model/associations/embeds_many.rb +9 -58
  22. data/lib/granite/form/model/associations/embeds_one.rb +7 -36
  23. data/lib/granite/form/model/associations/nested_attributes.rb +7 -7
  24. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +0 -4
  25. data/lib/granite/form/model/associations/persistence_adapters/base.rb +0 -4
  26. data/lib/granite/form/model/associations/references_many.rb +0 -32
  27. data/lib/granite/form/model/associations/references_one.rb +0 -28
  28. data/lib/granite/form/model/associations/reflections/embeds_any.rb +1 -1
  29. data/lib/granite/form/model/associations/reflections/embeds_many.rb +1 -1
  30. data/lib/granite/form/model/associations/reflections/embeds_one.rb +11 -1
  31. data/lib/granite/form/model/associations/reflections/references_any.rb +0 -4
  32. data/lib/granite/form/model/associations/reflections/singular.rb +0 -22
  33. data/lib/granite/form/model/associations.rb +0 -6
  34. data/lib/granite/form/model/attributes/attribute.rb +3 -21
  35. data/lib/granite/form/model/attributes/base.rb +5 -23
  36. data/lib/granite/form/model/attributes/reference_many.rb +1 -1
  37. data/lib/granite/form/model/attributes/reference_one.rb +1 -1
  38. data/lib/granite/form/model/attributes/reflections/attribute.rb +2 -8
  39. data/lib/granite/form/model/attributes/reflections/base/build_type_definition.rb +38 -0
  40. data/lib/granite/form/model/attributes/reflections/base.rb +20 -17
  41. data/lib/granite/form/model/attributes/reflections/collection/build_type_definition.rb +19 -0
  42. data/lib/granite/form/model/attributes/reflections/dictionary/build_type_definition.rb +19 -0
  43. data/lib/granite/form/model/attributes/reflections/dictionary.rb +0 -3
  44. data/lib/granite/form/model/attributes/reflections/reference_one.rb +0 -6
  45. data/lib/granite/form/model/attributes/reflections/represents/build_type_definition.rb +73 -0
  46. data/lib/granite/form/model/attributes/reflections/represents.rb +10 -2
  47. data/lib/granite/form/model/attributes/represents.rb +22 -37
  48. data/lib/granite/form/model/attributes.rb +10 -2
  49. data/lib/granite/form/model/persistence.rb +1 -19
  50. data/lib/granite/form/model/representation.rb +1 -0
  51. data/lib/granite/form/model/validations.rb +6 -0
  52. data/lib/granite/form/model.rb +1 -3
  53. data/lib/granite/form/types/active_support/time_zone.rb +2 -0
  54. data/lib/granite/form/types/array.rb +2 -0
  55. data/lib/granite/form/types/big_decimal.rb +2 -0
  56. data/lib/granite/form/types/boolean.rb +2 -0
  57. data/lib/granite/form/types/collection.rb +11 -0
  58. data/lib/granite/form/types/date.rb +2 -0
  59. data/lib/granite/form/types/date_time.rb +2 -0
  60. data/lib/granite/form/types/dictionary.rb +23 -0
  61. data/lib/granite/form/types/float.rb +2 -0
  62. data/lib/granite/form/types/has_subtype.rb +18 -0
  63. data/lib/granite/form/types/hash_with_action_controller_parameters.rb +2 -0
  64. data/lib/granite/form/types/integer.rb +2 -0
  65. data/lib/granite/form/types/object.rb +28 -0
  66. data/lib/granite/form/types/string.rb +2 -0
  67. data/lib/granite/form/types/time.rb +2 -0
  68. data/lib/granite/form/types/uuid.rb +2 -0
  69. data/lib/granite/form/types.rb +3 -0
  70. data/lib/granite/form/util.rb +55 -0
  71. data/lib/granite/form/version.rb +1 -1
  72. data/lib/granite/form.rb +1 -0
  73. data/spec/granite/form/active_record/associations_spec.rb +16 -18
  74. data/spec/granite/form/model/associations/embeds_many_spec.rb +29 -305
  75. data/spec/granite/form/model/associations/embeds_one_spec.rb +27 -212
  76. data/spec/granite/form/model/associations/nested_attributes_spec.rb +0 -95
  77. data/spec/granite/form/model/associations/references_many_spec.rb +5 -326
  78. data/spec/granite/form/model/associations/references_one_spec.rb +7 -279
  79. data/spec/granite/form/model/associations/reflections/embeds_any_spec.rb +1 -2
  80. data/spec/granite/form/model/associations/reflections/embeds_many_spec.rb +18 -26
  81. data/spec/granite/form/model/associations/reflections/embeds_one_spec.rb +16 -23
  82. data/spec/granite/form/model/associations/reflections/references_many_spec.rb +1 -1
  83. data/spec/granite/form/model/associations/reflections/references_one_spec.rb +1 -22
  84. data/spec/granite/form/model/associations/validations_spec.rb +0 -3
  85. data/spec/granite/form/model/associations_spec.rb +3 -24
  86. data/spec/granite/form/model/attributes/attribute_spec.rb +0 -29
  87. data/spec/granite/form/model/attributes/reflections/attribute_spec.rb +0 -9
  88. data/spec/granite/form/model/attributes/reflections/base/build_type_definition_spec.rb +27 -0
  89. data/spec/granite/form/model/attributes/reflections/base_spec.rb +16 -10
  90. data/spec/granite/form/model/attributes/reflections/collection/build_type_definition_spec.rb +24 -0
  91. data/spec/granite/form/model/attributes/reflections/dictionary/build_type_definition_spec.rb +24 -0
  92. data/spec/granite/form/model/attributes/reflections/dictionary_spec.rb +0 -6
  93. data/spec/granite/form/model/attributes/reflections/represents/build_type_definition_spec.rb +129 -0
  94. data/spec/granite/form/model/attributes/reflections/represents_spec.rb +43 -20
  95. data/spec/granite/form/model/attributes/represents_spec.rb +78 -55
  96. data/spec/granite/form/model/attributes_spec.rb +84 -23
  97. data/spec/granite/form/model/dirty_spec.rb +1 -7
  98. data/spec/granite/form/model/persistence_spec.rb +0 -2
  99. data/spec/granite/form/model/representation_spec.rb +4 -7
  100. data/spec/granite/form/model/validations/associated_spec.rb +2 -4
  101. data/spec/granite/form/model/validations/nested_spec.rb +2 -4
  102. data/spec/granite/form/model/validations_spec.rb +28 -1
  103. data/spec/granite/form/types/collection_spec.rb +22 -0
  104. data/spec/granite/form/types/dictionary_spec.rb +32 -0
  105. data/spec/granite/form/types/has_subtype_spec.rb +20 -0
  106. data/spec/granite/form/types/object_spec.rb +50 -4
  107. data/spec/granite/form/util_spec.rb +108 -0
  108. data/spec/spec_helper.rb +0 -15
  109. data/spec/support/active_record.rb +23 -0
  110. data/spec/support/shared/nested_attribute_examples.rb +3 -21
  111. metadata +56 -51
  112. data/.github/workflows/main.yml +0 -29
  113. data/gemfiles/rails.4.2.gemfile +0 -15
  114. data/lib/granite/form/model/attributes/collection.rb +0 -19
  115. data/lib/granite/form/model/attributes/dictionary.rb +0 -28
  116. data/lib/granite/form/model/attributes/localized.rb +0 -44
  117. data/lib/granite/form/model/attributes/reflections/localized.rb +0 -45
  118. data/lib/granite/form/model/callbacks.rb +0 -72
  119. data/lib/granite/form/model/lifecycle.rb +0 -309
  120. data/lib/granite/form/model/localization.rb +0 -26
  121. data/spec/granite/form/model/attributes/collection_spec.rb +0 -72
  122. data/spec/granite/form/model/attributes/dictionary_spec.rb +0 -100
  123. data/spec/granite/form/model/attributes/localized_spec.rb +0 -103
  124. data/spec/granite/form/model/attributes/reflections/localized_spec.rb +0 -37
  125. data/spec/granite/form/model/callbacks_spec.rb +0 -337
  126. data/spec/granite/form/model/lifecycle_spec.rb +0 -356
@@ -3,34 +3,6 @@ module Granite
3
3
  module Model
4
4
  module Associations
5
5
  class ReferencesOne < ReferencesAny
6
- def build(attributes = {})
7
- replace(build_object(attributes))
8
- end
9
-
10
- def create(attributes = {})
11
- persist_object(build(attributes))
12
- target
13
- end
14
-
15
- def create!(attributes = {})
16
- persist_object(build(attributes), raise_error: true)
17
- target
18
- end
19
-
20
- def apply_changes
21
- if target
22
- if target.marked_for_destruction? && reflection.autosave?
23
- target.destroy
24
- elsif target.new_record? || (reflection.autosave? && target.changed?)
25
- persist_object(target)
26
- else
27
- true
28
- end
29
- else
30
- true
31
- end
32
- end
33
-
34
6
  def target=(object)
35
7
  loaded!
36
8
  @target = object
@@ -22,7 +22,7 @@ module Granite
22
22
  Class.new(superclass || Granite::Form.base_class) do
23
23
  include Granite::Form::Model
24
24
  include Granite::Form::Model::Associations
25
- include Granite::Form::Model::Lifecycle
25
+ include Granite::Form::Model::Persistence
26
26
  include Granite::Form::Model::Primary
27
27
  include Granite::Form.base_concern if Granite::Form.base_concern
28
28
  end
@@ -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
@@ -53,10 +53,6 @@ module Granite
53
53
  def inspect
54
54
  "#{self.class.name.demodulize}(#{persistence_adapter.data_type})"
55
55
  end
56
-
57
- def autosave?
58
- !!options[:autosave]
59
- end
60
56
  end
61
57
  end
62
58
  end
@@ -4,28 +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
-
18
- def create_#{name} attributes = {}
19
- association(:#{name}).create(attributes)
20
- end
21
-
22
- def create_#{name}! attributes = {}
23
- association(:#{name}).create!(attributes)
24
- end
25
- RUBY
26
- end
27
- end
28
-
29
7
  def collection?
30
8
  false
31
9
  end
@@ -100,12 +100,6 @@ module Granite
100
100
  (@_associations ||= {})[reflection.name] ||= reflection.build_association(self)
101
101
  end
102
102
 
103
- def apply_association_changes!
104
- association_names.all? do |name|
105
- association(name).apply_changes!
106
- end
107
- end
108
-
109
103
  private
110
104
 
111
105
  def attributes_for_inspect
@@ -14,7 +14,7 @@ module Granite
14
14
 
15
15
  def read
16
16
  variable_cache(:value) do
17
- normalize(enumerize(type_definition.ensure_type(read_before_type_cast)))
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
- defaultizer.is_a?(Proc) ? evaluate(&defaultizer) : defaultizer
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(val, &normalizer)
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 :owner, :reflection
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(reflection, owner)
11
- @reflection = reflection
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
- !!(readonly.is_a?(Proc) ? evaluate(&readonly) : readonly)
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 < 0 ? 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}"
@@ -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| type_definition.ensure_type(id) }
8
+ read_before_type_cast.map { |id| type_definition.prepare(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
- type_definition.ensure_type(read_before_type_cast)
27
+ type_definition.prepare(read_before_type_cast)
28
28
  end
29
29
  end
30
30
 
@@ -4,10 +4,8 @@ module Granite
4
4
  module Attributes
5
5
  module Reflections
6
6
  class Attribute < Base
7
- def self.build(target, generated_methods, name, *args, &block)
8
- attribute = super(target, generated_methods, name, *args, &block)
9
- generate_methods name, generated_methods
10
- attribute
7
+ def self.attribute_class
8
+ Granite::Form::Model::Attributes::Attribute
11
9
  end
12
10
 
13
11
  def self.generate_methods(name, target)
@@ -46,10 +44,6 @@ module Granite
46
44
  @defaultizer ||= options[:default]
47
45
  end
48
46
 
49
- def enumerizer
50
- @enumerizer ||= options[:enum] || options[:in]
51
- end
52
-
53
47
  def normalizers
54
48
  @normalizers ||= Array.wrap(options[:normalize] || options[:normalizer] || options[:normalizers])
55
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
@@ -7,11 +7,9 @@ module Granite
7
7
  attr_reader :name, :options
8
8
 
9
9
  class << self
10
- def build(_target, _generated_methods, name, *args, &block)
11
- options = args.extract_options!
12
- options[:type] = args.first if args.first
13
- options[:default] = block if block
14
- new(name, options)
10
+ def build(_target, generated_methods, name, *args, &block)
11
+ generate_methods name, generated_methods
12
+ new(name, *args, &block)
15
13
  end
16
14
 
17
15
  def generate_methods(name, target) end
@@ -21,30 +19,35 @@ module Granite
21
19
  end
22
20
  end
23
21
 
24
- def initialize(name, options = {})
22
+ def initialize(name, *args, &block)
25
23
  @name = name.to_s
26
- @options = options
24
+
25
+ @options = args.extract_options!
26
+ @options[:type] = args.first if args.first
27
+ @options[:default] = block if block
27
28
  end
28
29
 
29
30
  def build_attribute(owner, raw_value = Granite::Form::UNDEFINED)
30
- attribute = self.class.attribute_class.new(self, owner)
31
+ type_definition = self.class::BuildTypeDefinition.new(owner, self).call
32
+ attribute = self.class.attribute_class.new(type_definition)
31
33
  attribute.write_value(raw_value, origin: :persistence) unless raw_value == Granite::Form::UNDEFINED
32
34
  attribute
33
35
  end
34
36
 
35
37
  def type
36
- @type ||= case options[:type]
37
- when Class, Module
38
- options[:type]
39
- when nil
40
- raise "Type is not specified for `#{name}`"
41
- else
42
- options[:type].to_s.camelize.constantize
43
- end
38
+ options[:type]
44
39
  end
45
40
 
46
41
  def readonly
47
- @readonly ||= options[:readonly]
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)
48
51
  end
49
52
 
50
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
@@ -4,9 +4,6 @@ module Granite
4
4
  module Attributes
5
5
  module Reflections
6
6
  class Dictionary < Attribute
7
- def keys
8
- @keys ||= Array.wrap(options[:keys]).map(&:to_s)
9
- end
10
7
  end
11
8
  end
12
9
  end
@@ -4,12 +4,6 @@ module Granite
4
4
  module Attributes
5
5
  module Reflections
6
6
  class ReferenceOne < Base
7
- def self.build(_target, generated_methods, name, *args)
8
- options = args.extract_options!
9
- generate_methods name, generated_methods
10
- new(name, options)
11
- end
12
-
13
7
  def self.generate_methods(name, target)
14
8
  target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
15
9
  def #{name}
@@ -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
- validates_nested = target.respond_to?(:validates_nested) && !target.validates_nested?(options[:of])
14
- target.validates_nested(options[:of]) if validates_nested
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 write(value)
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
- remove_variable(:cached_value, :cached_value_before_type_cast)
10
+
11
+ set_default_value
12
+ set_default_value_before_type_cast
19
13
  end
20
14
 
21
- def read
22
- reset if cached_value != read_value
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 read_before_type_cast
29
- reset if cached_value_before_type_cast != read_value_before_type_cast
30
- variable_cache(:value_before_type_cast) do
31
- defaultize(cached_value_before_type_cast)
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.send(reflection.reference)
30
+ owner.__send__(reflection.reference)
39
31
  end
40
32
 
41
- def read_value
42
- ref = reference
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
- def read_value_before_type_cast
51
- ref = reference
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 cached_value_before_type_cast
61
- variable_cache(:cached_value_before_type_cast) { read_value_before_type_cast }
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