active_data 1.0.0 → 1.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.
- 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
|