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,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