active_data 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rspec +0 -1
- data/.rvmrc +1 -1
- data/.travis.yml +13 -6
- data/Appraisals +7 -0
- data/Gemfile +1 -5
- data/Guardfile +68 -15
- data/README.md +144 -2
- data/active_data.gemspec +19 -11
- data/gemfiles/rails.4.0.gemfile +14 -0
- data/gemfiles/rails.4.1.gemfile +14 -0
- data/gemfiles/rails.4.2.gemfile +14 -0
- data/gemfiles/rails.5.0.gemfile +14 -0
- data/lib/active_data.rb +120 -3
- data/lib/active_data/active_record/associations.rb +50 -0
- data/lib/active_data/active_record/nested_attributes.rb +24 -0
- data/lib/active_data/config.rb +40 -0
- data/lib/active_data/errors.rb +93 -0
- data/lib/active_data/extensions.rb +33 -0
- data/lib/active_data/model.rb +16 -74
- data/lib/active_data/model/associations.rb +84 -15
- data/lib/active_data/model/associations/base.rb +79 -0
- data/lib/active_data/model/associations/collection/embedded.rb +12 -0
- data/lib/active_data/model/associations/collection/proxy.rb +32 -0
- data/lib/active_data/model/associations/collection/referenced.rb +26 -0
- data/lib/active_data/model/associations/embeds_many.rb +124 -18
- data/lib/active_data/model/associations/embeds_one.rb +90 -15
- data/lib/active_data/model/associations/nested_attributes.rb +180 -0
- data/lib/active_data/model/associations/references_many.rb +96 -0
- data/lib/active_data/model/associations/references_one.rb +83 -0
- data/lib/active_data/model/associations/reflections/base.rb +100 -0
- data/lib/active_data/model/associations/reflections/embeds_many.rb +25 -0
- data/lib/active_data/model/associations/reflections/embeds_one.rb +49 -0
- data/lib/active_data/model/associations/reflections/reference_reflection.rb +45 -0
- data/lib/active_data/model/associations/reflections/references_many.rb +28 -0
- data/lib/active_data/model/associations/reflections/references_one.rb +28 -0
- data/lib/active_data/model/associations/validations.rb +63 -0
- data/lib/active_data/model/attributes.rb +247 -0
- data/lib/active_data/model/attributes/attribute.rb +73 -0
- data/lib/active_data/model/attributes/base.rb +116 -0
- data/lib/active_data/model/attributes/collection.rb +17 -0
- data/lib/active_data/model/attributes/dictionary.rb +26 -0
- data/lib/active_data/model/attributes/localized.rb +42 -0
- data/lib/active_data/model/attributes/reference_many.rb +21 -0
- data/lib/active_data/model/attributes/reference_one.rb +42 -0
- data/lib/active_data/model/attributes/reflections/attribute.rb +55 -0
- data/lib/active_data/model/attributes/reflections/base.rb +62 -0
- data/lib/active_data/model/attributes/reflections/collection.rb +10 -0
- data/lib/active_data/model/attributes/reflections/dictionary.rb +13 -0
- data/lib/active_data/model/attributes/reflections/localized.rb +43 -0
- data/lib/active_data/model/attributes/reflections/reference_many.rb +10 -0
- data/lib/active_data/model/attributes/reflections/reference_one.rb +58 -0
- data/lib/active_data/model/attributes/reflections/represents.rb +55 -0
- data/lib/active_data/model/attributes/represents.rb +64 -0
- data/lib/active_data/model/callbacks.rb +71 -0
- data/lib/active_data/model/conventions.rb +35 -0
- data/lib/active_data/model/dirty.rb +77 -0
- data/lib/active_data/model/lifecycle.rb +307 -0
- data/lib/active_data/model/localization.rb +21 -0
- data/lib/active_data/model/persistence.rb +57 -0
- data/lib/active_data/model/primary.rb +51 -0
- data/lib/active_data/model/scopes.rb +77 -0
- data/lib/active_data/model/validations.rb +27 -0
- data/lib/active_data/model/validations/associated.rb +19 -0
- data/lib/active_data/model/validations/nested.rb +39 -0
- data/lib/active_data/railtie.rb +7 -0
- data/lib/active_data/version.rb +1 -1
- data/spec/lib/active_data/active_record/associations_spec.rb +149 -0
- data/spec/lib/active_data/active_record/nested_attributes_spec.rb +16 -0
- data/spec/lib/active_data/config_spec.rb +44 -0
- data/spec/lib/active_data/model/associations/embeds_many_spec.rb +362 -52
- data/spec/lib/active_data/model/associations/embeds_one_spec.rb +250 -31
- data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +23 -0
- data/spec/lib/active_data/model/associations/references_many_spec.rb +196 -0
- data/spec/lib/active_data/model/associations/references_one_spec.rb +134 -0
- data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +144 -0
- data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +116 -0
- data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +255 -0
- data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +208 -0
- data/spec/lib/active_data/model/associations/validations_spec.rb +153 -0
- data/spec/lib/active_data/model/associations_spec.rb +189 -0
- data/spec/lib/active_data/model/attributes/attribute_spec.rb +144 -0
- data/spec/lib/active_data/model/attributes/base_spec.rb +82 -0
- data/spec/lib/active_data/model/attributes/collection_spec.rb +73 -0
- data/spec/lib/active_data/model/attributes/dictionary_spec.rb +93 -0
- data/spec/lib/active_data/model/attributes/localized_spec.rb +88 -33
- data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +72 -0
- data/spec/lib/active_data/model/attributes/reflections/base_spec.rb +56 -0
- data/spec/lib/active_data/model/attributes/reflections/collection_spec.rb +37 -0
- data/spec/lib/active_data/model/attributes/reflections/dictionary_spec.rb +43 -0
- data/spec/lib/active_data/model/attributes/reflections/localized_spec.rb +37 -0
- data/spec/lib/active_data/model/attributes/reflections/represents_spec.rb +70 -0
- data/spec/lib/active_data/model/attributes/represents_spec.rb +153 -0
- data/spec/lib/active_data/model/attributes_spec.rb +243 -0
- data/spec/lib/active_data/model/callbacks_spec.rb +338 -0
- data/spec/lib/active_data/model/conventions_spec.rb +12 -0
- data/spec/lib/active_data/model/dirty_spec.rb +75 -0
- data/spec/lib/active_data/model/lifecycle_spec.rb +330 -0
- data/spec/lib/active_data/model/nested_attributes.rb +202 -0
- data/spec/lib/active_data/model/persistence_spec.rb +47 -0
- data/spec/lib/active_data/model/primary_spec.rb +84 -0
- data/spec/lib/active_data/model/scopes_spec.rb +88 -0
- data/spec/lib/active_data/model/typecasting_spec.rb +192 -0
- data/spec/lib/active_data/model/validations/associated_spec.rb +94 -0
- data/spec/lib/active_data/model/validations/nested_spec.rb +93 -0
- data/spec/lib/active_data/model/validations_spec.rb +31 -0
- data/spec/lib/active_data/model_spec.rb +1 -32
- data/spec/lib/active_data_spec.rb +12 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/model_helpers.rb +10 -0
- metadata +246 -54
- data/gemfiles/Gemfile.rails-3 +0 -14
- data/lib/active_data/attributes/base.rb +0 -69
- data/lib/active_data/attributes/localized.rb +0 -42
- data/lib/active_data/model/associations/association.rb +0 -30
- data/lib/active_data/model/attributable.rb +0 -122
- data/lib/active_data/model/collectionizable.rb +0 -55
- data/lib/active_data/model/collectionizable/proxy.rb +0 -42
- data/lib/active_data/model/extensions.rb +0 -9
- data/lib/active_data/model/extensions/array.rb +0 -24
- data/lib/active_data/model/extensions/big_decimal.rb +0 -17
- data/lib/active_data/model/extensions/boolean.rb +0 -38
- data/lib/active_data/model/extensions/date.rb +0 -17
- data/lib/active_data/model/extensions/date_time.rb +0 -17
- data/lib/active_data/model/extensions/float.rb +0 -17
- data/lib/active_data/model/extensions/hash.rb +0 -22
- data/lib/active_data/model/extensions/integer.rb +0 -17
- data/lib/active_data/model/extensions/localized.rb +0 -22
- data/lib/active_data/model/extensions/object.rb +0 -17
- data/lib/active_data/model/extensions/string.rb +0 -17
- data/lib/active_data/model/extensions/time.rb +0 -17
- data/lib/active_data/model/localizable.rb +0 -31
- data/lib/active_data/model/nested_attributes.rb +0 -58
- data/lib/active_data/model/parameterizable.rb +0 -29
- data/lib/active_data/validations.rb +0 -7
- data/lib/active_data/validations/associated.rb +0 -17
- data/spec/lib/active_data/model/attributable_spec.rb +0 -191
- data/spec/lib/active_data/model/collectionizable_spec.rb +0 -60
- data/spec/lib/active_data/model/nested_attributes_spec.rb +0 -67
- data/spec/lib/active_data/model/type_cast_spec.rb +0 -116
- data/spec/lib/active_data/validations/associated_spec.rb +0 -88
@@ -0,0 +1,79 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
class Base
|
5
|
+
attr_accessor :owner, :reflection
|
6
|
+
delegate :macro, :collection?, to: :reflection
|
7
|
+
|
8
|
+
def initialize owner, reflection
|
9
|
+
@owner, @reflection = owner, reflection
|
10
|
+
@evar_loaded = owner.persisted?
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset
|
15
|
+
@loaded = false
|
16
|
+
@target = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def evar_loaded?
|
20
|
+
!!@evar_loaded
|
21
|
+
end
|
22
|
+
|
23
|
+
def loaded?
|
24
|
+
!!@loaded
|
25
|
+
end
|
26
|
+
|
27
|
+
def loaded!
|
28
|
+
@evar_loaded = true
|
29
|
+
@loaded = true
|
30
|
+
end
|
31
|
+
|
32
|
+
def target
|
33
|
+
return @target if loaded?
|
34
|
+
self.target = load_target
|
35
|
+
end
|
36
|
+
|
37
|
+
def reload
|
38
|
+
reset
|
39
|
+
target
|
40
|
+
end
|
41
|
+
|
42
|
+
def apply_changes!
|
43
|
+
apply_changes or raise ActiveData::AssociationChangesNotApplied
|
44
|
+
end
|
45
|
+
|
46
|
+
def transaction &block
|
47
|
+
data = Marshal.load(Marshal.dump(read_source))
|
48
|
+
block.call
|
49
|
+
rescue StandardError => e
|
50
|
+
write_source data
|
51
|
+
reload
|
52
|
+
raise e
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"#<#{reflection.macro.to_s.camelize} #{target.inspect.truncate(50, omission: collection? ? '...]' : '...')}>"
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def read_source
|
62
|
+
reflection.read_source owner
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_source value
|
66
|
+
reflection.write_source owner, value
|
67
|
+
end
|
68
|
+
|
69
|
+
def target_for_inspect
|
70
|
+
if value.length > 50
|
71
|
+
"#{value[0..50]}...".inspect
|
72
|
+
else
|
73
|
+
value.inspect
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
module Collection
|
5
|
+
class Proxy
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
delegate :target, :save, :save!, :loaded?, :reload, :clear, :concat, to: :@association
|
9
|
+
delegate :each, :size, :length, :first, :last, :empty?, :many?, :==, :dup, to: :target
|
10
|
+
alias_method :<<, :concat
|
11
|
+
alias_method :push, :concat
|
12
|
+
|
13
|
+
def initialize(association)
|
14
|
+
@association = association
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_ary
|
18
|
+
dup
|
19
|
+
end
|
20
|
+
alias_method :to_a, :to_ary
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
entries = target.take(10).map!(&:inspect)
|
24
|
+
entries[10] = '...' if target.size > 10
|
25
|
+
|
26
|
+
"#<#{self.class.name.demodulize} [#{entries.join(', ')}]>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
module Collection
|
5
|
+
class Referenced < Proxy
|
6
|
+
METHODS_EXCLUDED_FROM_DELEGATION = %w[build create create!].map(&:to_sym).freeze
|
7
|
+
delegate :scope, to: :@association
|
8
|
+
|
9
|
+
def method_missing(method, *args, &block)
|
10
|
+
delegate_to_scope?(method) ? scope.send(method, *args, &block) : super
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(method, include_private = false)
|
14
|
+
delegate_to_scope?(method) || super
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def delegate_to_scope?(method)
|
20
|
+
METHODS_EXCLUDED_FROM_DELEGATION.exclude?(method) && scope.respond_to?(method)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,33 +1,139 @@
|
|
1
1
|
module ActiveData
|
2
2
|
module Model
|
3
3
|
module Associations
|
4
|
-
class EmbedsMany <
|
4
|
+
class EmbedsMany < Base
|
5
|
+
def build attributes = {}
|
6
|
+
push_object(reflection.klass.new(attributes))
|
7
|
+
end
|
8
|
+
|
9
|
+
def create attributes = {}
|
10
|
+
build(attributes).tap(&:save)
|
11
|
+
end
|
12
|
+
|
13
|
+
def create! attributes = {}
|
14
|
+
build(attributes).tap(&:save!)
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroyed
|
18
|
+
@destroyed ||= []
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply_changes
|
22
|
+
result = target.map do |object|
|
23
|
+
object.destroyed? || object.marked_for_destruction? ? object.destroy : object.save
|
24
|
+
end.all?
|
25
|
+
@destroyed = target.select(&:destroyed?)
|
26
|
+
target.delete_if(&:destroyed?)
|
27
|
+
result
|
28
|
+
end
|
5
29
|
|
6
|
-
def
|
7
|
-
|
30
|
+
def target= objects
|
31
|
+
objects.each { |object| setup_performers! object }
|
32
|
+
loaded!
|
33
|
+
@target = objects
|
8
34
|
end
|
9
35
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
36
|
+
def load_target
|
37
|
+
source = read_source
|
38
|
+
source.present? ? reflection.klass.instantiate_collection(source) : default
|
39
|
+
end
|
40
|
+
|
41
|
+
def default
|
42
|
+
unless evar_loaded?
|
43
|
+
default = Array.wrap(reflection.default(owner))
|
44
|
+
if default.present?
|
45
|
+
collection = if default.all? { |object| object.is_a?(reflection.klass) }
|
46
|
+
default
|
47
|
+
else
|
48
|
+
default.map do |attributes|
|
49
|
+
reflection.klass.with_sanitize(false) do
|
50
|
+
reflection.klass.new(attributes)
|
51
|
+
end
|
52
|
+
end
|
16
53
|
end
|
54
|
+
collection.map { |object| object.send(:clear_changes_information) } if reflection.klass.dirty?
|
55
|
+
collection
|
17
56
|
end
|
18
|
-
|
57
|
+
end || []
|
19
58
|
end
|
20
59
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
60
|
+
def reset
|
61
|
+
super
|
62
|
+
@target = []
|
63
|
+
end
|
64
|
+
|
65
|
+
def clear
|
66
|
+
transaction { target.all?(&:destroy!) } rescue ActiveData::ObjectNotDestroyed
|
67
|
+
reload.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
def reader force_reload = false
|
71
|
+
reload if force_reload
|
72
|
+
@proxy ||= Collection::Embedded.new self
|
73
|
+
end
|
74
|
+
|
75
|
+
def replace objects
|
76
|
+
transaction do
|
77
|
+
clear
|
78
|
+
append(objects) or raise ActiveData::AssociationChangesNotApplied
|
79
|
+
end
|
80
|
+
end
|
81
|
+
alias_method :writer, :replace
|
82
|
+
|
83
|
+
def concat *objects
|
84
|
+
append objects.flatten
|
28
85
|
end
|
29
86
|
|
87
|
+
private
|
88
|
+
|
89
|
+
def read_source
|
90
|
+
super || []
|
91
|
+
end
|
92
|
+
|
93
|
+
def append objects
|
94
|
+
objects.each do |object|
|
95
|
+
raise AssociationTypeMismatch.new(reflection.klass, object.class) unless object && object.is_a?(reflection.klass)
|
96
|
+
push_object object
|
97
|
+
end
|
98
|
+
result = owner.persisted? ? apply_changes : true
|
99
|
+
result && target
|
100
|
+
end
|
101
|
+
|
102
|
+
def push_object object
|
103
|
+
setup_performers! object
|
104
|
+
target[target.size] = object
|
105
|
+
end
|
106
|
+
|
107
|
+
def setup_performers! object
|
108
|
+
association = self
|
109
|
+
|
110
|
+
object.define_create do
|
111
|
+
source = association.send(:read_source)
|
112
|
+
index = association.target.select do |one|
|
113
|
+
one.persisted? || one === self
|
114
|
+
end.index { |one| one === self }
|
115
|
+
|
116
|
+
source.insert(index, attributes)
|
117
|
+
association.send(:write_source, source)
|
118
|
+
end
|
119
|
+
|
120
|
+
object.define_update do
|
121
|
+
source = association.send(:read_source)
|
122
|
+
index = association.target.select(&:persisted?).index { |one| one === self }
|
123
|
+
|
124
|
+
source[index] = attributes
|
125
|
+
association.send(:write_source, source)
|
126
|
+
end
|
127
|
+
|
128
|
+
object.define_destroy do
|
129
|
+
source = association.send(:read_source)
|
130
|
+
index = association.target.select(&:persisted?).index { |one| one === self }
|
131
|
+
|
132
|
+
source.delete_at(index) if index
|
133
|
+
association.send(:write_source, source)
|
134
|
+
end
|
135
|
+
end
|
30
136
|
end
|
31
137
|
end
|
32
138
|
end
|
33
|
-
end
|
139
|
+
end
|
@@ -1,30 +1,105 @@
|
|
1
1
|
module ActiveData
|
2
2
|
module Model
|
3
3
|
module Associations
|
4
|
-
class EmbedsOne <
|
4
|
+
class EmbedsOne < Base
|
5
|
+
def build attributes = {}
|
6
|
+
self.target = reflection.klass.new(attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create attributes = {}
|
10
|
+
build(attributes).tap(&:save)
|
11
|
+
end
|
5
12
|
|
6
|
-
def
|
7
|
-
|
13
|
+
def create! attributes = {}
|
14
|
+
build(attributes).tap(&:save!)
|
8
15
|
end
|
9
16
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
17
|
+
def destroyed
|
18
|
+
@destroyed
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply_changes
|
22
|
+
if target
|
23
|
+
if target.destroyed? || target.marked_for_destruction?
|
24
|
+
@destroyed = target
|
25
|
+
clear
|
26
|
+
else
|
27
|
+
target.save
|
14
28
|
end
|
15
|
-
|
29
|
+
else
|
30
|
+
true
|
31
|
+
end
|
16
32
|
end
|
17
33
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
34
|
+
def target= object
|
35
|
+
setup_performers! object if object
|
36
|
+
loaded!
|
37
|
+
@target = object
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_target
|
41
|
+
source = read_source
|
42
|
+
source ? reflection.klass.instantiate(source) : default
|
43
|
+
end
|
44
|
+
|
45
|
+
def default
|
46
|
+
unless evar_loaded?
|
47
|
+
default = reflection.default(owner)
|
48
|
+
if default
|
49
|
+
object = if default.is_a?(reflection.klass)
|
50
|
+
default
|
51
|
+
else
|
52
|
+
reflection.klass.with_sanitize(false) do
|
53
|
+
reflection.klass.new(default)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
object.send(:clear_changes_information) if reflection.klass.dirty?
|
57
|
+
object
|
23
58
|
end
|
24
|
-
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear
|
63
|
+
target.try(:destroy)
|
64
|
+
reload.nil?
|
25
65
|
end
|
26
66
|
|
67
|
+
def reader force_reload = false
|
68
|
+
reload if force_reload
|
69
|
+
target
|
70
|
+
end
|
71
|
+
|
72
|
+
def replace object
|
73
|
+
if object
|
74
|
+
raise AssociationTypeMismatch.new(reflection.klass, object.class) unless object.is_a?(reflection.klass)
|
75
|
+
transaction do
|
76
|
+
clear
|
77
|
+
self.target = object
|
78
|
+
apply_changes! if owner.persisted?
|
79
|
+
end
|
80
|
+
else
|
81
|
+
clear
|
82
|
+
end
|
83
|
+
|
84
|
+
target
|
85
|
+
end
|
86
|
+
alias_method :writer, :replace
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def setup_performers! object
|
91
|
+
association = self
|
92
|
+
|
93
|
+
object.define_save do
|
94
|
+
association.send(:write_source, attributes)
|
95
|
+
end
|
96
|
+
|
97
|
+
object.define_destroy do
|
98
|
+
association.send(:write_source, nil)
|
99
|
+
true
|
100
|
+
end
|
101
|
+
end
|
27
102
|
end
|
28
103
|
end
|
29
104
|
end
|
30
|
-
end
|
105
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
module NestedAttributes
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
DESTROY_ATTRIBUTE = '_destroy'
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :nested_attributes_options, instance_writer: false
|
11
|
+
self.nested_attributes_options = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
class NestedAttributesMethods
|
15
|
+
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == DESTROY_ATTRIBUTE || value.blank? } }
|
16
|
+
|
17
|
+
def self.accepts_nested_attributes_for(klass, *attr_names)
|
18
|
+
options = { allow_destroy: false, update_only: false }
|
19
|
+
options.update(attr_names.extract_options!)
|
20
|
+
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
|
21
|
+
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
22
|
+
|
23
|
+
attr_names.each do |association_name|
|
24
|
+
if reflection = klass.reflect_on_association(association_name)
|
25
|
+
klass.nested_attributes_options = klass.nested_attributes_options.merge(association_name.to_sym => options)
|
26
|
+
|
27
|
+
klass.validates_nested association_name if klass.respond_to?(:validates_nested)
|
28
|
+
type = (reflection.collection? ? :collection : :one_to_one)
|
29
|
+
klass.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
30
|
+
def #{association_name}_attributes=(attributes)
|
31
|
+
ActiveData::Model::Associations::NestedAttributes::NestedAttributesMethods
|
32
|
+
.assign_nested_attributes_for_#{type}_association(self, :#{association_name}, attributes)
|
33
|
+
end
|
34
|
+
METHOD
|
35
|
+
else
|
36
|
+
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.assign_nested_attributes_for_one_to_one_association(object, association_name, attributes)
|
42
|
+
options = object.nested_attributes_options[association_name]
|
43
|
+
attributes = attributes.with_indifferent_access
|
44
|
+
|
45
|
+
association = object.association(association_name)
|
46
|
+
existing_record = association.target
|
47
|
+
primary_attribute_name = primary_name_for(association.reflection.klass)
|
48
|
+
if existing_record
|
49
|
+
primary_attribute = existing_record.attribute(primary_attribute_name)
|
50
|
+
primary_attribute_value = primary_attribute.typecast(attributes[primary_attribute_name]) if primary_attribute
|
51
|
+
end
|
52
|
+
|
53
|
+
if existing_record && (!primary_attribute || options[:update_only] || existing_record.primary_attribute == primary_attribute_value)
|
54
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(object, association_name, attributes)
|
55
|
+
elsif attributes[primary_attribute_name].present?
|
56
|
+
raise ActiveData::ObjectNotFound.new(object, association_name, attributes[primary_attribute_name])
|
57
|
+
elsif !reject_new_object?(object, association_name, attributes)
|
58
|
+
assignable_attributes = attributes.except(*unassignable_keys(object))
|
59
|
+
|
60
|
+
if existing_record && !existing_record.persisted?
|
61
|
+
existing_record.assign_attributes(assignable_attributes)
|
62
|
+
else
|
63
|
+
association.build(assignable_attributes)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.assign_nested_attributes_for_collection_association(object, association_name, attributes_collection)
|
69
|
+
options = object.nested_attributes_options[association_name]
|
70
|
+
|
71
|
+
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
72
|
+
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
73
|
+
end
|
74
|
+
|
75
|
+
check_record_limit!(options[:limit], attributes_collection)
|
76
|
+
|
77
|
+
association = object.association(association_name)
|
78
|
+
primary_attribute_name = primary_name_for(association.reflection.klass)
|
79
|
+
|
80
|
+
raise ActiveData::UndefinedPrimaryAttribute.new(object.class, association_name) unless primary_attribute_name
|
81
|
+
|
82
|
+
if attributes_collection.is_a? Hash
|
83
|
+
keys = attributes_collection.keys
|
84
|
+
attributes_collection = if keys.include?(primary_attribute_name) || keys.include?(primary_attribute_name.to_sym)
|
85
|
+
[attributes_collection]
|
86
|
+
else
|
87
|
+
attributes_collection.values
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
attributes_collection.each do |attributes|
|
92
|
+
attributes = attributes.with_indifferent_access
|
93
|
+
|
94
|
+
if attributes[primary_attribute_name].blank?
|
95
|
+
unless reject_new_object?(object, association_name, attributes)
|
96
|
+
association.build(attributes.except(*unassignable_keys(object)))
|
97
|
+
end
|
98
|
+
else
|
99
|
+
existing_record = association.target.detect do |record|
|
100
|
+
primary_attribute_value = record.attribute(primary_attribute_name)
|
101
|
+
.typecast(attributes[primary_attribute_name])
|
102
|
+
record.primary_attribute == primary_attribute_value
|
103
|
+
end
|
104
|
+
if existing_record
|
105
|
+
if !call_reject_if(object, association_name, attributes)
|
106
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
|
107
|
+
end
|
108
|
+
else
|
109
|
+
if association.reflection.embedded?
|
110
|
+
unless reject_new_object?(object, association_name, attributes)
|
111
|
+
association.reflection.klass.with_sanitize(false) do
|
112
|
+
association.build(attributes.except(DESTROY_ATTRIBUTE))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
else
|
116
|
+
raise ActiveData::ObjectNotFound.new(object, association_name, attributes[primary_attribute_name])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.check_record_limit!(limit, attributes_collection)
|
124
|
+
if limit
|
125
|
+
limit = case limit
|
126
|
+
when Symbol
|
127
|
+
send(limit)
|
128
|
+
when Proc
|
129
|
+
limit.call
|
130
|
+
else
|
131
|
+
limit
|
132
|
+
end
|
133
|
+
|
134
|
+
if limit && attributes_collection.size > limit
|
135
|
+
raise ActiveData::TooManyObjects.new(limit, attributes_collection.size)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.assign_to_or_mark_for_destruction(object, attributes, allow_destroy)
|
141
|
+
object.assign_attributes(attributes.except(*unassignable_keys(object)))
|
142
|
+
object.mark_for_destruction if has_destroy_flag?(attributes) && allow_destroy
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.has_destroy_flag?(hash)
|
146
|
+
ActiveData.typecaster(Boolean).call(hash[DESTROY_ATTRIBUTE])
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.reject_new_object?(object, association_name, attributes)
|
150
|
+
has_destroy_flag?(attributes) || call_reject_if(object, association_name, attributes)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.call_reject_if(object, association_name, attributes)
|
154
|
+
return false if has_destroy_flag?(attributes)
|
155
|
+
case callback = object.nested_attributes_options[association_name][:reject_if]
|
156
|
+
when Symbol
|
157
|
+
method(callback).arity == 0 ? send(callback) : send(callback, attributes)
|
158
|
+
when Proc
|
159
|
+
callback.call(attributes)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.unassignable_keys(object)
|
164
|
+
[primary_name_for(object.class), DESTROY_ATTRIBUTE].compact
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.primary_name_for(klass)
|
168
|
+
klass < ActiveData::Model ? klass.primary_name : 'id'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
module ClassMethods
|
173
|
+
def accepts_nested_attributes_for(*attr_names)
|
174
|
+
NestedAttributesMethods.accepts_nested_attributes_for self, *attr_names
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|