active_data 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|