active_data 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.codeclimate.yml +13 -0
- data/.rubocop.yml +56 -0
- data/.rubocop_todo.yml +53 -0
- data/.rvmrc +1 -1
- data/.travis.yml +15 -2
- data/Appraisals +1 -1
- data/CHANGELOG.md +31 -0
- data/Guardfile +8 -8
- data/README.md +256 -0
- data/Rakefile +2 -4
- data/active_data.gemspec +8 -7
- data/gemfiles/rails.4.0.gemfile +1 -1
- data/gemfiles/rails.4.1.gemfile +1 -1
- data/gemfiles/rails.4.2.gemfile +1 -1
- data/gemfiles/rails.5.0.gemfile +1 -1
- data/gemfiles/rails.5.1.gemfile +14 -0
- data/lib/active_data/active_record/associations.rb +18 -13
- data/lib/active_data/active_record/nested_attributes.rb +8 -14
- data/lib/active_data/base.rb +13 -0
- data/lib/active_data/config.rb +4 -4
- data/lib/active_data/errors.rb +29 -13
- data/lib/active_data/extensions.rb +22 -21
- data/lib/active_data/model/associations/base.rb +22 -6
- data/lib/active_data/model/associations/embeds_any.rb +17 -0
- data/lib/active_data/model/associations/embeds_many.rb +29 -19
- data/lib/active_data/model/associations/embeds_one.rb +30 -26
- data/lib/active_data/model/associations/nested_attributes.rb +82 -50
- data/lib/active_data/model/associations/persistence_adapters/active_record/referenced_proxy.rb +31 -0
- data/lib/active_data/model/associations/persistence_adapters/active_record.rb +66 -0
- data/lib/active_data/model/associations/persistence_adapters/base.rb +53 -0
- data/lib/active_data/model/associations/references_any.rb +41 -0
- data/lib/active_data/model/associations/references_many.rb +51 -37
- data/lib/active_data/model/associations/references_one.rb +43 -41
- data/lib/active_data/model/associations/reflections/base.rb +19 -29
- data/lib/active_data/model/associations/reflections/embeds_any.rb +43 -0
- data/lib/active_data/model/associations/reflections/embeds_many.rb +3 -13
- data/lib/active_data/model/associations/reflections/embeds_one.rb +5 -37
- data/lib/active_data/model/associations/reflections/references_any.rb +62 -0
- data/lib/active_data/model/associations/reflections/references_many.rb +7 -7
- data/lib/active_data/model/associations/reflections/references_one.rb +9 -7
- data/lib/active_data/model/associations/reflections/singular.rb +35 -0
- data/lib/active_data/model/associations/validations.rb +2 -27
- data/lib/active_data/model/associations.rb +12 -10
- data/lib/active_data/model/attributes/attribute.rb +10 -10
- data/lib/active_data/model/attributes/base.rb +8 -7
- data/lib/active_data/model/attributes/localized.rb +4 -4
- data/lib/active_data/model/attributes/reference_many.rb +6 -8
- data/lib/active_data/model/attributes/reference_one.rb +17 -9
- data/lib/active_data/model/attributes/reflections/attribute.rb +2 -2
- data/lib/active_data/model/attributes/reflections/base.rb +8 -11
- data/lib/active_data/model/attributes/reflections/localized.rb +2 -2
- data/lib/active_data/model/attributes/reflections/reference_one.rb +11 -22
- data/lib/active_data/model/attributes/reflections/represents.rb +5 -6
- data/lib/active_data/model/attributes/represents.rb +6 -5
- data/lib/active_data/model/attributes.rb +33 -87
- data/lib/active_data/model/callbacks.rb +6 -7
- data/lib/active_data/model/conventions.rb +2 -0
- data/lib/active_data/model/dirty.rb +4 -4
- data/lib/active_data/model/lifecycle.rb +18 -20
- data/lib/active_data/model/localization.rb +5 -2
- data/lib/active_data/model/persistence.rb +2 -2
- data/lib/active_data/model/primary.rb +19 -14
- data/lib/active_data/model/representation.rb +81 -0
- data/lib/active_data/model/scopes.rb +22 -12
- data/lib/active_data/model/validations/associated.rb +3 -2
- data/lib/active_data/model/validations/nested.rb +6 -1
- data/lib/active_data/model/validations.rb +3 -3
- data/lib/active_data/model.rb +2 -1
- data/lib/active_data/undefined_class.rb +9 -0
- data/lib/active_data/version.rb +1 -1
- data/lib/active_data.rb +40 -17
- data/spec/lib/active_data/active_record/associations_spec.rb +107 -45
- data/spec/lib/active_data/active_record/nested_attributes_spec.rb +1 -2
- data/spec/lib/active_data/config_spec.rb +37 -15
- data/spec/lib/active_data/model/associations/embeds_many_spec.rb +475 -172
- data/spec/lib/active_data/model/associations/embeds_one_spec.rb +353 -96
- data/spec/lib/active_data/model/associations/nested_attributes_spec.rb +108 -12
- data/spec/lib/active_data/model/associations/persistence_adapters/active_record_spec.rb +58 -0
- data/spec/lib/active_data/model/associations/references_many_spec.rb +440 -64
- data/spec/lib/active_data/model/associations/references_one_spec.rb +347 -36
- data/spec/lib/active_data/model/associations/reflections/embeds_many_spec.rb +8 -7
- data/spec/lib/active_data/model/associations/reflections/embeds_one_spec.rb +7 -6
- data/spec/lib/active_data/model/associations/reflections/references_many_spec.rb +81 -33
- data/spec/lib/active_data/model/associations/reflections/references_one_spec.rb +116 -37
- data/spec/lib/active_data/model/associations/validations_spec.rb +27 -43
- data/spec/lib/active_data/model/associations_spec.rb +34 -25
- data/spec/lib/active_data/model/attributes/attribute_spec.rb +26 -23
- data/spec/lib/active_data/model/attributes/base_spec.rb +5 -6
- data/spec/lib/active_data/model/attributes/collection_spec.rb +7 -8
- data/spec/lib/active_data/model/attributes/dictionary_spec.rb +40 -33
- data/spec/lib/active_data/model/attributes/localized_spec.rb +27 -28
- data/spec/lib/active_data/model/attributes/reflections/attribute_spec.rb +6 -6
- data/spec/lib/active_data/model/attributes/represents_spec.rb +10 -78
- data/spec/lib/active_data/model/attributes_spec.rb +150 -45
- data/spec/lib/active_data/model/callbacks_spec.rb +69 -70
- data/spec/lib/active_data/model/conventions_spec.rb +0 -1
- data/spec/lib/active_data/model/dirty_spec.rb +22 -13
- data/spec/lib/active_data/model/lifecycle_spec.rb +49 -23
- data/spec/lib/active_data/model/persistence_spec.rb +5 -6
- data/spec/lib/active_data/model/representation_spec.rb +126 -0
- data/spec/lib/active_data/model/scopes_spec.rb +1 -3
- data/spec/lib/active_data/model/typecasting_spec.rb +6 -5
- data/spec/lib/active_data/model/validations/associated_spec.rb +26 -18
- data/spec/lib/active_data/model/validations/nested_spec.rb +89 -18
- data/spec/lib/active_data/model_spec.rb +1 -2
- data/spec/lib/active_data_spec.rb +0 -1
- data/spec/shared/nested_attribute_examples.rb +332 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/model_helpers.rb +2 -2
- data/spec/support/muffle_helper.rb +7 -0
- metadata +52 -18
- data/lib/active_data/model/associations/collection/referenced.rb +0 -26
- data/lib/active_data/model/associations/reflections/reference_reflection.rb +0 -45
- data/spec/lib/active_data/model/nested_attributes.rb +0 -202
@@ -4,37 +4,62 @@ module ActiveData
|
|
4
4
|
module NestedAttributes
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
DESTROY_ATTRIBUTE = '_destroy'
|
7
|
+
DESTROY_ATTRIBUTE = '_destroy'.freeze
|
8
8
|
|
9
9
|
included do
|
10
10
|
class_attribute :nested_attributes_options, instance_writer: false
|
11
11
|
self.nested_attributes_options = {}
|
12
|
+
|
13
|
+
extend NestedAttributesMethodsExtension
|
14
|
+
prepend PrependMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module PrependMethods
|
18
|
+
def assign_attributes(attrs)
|
19
|
+
if self.class.nested_attributes_options.present?
|
20
|
+
attrs = attrs.to_unsafe_hash if attrs.respond_to?(:to_unsafe_hash)
|
21
|
+
attrs = attrs.stringify_keys
|
22
|
+
|
23
|
+
nested_attrs = self.class.nested_attributes_options.keys
|
24
|
+
.each_with_object({}) do |association_name, result|
|
25
|
+
name = "#{association_name}_attributes"
|
26
|
+
result[name] = attrs.delete(name) if attrs.key?(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
super(attrs.merge!(nested_attrs))
|
30
|
+
else
|
31
|
+
super(attrs)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias_method :attributes=, :assign_attributes
|
12
35
|
end
|
13
36
|
|
14
37
|
class NestedAttributesMethods
|
15
38
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == DESTROY_ATTRIBUTE || value.blank? } }
|
16
39
|
|
17
40
|
def self.accepts_nested_attributes_for(klass, *attr_names)
|
18
|
-
options = {
|
41
|
+
options = {allow_destroy: false, update_only: false}
|
19
42
|
options.update(attr_names.extract_options!)
|
20
43
|
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
|
21
44
|
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
22
45
|
|
46
|
+
NestedAttributesMethodsExtension.ensure_extended!(klass)
|
47
|
+
|
23
48
|
attr_names.each do |association_name|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
49
|
+
reflection = klass.reflect_on_association(association_name)
|
50
|
+
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" unless reflection
|
51
|
+
klass.nested_attributes_options = klass.nested_attributes_options.merge(association_name.to_sym => options)
|
52
|
+
|
53
|
+
should_validate_nested = klass.respond_to?(:validates_nested) && !klass.validates_nested?(association_name)
|
54
|
+
klass.validates_nested(association_name) if should_validate_nested
|
55
|
+
|
56
|
+
type = (reflection.collection? ? :collection : :one_to_one)
|
57
|
+
klass.nested_attributes_methods_module.class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
58
|
+
def #{association_name}_attributes=(attributes)
|
59
|
+
ActiveData::Model::Associations::NestedAttributes::NestedAttributesMethods
|
60
|
+
.assign_nested_attributes_for_#{type}_association(self, :#{association_name}, attributes)
|
61
|
+
end
|
62
|
+
METHOD
|
38
63
|
end
|
39
64
|
end
|
40
65
|
|
@@ -54,7 +79,7 @@ module ActiveData
|
|
54
79
|
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(object, association_name, attributes)
|
55
80
|
elsif attributes[primary_attribute_name].present?
|
56
81
|
raise ActiveData::ObjectNotFound.new(object, association_name, attributes[primary_attribute_name])
|
57
|
-
elsif !reject_new_object?(object, association_name, attributes)
|
82
|
+
elsif !reject_new_object?(object, association_name, attributes, options)
|
58
83
|
assignable_attributes = attributes.except(*unassignable_keys(object))
|
59
84
|
|
60
85
|
if existing_record && !existing_record.persisted?
|
@@ -92,9 +117,7 @@ module ActiveData
|
|
92
117
|
attributes = attributes.with_indifferent_access
|
93
118
|
|
94
119
|
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
|
120
|
+
association.build(attributes.except(*unassignable_keys(object))) unless reject_new_object?(object, association_name, attributes, options)
|
98
121
|
else
|
99
122
|
existing_record = association.target.detect do |record|
|
100
123
|
primary_attribute_value = record.attribute(primary_attribute_name)
|
@@ -102,59 +125,53 @@ module ActiveData
|
|
102
125
|
record.primary_attribute == primary_attribute_value
|
103
126
|
end
|
104
127
|
if existing_record
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
128
|
+
assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(object, association_name, attributes)
|
129
|
+
elsif association.reflection.embedded?
|
130
|
+
unless reject_new_object?(object, association_name, attributes, options)
|
131
|
+
association.reflection.klass.with_sanitize(false) do
|
132
|
+
association.build(attributes.except(DESTROY_ATTRIBUTE))
|
114
133
|
end
|
115
|
-
else
|
116
|
-
raise ActiveData::ObjectNotFound.new(object, association_name, attributes[primary_attribute_name])
|
117
134
|
end
|
135
|
+
else
|
136
|
+
raise ActiveData::ObjectNotFound.new(object, association_name, attributes[primary_attribute_name])
|
118
137
|
end
|
119
138
|
end
|
120
139
|
end
|
121
140
|
end
|
122
141
|
|
123
142
|
def self.check_record_limit!(limit, attributes_collection)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
limit
|
132
|
-
end
|
133
|
-
|
134
|
-
if limit && attributes_collection.size > limit
|
135
|
-
raise ActiveData::TooManyObjects.new(limit, attributes_collection.size)
|
136
|
-
end
|
143
|
+
limit = case limit
|
144
|
+
when Symbol
|
145
|
+
send(limit)
|
146
|
+
when Proc
|
147
|
+
limit.call
|
148
|
+
else
|
149
|
+
limit
|
137
150
|
end
|
151
|
+
|
152
|
+
return unless limit && attributes_collection.size > limit
|
153
|
+
|
154
|
+
raise ActiveData::TooManyObjects.new(limit, attributes_collection.size)
|
138
155
|
end
|
139
156
|
|
140
157
|
def self.assign_to_or_mark_for_destruction(object, attributes, allow_destroy)
|
141
158
|
object.assign_attributes(attributes.except(*unassignable_keys(object)))
|
142
|
-
object.mark_for_destruction if
|
159
|
+
object.mark_for_destruction if destroy_flag?(attributes) && allow_destroy
|
143
160
|
end
|
144
161
|
|
145
|
-
def self.
|
162
|
+
def self.destroy_flag?(hash)
|
146
163
|
ActiveData.typecaster(Boolean).call(hash[DESTROY_ATTRIBUTE])
|
147
164
|
end
|
148
165
|
|
149
|
-
def self.reject_new_object?(object, association_name, attributes)
|
150
|
-
|
166
|
+
def self.reject_new_object?(object, association_name, attributes, options)
|
167
|
+
options[:update_only] || destroy_flag?(attributes) || call_reject_if(object, association_name, attributes)
|
151
168
|
end
|
152
169
|
|
153
170
|
def self.call_reject_if(object, association_name, attributes)
|
154
|
-
return false if
|
171
|
+
return false if destroy_flag?(attributes)
|
155
172
|
case callback = object.nested_attributes_options[association_name][:reject_if]
|
156
173
|
when Symbol
|
157
|
-
method(callback).arity
|
174
|
+
method(callback).arity.zero? ? send(callback) : send(callback, attributes)
|
158
175
|
when Proc
|
159
176
|
callback.call(attributes)
|
160
177
|
end
|
@@ -174,6 +191,21 @@ module ActiveData
|
|
174
191
|
NestedAttributesMethods.accepts_nested_attributes_for self, *attr_names
|
175
192
|
end
|
176
193
|
end
|
194
|
+
|
195
|
+
module NestedAttributesMethodsExtension
|
196
|
+
def self.ensure_extended!(klass)
|
197
|
+
return if klass.singleton_class.ancestors.include?(self)
|
198
|
+
klass.extend(self)
|
199
|
+
end
|
200
|
+
|
201
|
+
def nested_attributes_methods_module
|
202
|
+
@nested_attributes_methods_module ||= begin
|
203
|
+
mod = const_set(:NestedAttributesMethods, Module.new)
|
204
|
+
include(mod)
|
205
|
+
mod
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
177
209
|
end
|
178
210
|
end
|
179
211
|
end
|
data/lib/active_data/model/associations/persistence_adapters/active_record/referenced_proxy.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
module PersistenceAdapters
|
5
|
+
class ActiveRecord < Base
|
6
|
+
class ReferencedProxy < ActiveData::Model::Associations::Collection::Proxy
|
7
|
+
# You can't create data directly through ActiveRecord::Relation
|
8
|
+
METHODS_EXCLUDED_FROM_DELEGATION = %w[build create create!].map(&:to_sym).freeze
|
9
|
+
|
10
|
+
attr_reader :association
|
11
|
+
delegate :scope, to: :@association
|
12
|
+
|
13
|
+
def method_missing(method, *args, &block)
|
14
|
+
delegate_to_scope?(method) ? scope.send(method, *args, &block) : super
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(method, include_private = false)
|
18
|
+
delegate_to_scope?(method) || super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def delegate_to_scope?(method)
|
24
|
+
METHODS_EXCLUDED_FROM_DELEGATION.exclude?(method) && scope.respond_to?(method)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'active_data/model/associations/persistence_adapters/active_record/referenced_proxy'
|
2
|
+
|
3
|
+
module ActiveData
|
4
|
+
module Model
|
5
|
+
module Associations
|
6
|
+
module PersistenceAdapters
|
7
|
+
class ActiveRecord < Base
|
8
|
+
TYPES = {
|
9
|
+
integer: Integer,
|
10
|
+
float: Float,
|
11
|
+
decimal: BigDecimal,
|
12
|
+
datetime: Time,
|
13
|
+
timestamp: Time,
|
14
|
+
time: Time,
|
15
|
+
date: Date,
|
16
|
+
text: String,
|
17
|
+
string: String,
|
18
|
+
binary: String,
|
19
|
+
boolean: Boolean
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
alias_method :data_type, :data_source
|
23
|
+
|
24
|
+
def build(attributes)
|
25
|
+
data_source.new(attributes)
|
26
|
+
end
|
27
|
+
|
28
|
+
def persist(object, raise_error: false)
|
29
|
+
raise_error ? object.save! : object.save
|
30
|
+
end
|
31
|
+
|
32
|
+
def scope(owner, source)
|
33
|
+
scope = data_source.unscoped
|
34
|
+
|
35
|
+
if scope_proc
|
36
|
+
scope = if scope_proc.arity.zero?
|
37
|
+
scope.instance_exec(&scope_proc)
|
38
|
+
else
|
39
|
+
scope.instance_exec(owner, &scope_proc)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
scope.where(primary_key => source)
|
44
|
+
end
|
45
|
+
|
46
|
+
def identify(object)
|
47
|
+
object[primary_key] if object
|
48
|
+
end
|
49
|
+
|
50
|
+
def primary_key
|
51
|
+
@primary_key ||= :id
|
52
|
+
end
|
53
|
+
|
54
|
+
def primary_key_type
|
55
|
+
column = data_source.columns_hash[primary_key.to_s]
|
56
|
+
TYPES[column.type]
|
57
|
+
end
|
58
|
+
|
59
|
+
def referenced_proxy(association)
|
60
|
+
ReferencedProxy.new(association)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
module PersistenceAdapters
|
5
|
+
class Base
|
6
|
+
attr_reader :data_source, :primary_key, :scope_proc
|
7
|
+
|
8
|
+
def initialize(data_source, primary_key, scope_proc = nil)
|
9
|
+
@data_source = data_source
|
10
|
+
@primary_key = primary_key
|
11
|
+
@scope_proc = scope_proc
|
12
|
+
end
|
13
|
+
|
14
|
+
def build(_attributes)
|
15
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Build new instance of data object by attributes'
|
16
|
+
end
|
17
|
+
|
18
|
+
def persist(_object, *)
|
19
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Build new instance of data object by attributes'
|
20
|
+
end
|
21
|
+
|
22
|
+
def scope(_owner, _source)
|
23
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Better to be Enumerable'
|
24
|
+
end
|
25
|
+
|
26
|
+
def find_one(owner, identificator)
|
27
|
+
scope(owner, identificator).first
|
28
|
+
end
|
29
|
+
|
30
|
+
def find_all(owner, identificators)
|
31
|
+
scope(owner, identificators).to_a
|
32
|
+
end
|
33
|
+
|
34
|
+
def identify(_object)
|
35
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Field to be used as primary_key for object'
|
36
|
+
end
|
37
|
+
|
38
|
+
def data_type
|
39
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Type of data object for type_check'
|
40
|
+
end
|
41
|
+
|
42
|
+
def primary_key_type
|
43
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Ruby data type'
|
44
|
+
end
|
45
|
+
|
46
|
+
def referenced_proxy
|
47
|
+
raise NotImplementedError, 'Should be implemented in inhereted adapter. Object to manage proxying of methods to scope.'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module ActiveData
|
2
|
+
module Model
|
3
|
+
module Associations
|
4
|
+
class ReferencesAny < Base
|
5
|
+
def scope(source = read_source)
|
6
|
+
reflection.persistence_adapter.scope(owner, source)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def read_source
|
12
|
+
attribute.read_before_type_cast
|
13
|
+
end
|
14
|
+
|
15
|
+
def write_source(value)
|
16
|
+
attribute.write_value value
|
17
|
+
end
|
18
|
+
|
19
|
+
def attribute
|
20
|
+
@attribute ||= owner.attribute(reflection.reference_key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_object(attributes)
|
24
|
+
reflection.persistence_adapter.build(attributes)
|
25
|
+
end
|
26
|
+
|
27
|
+
def persist_object(object, **options)
|
28
|
+
reflection.persistence_adapter.persist(object, **options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def matches_type?(object)
|
32
|
+
object.is_a?(reflection.persistence_adapter.data_type)
|
33
|
+
end
|
34
|
+
|
35
|
+
def raise_type_mismatch(object)
|
36
|
+
raise AssociationTypeMismatch.new(reflection.persistence_adapter.data_type, object.class)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,50 +1,71 @@
|
|
1
1
|
module ActiveData
|
2
2
|
module Model
|
3
3
|
module Associations
|
4
|
-
class ReferencesMany <
|
4
|
+
class ReferencesMany < ReferencesAny
|
5
|
+
def build(attributes = {})
|
6
|
+
append([build_object(attributes)]).last
|
7
|
+
end
|
8
|
+
|
9
|
+
def create(attributes = {})
|
10
|
+
object = build(attributes)
|
11
|
+
persist_object(object)
|
12
|
+
object
|
13
|
+
end
|
14
|
+
|
15
|
+
def create!(attributes = {})
|
16
|
+
object = build(attributes)
|
17
|
+
persist_object(object, raise_error: true)
|
18
|
+
object
|
19
|
+
end
|
20
|
+
|
5
21
|
def apply_changes
|
6
|
-
|
7
|
-
|
8
|
-
|
22
|
+
target.all? do |object|
|
23
|
+
if object
|
24
|
+
if object.marked_for_destruction? && reflection.autosave?
|
25
|
+
object.destroy
|
26
|
+
elsif object.new_record? || (reflection.autosave? && object.changed?)
|
27
|
+
persist_object(object)
|
28
|
+
else
|
29
|
+
true
|
30
|
+
end
|
31
|
+
else
|
32
|
+
true
|
33
|
+
end
|
34
|
+
end
|
9
35
|
end
|
10
36
|
|
11
|
-
def target=
|
37
|
+
def target=(object)
|
12
38
|
loaded!
|
13
39
|
@target = object.to_a
|
14
40
|
end
|
15
41
|
|
16
42
|
def load_target
|
17
43
|
source = read_source
|
18
|
-
source.present? ?
|
44
|
+
source.present? ? reflection.persistence_adapter.find_all(owner, source) : default
|
19
45
|
end
|
20
46
|
|
21
47
|
def default
|
22
|
-
|
23
|
-
default = Array.wrap(reflection.default(owner))
|
24
|
-
if default.all? { |object| object.is_a?(reflection.klass) }
|
25
|
-
default
|
26
|
-
elsif default.all? { |object| object.is_a?(Hash) }
|
27
|
-
default.map { |attributes| reflection.klass.new(attributes) }
|
28
|
-
else
|
29
|
-
scope(default).to_a
|
30
|
-
end if default.present?
|
31
|
-
end || []
|
32
|
-
end
|
48
|
+
return [] if evar_loaded?
|
33
49
|
|
34
|
-
|
35
|
-
attribute.read_before_type_cast
|
36
|
-
end
|
50
|
+
default = Array.wrap(reflection.default(owner))
|
37
51
|
|
38
|
-
|
39
|
-
|
52
|
+
return [] unless default
|
53
|
+
|
54
|
+
if default.all? { |object| object.is_a?(reflection.persistence_adapter.data_type) }
|
55
|
+
default
|
56
|
+
elsif default.all? { |object| object.is_a?(Hash) }
|
57
|
+
default.map { |attributes| build_object(attributes) }
|
58
|
+
else
|
59
|
+
reflection.persistence_adapter.find_all(owner, default)
|
60
|
+
end || []
|
40
61
|
end
|
41
62
|
|
42
|
-
def reader
|
63
|
+
def reader(force_reload = false)
|
43
64
|
reload if force_reload
|
44
|
-
@proxy ||=
|
65
|
+
@proxy ||= reflection.persistence_adapter.referenced_proxy(self)
|
45
66
|
end
|
46
67
|
|
47
|
-
def replace
|
68
|
+
def replace(objects)
|
48
69
|
loaded!
|
49
70
|
transaction do
|
50
71
|
clear
|
@@ -65,31 +86,24 @@ module ActiveData
|
|
65
86
|
reload.empty?
|
66
87
|
end
|
67
88
|
|
68
|
-
def scope source = read_source
|
69
|
-
reflection.scope.where(reflection.primary_key => source)
|
70
|
-
end
|
71
|
-
|
72
89
|
def identify
|
73
|
-
target.map
|
90
|
+
target.map { |obj| reflection.persistence_adapter.identify(obj) }
|
74
91
|
end
|
75
92
|
|
76
93
|
private
|
77
94
|
|
78
|
-
def append
|
95
|
+
def append(objects)
|
79
96
|
attribute.pollute do
|
80
97
|
objects.each do |object|
|
81
98
|
next if target.include?(object)
|
82
|
-
|
99
|
+
raise_type_mismatch(object) unless matches_type?(object)
|
100
|
+
|
83
101
|
target.push(object)
|
84
|
-
|
102
|
+
write_source(identify)
|
85
103
|
end
|
86
104
|
end
|
87
105
|
target
|
88
106
|
end
|
89
|
-
|
90
|
-
def attribute
|
91
|
-
@attribute ||= owner.attribute(reflection.reference_key)
|
92
|
-
end
|
93
107
|
end
|
94
108
|
end
|
95
109
|
end
|
@@ -1,62 +1,74 @@
|
|
1
1
|
module ActiveData
|
2
2
|
module Model
|
3
3
|
module Associations
|
4
|
-
class ReferencesOne <
|
4
|
+
class ReferencesOne < ReferencesAny
|
5
|
+
def build(attributes = {})
|
6
|
+
replace(build_object(attributes))
|
7
|
+
end
|
8
|
+
|
9
|
+
def create(attributes = {})
|
10
|
+
persist_object(build(attributes))
|
11
|
+
target
|
12
|
+
end
|
13
|
+
|
14
|
+
def create!(attributes = {})
|
15
|
+
persist_object(build(attributes), raise_error: true)
|
16
|
+
target
|
17
|
+
end
|
18
|
+
|
5
19
|
def apply_changes
|
6
|
-
if target
|
7
|
-
|
20
|
+
if target
|
21
|
+
if target.marked_for_destruction? && reflection.autosave?
|
22
|
+
target.destroy
|
23
|
+
elsif target.new_record? || (reflection.autosave? && target.changed?)
|
24
|
+
persist_object(target)
|
25
|
+
else
|
26
|
+
true
|
27
|
+
end
|
8
28
|
else
|
9
|
-
|
29
|
+
true
|
10
30
|
end
|
11
|
-
true
|
12
31
|
end
|
13
32
|
|
14
|
-
def target=
|
33
|
+
def target=(object)
|
15
34
|
loaded!
|
16
35
|
@target = object
|
17
36
|
end
|
18
37
|
|
19
38
|
def load_target
|
20
39
|
source = read_source
|
21
|
-
source ?
|
40
|
+
source ? reflection.persistence_adapter.find_one(owner, source) : default
|
22
41
|
end
|
23
42
|
|
24
43
|
def default
|
25
|
-
|
26
|
-
default = reflection.default(owner)
|
27
|
-
case default
|
28
|
-
when reflection.klass
|
29
|
-
default
|
30
|
-
when Hash
|
31
|
-
reflection.klass.new(default)
|
32
|
-
else
|
33
|
-
scope(default).first
|
34
|
-
end if default
|
35
|
-
end
|
36
|
-
end
|
44
|
+
return if evar_loaded?
|
37
45
|
|
38
|
-
|
39
|
-
|
40
|
-
|
46
|
+
default = reflection.default(owner)
|
47
|
+
|
48
|
+
return unless default
|
41
49
|
|
42
|
-
|
43
|
-
|
50
|
+
case default
|
51
|
+
when reflection.persistence_adapter.data_type
|
52
|
+
default
|
53
|
+
when Hash
|
54
|
+
build_object(default)
|
55
|
+
else
|
56
|
+
reflection.persistence_adapter.find_one(owner, default)
|
57
|
+
end
|
44
58
|
end
|
45
59
|
|
46
|
-
def reader
|
60
|
+
def reader(force_reload = false)
|
47
61
|
reset if force_reload
|
48
62
|
target
|
49
63
|
end
|
50
64
|
|
51
|
-
def replace
|
52
|
-
unless object.nil? ||
|
53
|
-
raise AssociationTypeMismatch.new(reflection.klass, object.class)
|
54
|
-
end
|
65
|
+
def replace(object)
|
66
|
+
raise_type_mismatch(object) unless object.nil? || matches_type?(object)
|
55
67
|
|
56
68
|
transaction do
|
57
69
|
attribute.pollute do
|
58
70
|
self.target = object
|
59
|
-
|
71
|
+
write_source identify
|
60
72
|
end
|
61
73
|
end
|
62
74
|
|
@@ -64,18 +76,8 @@ module ActiveData
|
|
64
76
|
end
|
65
77
|
alias_method :writer, :replace
|
66
78
|
|
67
|
-
def scope source = read_source
|
68
|
-
reflection.scope.where(reflection.primary_key => source)
|
69
|
-
end
|
70
|
-
|
71
79
|
def identify
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def attribute
|
78
|
-
@attribute ||= owner.attribute(reflection.reference_key)
|
80
|
+
reflection.persistence_adapter.identify(target)
|
79
81
|
end
|
80
82
|
end
|
81
83
|
end
|