granite-form 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,42 @@
1
+ module Granite
2
+ module Form
3
+ class Config
4
+ include Singleton
5
+
6
+ attr_accessor :include_root_in_json, :i18n_scope, :logger, :primary_attribute, :base_class, :base_concern,
7
+ :_normalizers, :_typecasters
8
+
9
+ def self.delegated
10
+ public_instance_methods - superclass.public_instance_methods - Singleton.public_instance_methods
11
+ end
12
+
13
+ def initialize
14
+ @include_root_in_json = false
15
+ @i18n_scope = :granite
16
+ @logger = Logger.new(STDERR)
17
+ @primary_attribute = :id
18
+ @_normalizers = {}
19
+ @_typecasters = {}
20
+ end
21
+
22
+ def normalizer(name, &block)
23
+ if block
24
+ _normalizers[name.to_sym] = block
25
+ else
26
+ _normalizers[name.to_sym] or raise NormalizerMissing, name
27
+ end
28
+ end
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
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,111 @@
1
+ module Granite
2
+ module Form
3
+ class Error < StandardError
4
+ end
5
+
6
+ class NotFound < Error
7
+ end
8
+
9
+ # Backported from active_model 5
10
+ class ValidationError < Error
11
+ attr_reader :model
12
+
13
+ def initialize(model)
14
+ @model = model
15
+ errors = @model.errors.full_messages.join(', ')
16
+ super(I18n.t(:"#{@model.class.i18n_scope}.errors.messages.model_invalid", errors: errors, default: :'errors.messages.model_invalid'))
17
+ end
18
+ end
19
+
20
+ class UnsavableObject < Error
21
+ end
22
+
23
+ class UndestroyableObject < Error
24
+ end
25
+
26
+ class ObjectNotSaved < Error
27
+ end
28
+
29
+ class ObjectNotDestroyed < Error
30
+ end
31
+
32
+ class AssociationChangesNotApplied < Error
33
+ end
34
+
35
+ class AssociationTypeMismatch < Error
36
+ def initialize(expected, got)
37
+ super "Expected `#{expected}` (##{expected.object_id}), but got `#{got}` (##{got.object_id})"
38
+ end
39
+ end
40
+
41
+ class ObjectNotFound < Error
42
+ def initialize(object, association_name, record_id)
43
+ message = "Couldn't find #{object.class.reflect_on_association(association_name).klass.name}" \
44
+ "with #{object.respond_to?(:_primary_name) ? object._primary_name : 'id'} = #{record_id} for #{object.inspect}"
45
+ super message
46
+ end
47
+ end
48
+
49
+ class TooManyObjects < Error
50
+ def initialize(limit, actual_size)
51
+ super "Maximum #{limit} objects are allowed. Got #{actual_size} objects instead."
52
+ end
53
+ end
54
+
55
+ class UndefinedPrimaryAttribute < Error
56
+ def initialize(klass, association_name)
57
+ super <<-MESSAGE
58
+ Undefined primary attribute for `#{association_name}` in #{klass}.
59
+ It is required for embeds_many nested attributes proper operation.
60
+ You can define this association as:
61
+
62
+ embeds_many :#{association_name} do
63
+ primary :attribute_name
64
+ end
65
+ MESSAGE
66
+ end
67
+ end
68
+
69
+ class NormalizerMissing < NoMethodError
70
+ def initialize(name)
71
+ super <<-MESSAGE
72
+ Could not find normalizer `:#{name}`
73
+ You can define it with:
74
+
75
+ Granite::Form.normalizer(:#{name}) do |value, options|
76
+ # do some staff with value and options
77
+ end
78
+ MESSAGE
79
+ end
80
+ end
81
+
82
+ class TypecasterMissing < NoMethodError
83
+ def initialize(*classes)
84
+ classes = classes.flatten
85
+ super <<-MESSAGE
86
+ Could not find typecaster for #{classes}
87
+ You can define it with:
88
+
89
+ Granite::Form.typecaster('#{classes.first}') do |value|
90
+ # do some staff with value and options
91
+ end
92
+ MESSAGE
93
+ end
94
+ end
95
+
96
+ class PersistenceAdapterMissing < NoMethodError
97
+ def initialize(data_source)
98
+ super <<-MESSAGE
99
+ Could not find persistence adapter for #{data_source}
100
+ You can define it with:
101
+
102
+ class #{data_source}
103
+ def self.granite_persistence_adapter
104
+ #{data_source}GraniteFormPersistenceAdapter
105
+ end
106
+ end
107
+ MESSAGE
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,36 @@
1
+ class Boolean; end unless defined?(Boolean)
2
+
3
+ begin
4
+ require 'uuidtools'
5
+ rescue LoadError
6
+ nil
7
+ else
8
+ module Granite
9
+ module Form
10
+ class UUID < UUIDTools::UUID
11
+ def as_json(*_)
12
+ to_s
13
+ end
14
+
15
+ def to_param
16
+ to_s
17
+ end
18
+
19
+ def self.parse_string(value)
20
+ return nil if value.length.zero?
21
+ if value.length == 36
22
+ parse value
23
+ elsif value.length == 32
24
+ parse_hexdigest value
25
+ else
26
+ parse_raw value
27
+ end
28
+ end
29
+
30
+ def inspect
31
+ "#<Granite::Form::UUID:#{self}>"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,97 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ class Base
6
+ attr_accessor :owner, :reflection
7
+ delegate :macro, :collection?, to: :reflection
8
+
9
+ def initialize(owner, reflection)
10
+ @owner = owner
11
+ @reflection = reflection
12
+ @evar_loaded = owner.persisted?
13
+ reset
14
+ end
15
+
16
+ def reset
17
+ @loaded = false
18
+ @target = nil
19
+ end
20
+
21
+ def evar_loaded?
22
+ !!@evar_loaded
23
+ end
24
+
25
+ def loaded?
26
+ !!@loaded
27
+ end
28
+
29
+ def loaded!
30
+ @evar_loaded = true
31
+ @loaded = true
32
+ end
33
+
34
+ def target
35
+ return @target if loaded?
36
+ self.target = load_target
37
+ end
38
+
39
+ def reload
40
+ reset
41
+ target
42
+ end
43
+
44
+ def apply_changes!
45
+ apply_changes or raise Granite::Form::AssociationChangesNotApplied
46
+ end
47
+
48
+ def callback(name, object)
49
+ evaluator = reflection.options[name]
50
+ return true unless evaluator
51
+
52
+ if evaluator.is_a?(Proc)
53
+ if evaluator.arity == 1
54
+ owner.instance_exec(object, &evaluator)
55
+ else
56
+ evaluator.call(owner, object)
57
+ end
58
+ else
59
+ owner.send(evaluator, object)
60
+ end
61
+ end
62
+
63
+ def transaction
64
+ data = read_source.deep_dup
65
+ yield
66
+ rescue StandardError => e
67
+ write_source data
68
+ reload
69
+ raise e
70
+ end
71
+
72
+ def inspect
73
+ "#<#{reflection.macro.to_s.camelize} #{target.inspect.truncate(50, omission: collection? ? '...]' : '...')}>"
74
+ end
75
+
76
+ private
77
+
78
+ def read_source
79
+ reflection.read_source owner
80
+ end
81
+
82
+ def write_source(value)
83
+ reflection.write_source owner, value
84
+ end
85
+
86
+ def target_for_inspect
87
+ if value.length > 50
88
+ "#{value[0..50]}...".inspect
89
+ else
90
+ value.inspect
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,14 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Collection
6
+ class Embedded < Proxy
7
+ delegate :build, :create, :create!, to: :@association
8
+ alias_method :new, :build
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,35 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ module Collection
6
+ class Proxy
7
+ include Enumerable
8
+
9
+ delegate :target, :save, :save!, :loaded?, :reload, :clear, :concat, to: :@association
10
+ delegate :each, :size, :length, :first, :last, :empty?, :many?, :==, :dup, to: :target
11
+ alias_method :<<, :concat
12
+ alias_method :push, :concat
13
+
14
+ def initialize(association)
15
+ @association = association
16
+ end
17
+
18
+ def to_ary
19
+ dup
20
+ end
21
+
22
+ alias_method :to_a, :to_ary
23
+
24
+ def inspect
25
+ entries = target.take(10).map!(&:inspect)
26
+ entries[10] = '...' if target.size > 10
27
+
28
+ "#<#{self.class.name.demodulize} [#{entries.join(', ')}]>"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ class EmbedsAny < Base
6
+ private
7
+
8
+ def build_object(attributes)
9
+ reflection.klass.new(attributes)
10
+ end
11
+
12
+ def embed_object(object)
13
+ object.instance_variable_set(:@embedder, owner)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,152 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ class EmbedsMany < EmbedsAny
6
+ def build(attributes = {})
7
+ push_object(build_object(attributes))
8
+ end
9
+
10
+ def create(attributes = {})
11
+ build(attributes).tap(&:save)
12
+ end
13
+
14
+ def create!(attributes = {})
15
+ build(attributes).tap(&:save!)
16
+ end
17
+
18
+ def destroyed
19
+ @destroyed ||= []
20
+ end
21
+
22
+ def apply_changes
23
+ result = target.map do |object|
24
+ object.destroyed? || object.marked_for_destruction? ? object.destroy : object.save
25
+ end.all?
26
+ @destroyed = target.select(&:destroyed?)
27
+ target.delete_if(&:destroyed?)
28
+ result
29
+ end
30
+
31
+ def target=(objects)
32
+ objects.each { |object| setup_performers! object }
33
+ loaded!
34
+ @target = objects
35
+ end
36
+
37
+ def load_target
38
+ source = read_source
39
+ source.present? ? reflection.klass.instantiate_collection(source) : default
40
+ end
41
+
42
+ def default
43
+ unless evar_loaded?
44
+ default = Array.wrap(reflection.default(owner))
45
+ if default.present?
46
+ collection = if default.all? { |object| object.is_a?(reflection.klass) }
47
+ default
48
+ else
49
+ default.map do |attributes|
50
+ reflection.klass.with_sanitize(false) do
51
+ build_object(attributes)
52
+ end
53
+ end
54
+ end
55
+ collection.map { |object| object.send(:clear_changes_information) } if reflection.klass.dirty?
56
+ collection
57
+ end
58
+ end || []
59
+ end
60
+
61
+ def reset
62
+ super
63
+ @target = []
64
+ end
65
+
66
+ def clear
67
+ begin
68
+ transaction { target.all?(&:destroy!) }
69
+ rescue Granite::Form::ObjectNotDestroyed
70
+ nil
71
+ end
72
+ reload.empty?
73
+ end
74
+
75
+ def reader(force_reload = false)
76
+ reload if force_reload
77
+ @proxy ||= Collection::Embedded.new self
78
+ end
79
+
80
+ def replace(objects)
81
+ transaction do
82
+ clear
83
+ append(objects) or raise Granite::Form::AssociationChangesNotApplied
84
+ end
85
+ end
86
+
87
+ alias_method :writer, :replace
88
+
89
+ def concat(*objects)
90
+ append objects.flatten
91
+ end
92
+
93
+ private
94
+
95
+ def read_source
96
+ super || []
97
+ end
98
+
99
+ def append(objects)
100
+ objects.each do |object|
101
+ raise AssociationTypeMismatch.new(reflection.klass, object.class) unless object && object.is_a?(reflection.klass)
102
+ push_object object
103
+ end
104
+ result = owner.persisted? ? apply_changes : true
105
+ result && target
106
+ end
107
+
108
+ def push_object(object)
109
+ setup_performers! object
110
+ target[target.size] = object
111
+ object
112
+ end
113
+
114
+ def setup_performers!(object)
115
+ embed_object(object)
116
+ callback(:before_add, object)
117
+
118
+ association = self
119
+
120
+ object.define_create do
121
+ source = association.send(:read_source)
122
+ index = association.target
123
+ .select { |one| one.persisted? || one.equal?(self) }
124
+ .index { |one| one.equal?(self) }
125
+
126
+ source.insert(index, attributes)
127
+ association.send(:write_source, source)
128
+ end
129
+
130
+ object.define_update do
131
+ source = association.send(:read_source)
132
+ index = association.target.select(&:persisted?).index { |one| one.equal?(self) }
133
+
134
+ source[index] = attributes
135
+ association.send(:write_source, source)
136
+ end
137
+
138
+ object.define_destroy do
139
+ source = association.send(:read_source)
140
+ index = association.target.select(&:persisted?).index { |one| one.equal?(self) }
141
+
142
+ source.delete_at(index) if index
143
+ association.send(:write_source, source)
144
+ end
145
+
146
+ callback(:after_add, object)
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,112 @@
1
+ module Granite
2
+ module Form
3
+ module Model
4
+ module Associations
5
+ class EmbedsOne < EmbedsAny
6
+ attr_reader :destroyed
7
+
8
+ def build(attributes = {})
9
+ self.target = build_object(attributes)
10
+ end
11
+
12
+ def create(attributes = {})
13
+ build(attributes).tap(&:save)
14
+ end
15
+
16
+ def create!(attributes = {})
17
+ build(attributes).tap(&:save!)
18
+ end
19
+
20
+ def apply_changes
21
+ if target
22
+ if target.destroyed? || target.marked_for_destruction?
23
+ @destroyed = target
24
+ clear
25
+ else
26
+ target.save
27
+ end
28
+ else
29
+ true
30
+ end
31
+ end
32
+
33
+ def target=(object)
34
+ if object
35
+ callback(:before_add, object)
36
+ setup_performers! object
37
+ end
38
+ loaded!
39
+ @target = object
40
+ callback(:after_add, object) if object
41
+ end
42
+
43
+ def load_target
44
+ source = read_source
45
+ source ? reflection.klass.instantiate(source) : default
46
+ end
47
+
48
+ def default
49
+ return if evar_loaded?
50
+
51
+ default = reflection.default(owner)
52
+
53
+ return unless default
54
+
55
+ object = if default.is_a?(reflection.klass)
56
+ default
57
+ else
58
+ reflection.klass.with_sanitize(false) do
59
+ build_object(default)
60
+ end
61
+ end
62
+ object.send(:clear_changes_information) if reflection.klass.dirty?
63
+ object
64
+ end
65
+
66
+ def clear
67
+ target.try(:destroy)
68
+ reload.nil?
69
+ end
70
+
71
+ def reader(force_reload = false)
72
+ reload if force_reload
73
+ target
74
+ end
75
+
76
+ def replace(object)
77
+ if object
78
+ raise AssociationTypeMismatch.new(reflection.klass, object.class) unless object.is_a?(reflection.klass)
79
+ transaction do
80
+ clear
81
+ self.target = object
82
+ apply_changes! if owner.persisted?
83
+ end
84
+ else
85
+ clear
86
+ end
87
+
88
+ target
89
+ end
90
+
91
+ alias_method :writer, :replace
92
+
93
+ private
94
+
95
+ def setup_performers!(object)
96
+ embed_object(object)
97
+ association = self
98
+
99
+ object.define_save do
100
+ association.send(:write_source, attributes)
101
+ end
102
+
103
+ object.define_destroy do
104
+ association.send(:write_source, nil)
105
+ true
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end