granite-form 0.1.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.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +13 -0
  3. data/.github/workflows/ci.yml +35 -0
  4. data/.github/workflows/main.yml +29 -0
  5. data/.gitignore +21 -0
  6. data/.rspec +2 -0
  7. data/.rubocop.yml +64 -0
  8. data/.rubocop_todo.yml +48 -0
  9. data/Appraisals +8 -0
  10. data/CHANGELOG.md +73 -0
  11. data/Gemfile +8 -0
  12. data/Guardfile +77 -0
  13. data/LICENSE +22 -0
  14. data/README.md +429 -0
  15. data/Rakefile +6 -0
  16. data/gemfiles/rails.4.2.gemfile +15 -0
  17. data/gemfiles/rails.5.0.gemfile +15 -0
  18. data/gemfiles/rails.5.1.gemfile +15 -0
  19. data/gemfiles/rails.5.2.gemfile +15 -0
  20. data/gemfiles/rails.6.0.gemfile +14 -0
  21. data/gemfiles/rails.6.1.gemfile +14 -0
  22. data/gemfiles/rails.7.0.gemfile +14 -0
  23. data/granite-form.gemspec +31 -0
  24. data/lib/granite/form/active_record/associations.rb +57 -0
  25. data/lib/granite/form/active_record/nested_attributes.rb +20 -0
  26. data/lib/granite/form/base.rb +15 -0
  27. data/lib/granite/form/config.rb +42 -0
  28. data/lib/granite/form/errors.rb +111 -0
  29. data/lib/granite/form/extensions.rb +36 -0
  30. data/lib/granite/form/model/associations/base.rb +97 -0
  31. data/lib/granite/form/model/associations/collection/embedded.rb +14 -0
  32. data/lib/granite/form/model/associations/collection/proxy.rb +35 -0
  33. data/lib/granite/form/model/associations/embeds_any.rb +19 -0
  34. data/lib/granite/form/model/associations/embeds_many.rb +152 -0
  35. data/lib/granite/form/model/associations/embeds_one.rb +112 -0
  36. data/lib/granite/form/model/associations/nested_attributes.rb +215 -0
  37. data/lib/granite/form/model/associations/persistence_adapters/active_record/referenced_proxy.rb +33 -0
  38. data/lib/granite/form/model/associations/persistence_adapters/active_record.rb +68 -0
  39. data/lib/granite/form/model/associations/persistence_adapters/base.rb +55 -0
  40. data/lib/granite/form/model/associations/references_any.rb +43 -0
  41. data/lib/granite/form/model/associations/references_many.rb +113 -0
  42. data/lib/granite/form/model/associations/references_one.rb +88 -0
  43. data/lib/granite/form/model/associations/reflections/base.rb +92 -0
  44. data/lib/granite/form/model/associations/reflections/embeds_any.rb +52 -0
  45. data/lib/granite/form/model/associations/reflections/embeds_many.rb +17 -0
  46. data/lib/granite/form/model/associations/reflections/embeds_one.rb +19 -0
  47. data/lib/granite/form/model/associations/reflections/references_any.rb +65 -0
  48. data/lib/granite/form/model/associations/reflections/references_many.rb +30 -0
  49. data/lib/granite/form/model/associations/reflections/references_one.rb +32 -0
  50. data/lib/granite/form/model/associations/reflections/singular.rb +37 -0
  51. data/lib/granite/form/model/associations/validations.rb +41 -0
  52. data/lib/granite/form/model/associations.rb +120 -0
  53. data/lib/granite/form/model/attributes/attribute.rb +75 -0
  54. data/lib/granite/form/model/attributes/base.rb +134 -0
  55. data/lib/granite/form/model/attributes/collection.rb +19 -0
  56. data/lib/granite/form/model/attributes/dictionary.rb +28 -0
  57. data/lib/granite/form/model/attributes/localized.rb +44 -0
  58. data/lib/granite/form/model/attributes/reference_many.rb +21 -0
  59. data/lib/granite/form/model/attributes/reference_one.rb +52 -0
  60. data/lib/granite/form/model/attributes/reflections/attribute.rb +61 -0
  61. data/lib/granite/form/model/attributes/reflections/base.rb +62 -0
  62. data/lib/granite/form/model/attributes/reflections/collection.rb +12 -0
  63. data/lib/granite/form/model/attributes/reflections/dictionary.rb +15 -0
  64. data/lib/granite/form/model/attributes/reflections/localized.rb +45 -0
  65. data/lib/granite/form/model/attributes/reflections/reference_many.rb +12 -0
  66. data/lib/granite/form/model/attributes/reflections/reference_one.rb +49 -0
  67. data/lib/granite/form/model/attributes/reflections/represents.rb +56 -0
  68. data/lib/granite/form/model/attributes/represents.rb +67 -0
  69. data/lib/granite/form/model/attributes.rb +204 -0
  70. data/lib/granite/form/model/callbacks.rb +72 -0
  71. data/lib/granite/form/model/conventions.rb +40 -0
  72. data/lib/granite/form/model/dirty.rb +84 -0
  73. data/lib/granite/form/model/lifecycle.rb +309 -0
  74. data/lib/granite/form/model/localization.rb +26 -0
  75. data/lib/granite/form/model/persistence.rb +59 -0
  76. data/lib/granite/form/model/primary.rb +59 -0
  77. data/lib/granite/form/model/representation.rb +101 -0
  78. data/lib/granite/form/model/scopes.rb +118 -0
  79. data/lib/granite/form/model/validations/associated.rb +22 -0
  80. data/lib/granite/form/model/validations/nested.rb +56 -0
  81. data/lib/granite/form/model/validations.rb +29 -0
  82. data/lib/granite/form/model.rb +33 -0
  83. data/lib/granite/form/railtie.rb +9 -0
  84. data/lib/granite/form/undefined_class.rb +11 -0
  85. data/lib/granite/form/version.rb +5 -0
  86. data/lib/granite/form.rb +163 -0
  87. data/spec/lib/granite/form/active_record/associations_spec.rb +211 -0
  88. data/spec/lib/granite/form/active_record/nested_attributes_spec.rb +15 -0
  89. data/spec/lib/granite/form/config_spec.rb +66 -0
  90. data/spec/lib/granite/form/model/associations/embeds_many_spec.rb +706 -0
  91. data/spec/lib/granite/form/model/associations/embeds_one_spec.rb +533 -0
  92. data/spec/lib/granite/form/model/associations/nested_attributes_spec.rb +119 -0
  93. data/spec/lib/granite/form/model/associations/persistence_adapters/active_record_spec.rb +58 -0
  94. data/spec/lib/granite/form/model/associations/references_many_spec.rb +572 -0
  95. data/spec/lib/granite/form/model/associations/references_one_spec.rb +445 -0
  96. data/spec/lib/granite/form/model/associations/reflections/embeds_any_spec.rb +42 -0
  97. data/spec/lib/granite/form/model/associations/reflections/embeds_many_spec.rb +145 -0
  98. data/spec/lib/granite/form/model/associations/reflections/embeds_one_spec.rb +117 -0
  99. data/spec/lib/granite/form/model/associations/reflections/references_many_spec.rb +303 -0
  100. data/spec/lib/granite/form/model/associations/reflections/references_one_spec.rb +287 -0
  101. data/spec/lib/granite/form/model/associations/validations_spec.rb +137 -0
  102. data/spec/lib/granite/form/model/associations_spec.rb +198 -0
  103. data/spec/lib/granite/form/model/attributes/attribute_spec.rb +186 -0
  104. data/spec/lib/granite/form/model/attributes/base_spec.rb +97 -0
  105. data/spec/lib/granite/form/model/attributes/collection_spec.rb +72 -0
  106. data/spec/lib/granite/form/model/attributes/dictionary_spec.rb +100 -0
  107. data/spec/lib/granite/form/model/attributes/localized_spec.rb +103 -0
  108. data/spec/lib/granite/form/model/attributes/reflections/attribute_spec.rb +72 -0
  109. data/spec/lib/granite/form/model/attributes/reflections/base_spec.rb +56 -0
  110. data/spec/lib/granite/form/model/attributes/reflections/collection_spec.rb +37 -0
  111. data/spec/lib/granite/form/model/attributes/reflections/dictionary_spec.rb +43 -0
  112. data/spec/lib/granite/form/model/attributes/reflections/localized_spec.rb +37 -0
  113. data/spec/lib/granite/form/model/attributes/reflections/represents_spec.rb +70 -0
  114. data/spec/lib/granite/form/model/attributes/represents_spec.rb +85 -0
  115. data/spec/lib/granite/form/model/attributes_spec.rb +350 -0
  116. data/spec/lib/granite/form/model/callbacks_spec.rb +337 -0
  117. data/spec/lib/granite/form/model/conventions_spec.rb +11 -0
  118. data/spec/lib/granite/form/model/dirty_spec.rb +84 -0
  119. data/spec/lib/granite/form/model/lifecycle_spec.rb +356 -0
  120. data/spec/lib/granite/form/model/persistence_spec.rb +46 -0
  121. data/spec/lib/granite/form/model/primary_spec.rb +84 -0
  122. data/spec/lib/granite/form/model/representation_spec.rb +139 -0
  123. data/spec/lib/granite/form/model/scopes_spec.rb +86 -0
  124. data/spec/lib/granite/form/model/typecasting_spec.rb +193 -0
  125. data/spec/lib/granite/form/model/validations/associated_spec.rb +102 -0
  126. data/spec/lib/granite/form/model/validations/nested_spec.rb +164 -0
  127. data/spec/lib/granite/form/model/validations_spec.rb +31 -0
  128. data/spec/lib/granite/form/model_spec.rb +10 -0
  129. data/spec/lib/granite/form_spec.rb +11 -0
  130. data/spec/shared/nested_attribute_examples.rb +332 -0
  131. data/spec/spec_helper.rb +50 -0
  132. data/spec/support/model_helpers.rb +10 -0
  133. data/spec/support/muffle_helper.rb +7 -0
  134. metadata +403 -0
@@ -0,0 +1,92 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Reflections
6
+ class Base
7
+ READ = ->(reflection, object) { object.read_attribute reflection.name }
8
+ WRITE = ->(reflection, object, value) { object.write_attribute reflection.name, value }
9
+
10
+ attr_reader :name, :options
11
+ # AR compatibility
12
+ attr_accessor :parent_reflection
13
+ delegate :association_class, to: 'self.class'
14
+
15
+ def self.build(target, generated_methods, name, options = {}, &_block)
16
+ generate_methods name, generated_methods
17
+ if options.delete(:validate) &&
18
+ target.respond_to?(:validates_nested) &&
19
+ !target.validates_nested?(name)
20
+ target.validates_nested name
21
+ end
22
+ new(name, options)
23
+ end
24
+
25
+ def self.generate_methods(name, target)
26
+ target.class_eval <<-RUBY, __FILE__, __LINE__ + 1
27
+ def #{name} force_reload = false
28
+ association(:#{name}).reader(force_reload)
29
+ end
30
+
31
+ def #{name}= value
32
+ association(:#{name}).writer(value)
33
+ end
34
+ RUBY
35
+ end
36
+
37
+ def self.association_class
38
+ @association_class ||= "Granite::Form::Model::Associations::#{name.demodulize}".constantize
39
+ end
40
+
41
+ def initialize(name, options = {})
42
+ @name = name.to_sym
43
+ @options = options
44
+ end
45
+
46
+ def macro
47
+ self.class.name.demodulize.underscore.to_sym
48
+ end
49
+
50
+ def klass
51
+ @klass ||= (options[:class_name].presence || name.to_s.classify).to_s.constantize
52
+ end
53
+
54
+ # AR compatibility
55
+ def belongs_to?
56
+ false
57
+ end
58
+
59
+ def build_association(object)
60
+ self.class.association_class.new object, self
61
+ end
62
+
63
+ def read_source(object)
64
+ (options[:read] || READ).call(self, object)
65
+ end
66
+
67
+ def write_source(object, value)
68
+ (options[:write] || WRITE).call(self, object, value)
69
+ end
70
+
71
+ def default(object)
72
+ defaultizer = options[:default]
73
+ if defaultizer.is_a?(Proc)
74
+ if defaultizer.arity.nonzero?
75
+ defaultizer.call(object)
76
+ else
77
+ object.instance_exec(&defaultizer)
78
+ end
79
+ else
80
+ defaultizer
81
+ end
82
+ end
83
+
84
+ def collection?
85
+ true
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,52 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Reflections
6
+ class EmbedsAny < Base
7
+ class << self
8
+ def build(target, generated_methods, name, options = {}, &block)
9
+ if block
10
+ options[:class] = proc do |reflection|
11
+ superclass = reflection.options[:class_name].to_s.presence.try(:constantize)
12
+ klass = build_class(superclass)
13
+ target.const_set(name.to_s.classify, klass)
14
+ klass.class_eval(&block)
15
+ klass
16
+ end
17
+ end
18
+ super
19
+ end
20
+
21
+ private def build_class(superclass)
22
+ Class.new(superclass || Granite::Form.base_class) do
23
+ include Granite::Form::Model
24
+ include Granite::Form::Model::Associations
25
+ include Granite::Form::Model::Lifecycle
26
+ include Granite::Form::Model::Primary
27
+ include Granite::Form.base_concern if Granite::Form.base_concern
28
+ end
29
+ end
30
+ end
31
+
32
+ def klass
33
+ @klass ||= if options[:class]
34
+ options[:class].call(self)
35
+ else
36
+ super
37
+ end
38
+ end
39
+
40
+ def inspect
41
+ "#{self.class.name.demodulize}(#{klass})"
42
+ end
43
+
44
+ def embedded?
45
+ true
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,17 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Reflections
6
+ class EmbedsMany < EmbedsAny
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
9
+ options[:validate] = true unless options.key?(:validate)
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Reflections
6
+ class EmbedsOne < EmbedsAny
7
+ include Singular
8
+
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
11
+ options[:validate] = true unless options.key?(:validate)
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Reflections
6
+ class ReferencesAny < Base
7
+ def self.build(_target, generated_methods, name, *args)
8
+ reflection = new(name, *args)
9
+ generate_methods name, generated_methods
10
+ reflection
11
+ end
12
+
13
+ def self.persistence_adapter(klass)
14
+ adapter = klass.granite_persistence_adapter if klass.respond_to?(:granite_persistence_adapter)
15
+ adapter or raise PersistenceAdapterMissing, klass
16
+ end
17
+
18
+ delegate :primary_key, to: :persistence_adapter
19
+
20
+ def initialize(name, *args)
21
+ @options = args.extract_options!
22
+ @scope_proc = args.first
23
+ @name = name.to_sym
24
+ end
25
+
26
+ def klass
27
+ @klass ||= if options[:data_source].present?
28
+ options[:data_source].to_s.constantize
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ alias_method :data_source, :klass
35
+
36
+ def persistence_adapter
37
+ @persistence_adapter ||= self.class.persistence_adapter(klass)
38
+ .new(data_source, options[:primary_key], @scope_proc)
39
+ end
40
+
41
+ def read_source(object)
42
+ object.read_attribute(reference_key)
43
+ end
44
+
45
+ def write_source(object, value)
46
+ object.write_attribute(reference_key, value)
47
+ end
48
+
49
+ def embedded?
50
+ false
51
+ end
52
+
53
+ def inspect
54
+ "#{self.class.name.demodulize}(#{persistence_adapter.data_type})"
55
+ end
56
+
57
+ def autosave?
58
+ !!options[:autosave]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,30 @@
1
+ require 'granite/form/model/attributes/reflections/reference_many'
2
+ require 'granite/form/model/attributes/reference_many'
3
+
4
+ module Granite
5
+ module Form
6
+ module Model
7
+ module Associations
8
+ module Reflections
9
+ class ReferencesMany < ReferencesAny
10
+ def self.build(target, generated_methods, name, *args, &block)
11
+ reflection = super
12
+
13
+ target.add_attribute(
14
+ Granite::Form::Model::Attributes::Reflections::ReferenceMany,
15
+ reflection.reference_key, association: name
16
+ )
17
+
18
+ reflection
19
+ end
20
+
21
+ def reference_key
22
+ @reference_key ||= options[:reference_key].presence.try(:to_sym) ||
23
+ :"#{name.to_s.singularize}_#{primary_key.to_s.pluralize}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,32 @@
1
+ require 'granite/form/model/attributes/reflections/reference_one'
2
+ require 'granite/form/model/attributes/reference_one'
3
+
4
+ module Granite
5
+ module Form
6
+ module Model
7
+ module Associations
8
+ module Reflections
9
+ class ReferencesOne < ReferencesAny
10
+ include Singular
11
+
12
+ def self.build(target, generated_methods, name, *args, &block)
13
+ reflection = super
14
+
15
+ target.add_attribute(
16
+ Granite::Form::Model::Attributes::Reflections::ReferenceOne,
17
+ reflection.reference_key, association: name
18
+ )
19
+
20
+ reflection
21
+ end
22
+
23
+ def reference_key
24
+ @reference_key ||= options[:reference_key].presence.try(:to_sym) ||
25
+ :"#{name}_#{primary_key}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Reflections
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
+ def collection?
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,41 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Validations
6
+ def valid_ancestry?
7
+ errors.clear
8
+ validate_nested!
9
+ run_validations!
10
+ end
11
+
12
+ alias_method :validate_ancestry, :valid_ancestry?
13
+
14
+ def invalid_ancestry?
15
+ !valid_ancestry?
16
+ end
17
+
18
+ def validate_ancestry!
19
+ valid_ancestry? || raise_validation_error
20
+ end
21
+
22
+ private
23
+
24
+ def validate_nested!
25
+ association_names.each do |name|
26
+ association = association(name)
27
+ invalid_block = if association.reflection.klass.method_defined?(:invalid_ansestry?)
28
+ ->(object) { object.invalid_ansestry? }
29
+ else
30
+ ->(object) { object.invalid? }
31
+ end
32
+
33
+ Granite::Form::Model::Validations::NestedValidator
34
+ .validate_nested(self, name, association.target, &invalid_block)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,120 @@
1
+ require 'granite/form/model/associations/collection/proxy'
2
+ require 'granite/form/model/associations/collection/embedded'
3
+
4
+ require 'granite/form/model/associations/reflections/base'
5
+ require 'granite/form/model/associations/reflections/singular'
6
+ require 'granite/form/model/associations/reflections/embeds_any'
7
+ require 'granite/form/model/associations/reflections/embeds_one'
8
+ require 'granite/form/model/associations/reflections/embeds_many'
9
+ require 'granite/form/model/associations/reflections/references_any'
10
+ require 'granite/form/model/associations/reflections/references_one'
11
+ require 'granite/form/model/associations/reflections/references_many'
12
+
13
+ require 'granite/form/model/associations/base'
14
+ require 'granite/form/model/associations/embeds_any'
15
+ require 'granite/form/model/associations/embeds_one'
16
+ require 'granite/form/model/associations/embeds_many'
17
+ require 'granite/form/model/associations/references_any'
18
+ require 'granite/form/model/associations/references_one'
19
+ require 'granite/form/model/associations/references_many'
20
+
21
+ require 'granite/form/model/associations/nested_attributes'
22
+ require 'granite/form/model/associations/validations'
23
+
24
+ module Granite
25
+ module Form
26
+ module Model
27
+ module Associations
28
+ extend ActiveSupport::Concern
29
+
30
+ included do
31
+ include NestedAttributes
32
+
33
+ class_attribute :_associations, :_association_aliases, instance_reader: false, instance_writer: false
34
+ self._associations = {}
35
+ self._association_aliases = {}
36
+
37
+ delegate :association_names, to: 'self.class'
38
+
39
+ {
40
+ embeds_many: Reflections::EmbedsMany,
41
+ embeds_one: Reflections::EmbedsOne,
42
+ references_one: Reflections::ReferencesOne,
43
+ references_many: Reflections::ReferencesMany
44
+ }.each do |(name, reflection_class)|
45
+ define_singleton_method name do |*args, &block|
46
+ reflection = reflection_class.build self, generated_associations_methods, *args, &block
47
+ self._associations = _associations.merge(reflection.name => reflection)
48
+ reflection
49
+ end
50
+ end
51
+ end
52
+
53
+ module ClassMethods
54
+ def reflections
55
+ _associations
56
+ end
57
+
58
+ def alias_association(alias_name, association_name)
59
+ reflection = reflect_on_association(association_name)
60
+ raise ArgumentError, "Can't alias undefined association `#{attribute_name}` on #{self}" unless reflection
61
+ reflection.class.generate_methods alias_name, generated_associations_methods
62
+ self._association_aliases = _association_aliases.merge(alias_name.to_sym => reflection.name)
63
+ reflection
64
+ end
65
+
66
+ def reflect_on_association(name)
67
+ name = name.to_sym
68
+ _associations[_association_aliases[name] || name]
69
+ end
70
+
71
+ def association_names
72
+ _associations.keys
73
+ end
74
+
75
+ private
76
+
77
+ def attributes_for_inspect
78
+ (_associations.map do |name, reflection|
79
+ "#{name}: #{reflection.inspect}"
80
+ end + [super]).join(', ')
81
+ end
82
+
83
+ def generated_associations_methods
84
+ @generated_associations_methods ||= const_set(:GeneratedAssociationsMethods, Module.new)
85
+ .tap { |proxy| include proxy }
86
+ end
87
+ end
88
+
89
+ def ==(other)
90
+ super && association_names.all? do |association|
91
+ public_send(association) == other.public_send(association)
92
+ end
93
+ end
94
+
95
+ alias_method :eql?, :==
96
+
97
+ def association(name)
98
+ reflection = self.class.reflect_on_association(name)
99
+ return unless reflection
100
+ (@_associations ||= {})[reflection.name] ||= reflection.build_association(self)
101
+ end
102
+
103
+ def apply_association_changes!
104
+ association_names.all? do |name|
105
+ association(name).apply_changes!
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def attributes_for_inspect
112
+ (association_names.map do |name|
113
+ association = association(name)
114
+ "#{name}: #{association.inspect}"
115
+ end + [super]).join(', ')
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,75 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Attributes
5
+ class Attribute < Base
6
+ delegate :defaultizer, :enumerizer, :normalizers, to: :reflection
7
+
8
+ def write(value)
9
+ return if readonly?
10
+ pollute do
11
+ write_value value
12
+ end
13
+ end
14
+
15
+ def read
16
+ variable_cache(:value) do
17
+ normalize(enumerize(typecast(read_before_type_cast)))
18
+ end
19
+ end
20
+
21
+ def read_before_type_cast
22
+ variable_cache(:value_before_type_cast) do
23
+ defaultize(@value_cache)
24
+ end
25
+ end
26
+
27
+ def default
28
+ defaultizer.is_a?(Proc) ? evaluate(&defaultizer) : defaultizer
29
+ end
30
+
31
+ def defaultize(value, default_value = nil)
32
+ !defaultizer.nil? && value.nil? ? default_value || default : value
33
+ end
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
+ def normalize(value)
54
+ if normalizers.none?
55
+ value
56
+ else
57
+ normalizers.inject(value) do |val, normalizer|
58
+ case normalizer
59
+ when Proc
60
+ evaluate(val, &normalizer)
61
+ when Hash
62
+ normalizer.inject(val) do |v, (name, options)|
63
+ Granite::Form.normalizer(name).call(v, options, self)
64
+ end
65
+ else
66
+ Granite::Form.normalizer(normalizer).call(val, {}, self)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end