mongoid 9.1.0 → 9.1.1
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/lib/config/locales/en.yml +49 -0
- data/lib/mongoid/association/accessors.rb +9 -1
- data/lib/mongoid/association/depending.rb +14 -10
- data/lib/mongoid/association/eager.rb +9 -0
- data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
- data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
- data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
- data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
- data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
- data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
- data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
- data/lib/mongoid/association/eager_loadable.rb +57 -100
- data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
- data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
- data/lib/mongoid/association/macros.rb +13 -1
- data/lib/mongoid/association/nested/many.rb +34 -5
- data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
- data/lib/mongoid/association/nested/one.rb +6 -1
- data/lib/mongoid/association/options.rb +17 -0
- data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
- data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
- data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
- data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
- data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
- data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
- data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
- data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
- data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
- data/lib/mongoid/association/referenced/has_one.rb +1 -0
- data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
- data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
- data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
- data/lib/mongoid/association/referenced.rb +2 -0
- data/lib/mongoid/association/relatable.rb +40 -0
- data/lib/mongoid/association.rb +7 -0
- data/lib/mongoid/clients/factory.rb +7 -13
- data/lib/mongoid/collection_configurable.rb +8 -0
- data/lib/mongoid/config/defaults.rb +0 -1
- data/lib/mongoid/config/encryption.rb +36 -19
- data/lib/mongoid/config.rb +56 -5
- data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
- data/lib/mongoid/contextual/memory.rb +18 -7
- data/lib/mongoid/criteria/includable.rb +35 -11
- data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
- data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
- data/lib/mongoid/deprecable.rb +45 -2
- data/lib/mongoid/encryptable.rb +46 -0
- data/lib/mongoid/errors/config_redactor.rb +43 -0
- data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
- data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
- data/lib/mongoid/errors/no_client_database.rb +1 -1
- data/lib/mongoid/errors/no_client_hosts.rb +1 -1
- data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
- data/lib/mongoid/errors/readonly_association.rb +29 -0
- data/lib/mongoid/errors.rb +4 -0
- data/lib/mongoid/extensions/vector.rb +64 -0
- data/lib/mongoid/extensions.rb +1 -0
- data/lib/mongoid/field_readable.rb +69 -0
- data/lib/mongoid/indexable.rb +38 -26
- data/lib/mongoid/matchable.rb +6 -1
- data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
- data/lib/mongoid/matcher/regex.rb +11 -12
- data/lib/mongoid/matcher/regexp_budget.rb +372 -0
- data/lib/mongoid/matcher.rb +1 -0
- data/lib/mongoid/persistable/creatable.rb +1 -1
- data/lib/mongoid/persistable/updatable.rb +9 -11
- data/lib/mongoid/persistence_context.rb +35 -0
- data/lib/mongoid/search_indexable.rb +64 -23
- data/lib/mongoid/serializable.rb +12 -4
- data/lib/mongoid/tasks/database.rb +23 -9
- data/lib/mongoid/threaded.rb +36 -0
- data/lib/mongoid/timestamps/timeless.rb +84 -10
- data/lib/mongoid/traversable.rb +2 -2
- data/lib/mongoid/validatable/associated.rb +18 -2
- data/lib/mongoid/version.rb +1 -1
- data/spec/integration/app_spec.rb +7 -1
- data/spec/integration/associations/belongs_to_spec.rb +13 -0
- data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
- data/spec/integration/dots_and_dollars_spec.rb +12 -2
- data/spec/integration/encryption_spec.rb +151 -0
- data/spec/integration/matcher_operator_data/regex.yml +21 -0
- data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
- data/spec/integration/query_operator_guard_spec.rb +89 -0
- data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
- data/spec/mongoid/association/eager_spec.rb +2 -2
- data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
- data/spec/mongoid/association/fallback_spec.rb +425 -0
- data/spec/mongoid/association/macros_spec.rb +26 -0
- data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
- data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
- data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
- data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
- data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
- data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
- data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
- data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
- data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
- data/spec/mongoid/attributes/nested_spec.rb +249 -8
- data/spec/mongoid/config/defaults_spec.rb +33 -0
- data/spec/mongoid/config/encryption_spec.rb +228 -0
- data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
- data/spec/mongoid/contextual/memory_spec.rb +177 -0
- data/spec/mongoid/criteria/includable_spec.rb +875 -0
- data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
- data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
- data/spec/mongoid/criteria_spec.rb +3 -0
- data/spec/mongoid/encryptable_spec.rb +61 -0
- data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
- data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
- data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
- data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
- data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
- data/spec/mongoid/extensions/vector_spec.rb +175 -0
- data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
- data/spec/mongoid/search_indexable_spec.rb +295 -9
- data/spec/mongoid/tasks/database_spec.rb +18 -0
- data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
- data/spec/mongoid/touchable_spec.rb +25 -0
- data/spec/mongoid/touchable_spec_models.rb +10 -0
- data/spec/mongoid/validatable/associated_spec.rb +19 -0
- data/spec/spec_helper.rb +5 -1
- data/spec/support/crypt/models.rb +157 -0
- metadata +52 -2
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mongoid/association/referenced/has_many_through/proxy'
|
|
4
|
+
require 'mongoid/association/referenced/has_many_through/eager'
|
|
5
|
+
|
|
6
|
+
module Mongoid
|
|
7
|
+
module Association
|
|
8
|
+
module Referenced
|
|
9
|
+
# Metadata class for has_many :through associations.
|
|
10
|
+
class HasManyThrough
|
|
11
|
+
include Relatable
|
|
12
|
+
|
|
13
|
+
# The options available for this type of association, in addition to the
|
|
14
|
+
# common ones.
|
|
15
|
+
#
|
|
16
|
+
# @return [ Array<Symbol> ] The extra valid options.
|
|
17
|
+
ASSOCIATION_OPTIONS = %i[order source through].freeze
|
|
18
|
+
|
|
19
|
+
# The complete list of valid options for this association, including
|
|
20
|
+
# the shared ones.
|
|
21
|
+
#
|
|
22
|
+
# @return [ Array<Symbol> ] The valid options.
|
|
23
|
+
VALID_OPTIONS = (ASSOCIATION_OPTIONS + SHARED_OPTIONS).freeze
|
|
24
|
+
|
|
25
|
+
# The list of association complements.
|
|
26
|
+
#
|
|
27
|
+
# @return [ Array ]
|
|
28
|
+
def relation_complements
|
|
29
|
+
[].freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Setup instance methods on the owner class.
|
|
33
|
+
#
|
|
34
|
+
# @return [ self ]
|
|
35
|
+
def setup!
|
|
36
|
+
setup_instance_methods!
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Is this association embedded?
|
|
41
|
+
#
|
|
42
|
+
# @return [ false ]
|
|
43
|
+
def embedded?
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The proxy class for this association type.
|
|
48
|
+
#
|
|
49
|
+
# @return [ Class ]
|
|
50
|
+
def relation
|
|
51
|
+
Proxy
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Through associations never store a foreign key on the owner document.
|
|
55
|
+
#
|
|
56
|
+
# @return [ false ]
|
|
57
|
+
def stores_foreign_key?
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The intermediate association metadata on the owner class.
|
|
62
|
+
# Resolved lazily to allow forward references.
|
|
63
|
+
#
|
|
64
|
+
# @return [ Mongoid::Association::Relatable ]
|
|
65
|
+
def through_association
|
|
66
|
+
@through_association ||= begin
|
|
67
|
+
assoc = @owner_class.relations[@options[:through].to_s] ||
|
|
68
|
+
raise(
|
|
69
|
+
Errors::InvalidRelationOption.new(
|
|
70
|
+
@owner_class, name, :through, @options[:through]
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
if assoc.embedded?
|
|
74
|
+
raise(
|
|
75
|
+
Errors::InvalidRelationOption.new(
|
|
76
|
+
@owner_class, name, :through,
|
|
77
|
+
'through association must be a referenced association, not embedded'
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
assoc
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The source association metadata on the intermediate class.
|
|
86
|
+
# Resolved lazily to allow forward references.
|
|
87
|
+
#
|
|
88
|
+
# @return [ Mongoid::Association::Relatable ]
|
|
89
|
+
def source_association
|
|
90
|
+
@source_association ||= begin
|
|
91
|
+
source_name = (@options[:source] || name.to_s.singularize).to_s
|
|
92
|
+
assoc = through_association.klass.relations[source_name] ||
|
|
93
|
+
raise(
|
|
94
|
+
Errors::InvalidRelationOption.new(
|
|
95
|
+
@owner_class, name, :source, source_name
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
if assoc.is_a?(Referenced::HasAndBelongsToMany)
|
|
99
|
+
raise(
|
|
100
|
+
Errors::InvalidRelationOption.new(
|
|
101
|
+
@owner_class, name, :source,
|
|
102
|
+
'has_and_belongs_to_many is not supported as a :through source'
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
assoc
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Return a Criteria scoped to the target documents reachable from base
|
|
111
|
+
# via the through association. Performs two queries: one against the
|
|
112
|
+
# intermediate collection, one against the source collection.
|
|
113
|
+
#
|
|
114
|
+
# @param [ Document ] base The owner document.
|
|
115
|
+
#
|
|
116
|
+
# @return [ Mongoid::Criteria ]
|
|
117
|
+
def criteria(base)
|
|
118
|
+
through_crit = through_association.criteria(base)
|
|
119
|
+
|
|
120
|
+
crit = if source_association.stores_foreign_key?
|
|
121
|
+
# FK is on the intermediate (e.g. appointment.patient_id -> belongs_to :patient)
|
|
122
|
+
target_pk = source_association.primary_key # '_id' on Patient
|
|
123
|
+
source_fk = source_association.foreign_key # 'patient_id' on Appointment
|
|
124
|
+
source_association.klass.where(
|
|
125
|
+
target_pk => { '$in' => key_values(through_crit, source_fk) }
|
|
126
|
+
)
|
|
127
|
+
else
|
|
128
|
+
# FK is on the source (e.g. reader.book_id -> has_many :readers on Book)
|
|
129
|
+
source_pk = source_association.primary_key # '_id' on intermediate (Book)
|
|
130
|
+
source_fk = source_association.foreign_key # 'book_id' on Reader
|
|
131
|
+
source_association.klass.where(
|
|
132
|
+
source_fk => { '$in' => key_values(through_crit, source_pk) }
|
|
133
|
+
)
|
|
134
|
+
end
|
|
135
|
+
order ? crit.order_by(order) : crit
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# The default for validating the association object.
|
|
139
|
+
#
|
|
140
|
+
# @return [ false ]
|
|
141
|
+
def validation_default
|
|
142
|
+
false
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Pluck the given key from the criteria, dropping intermediates that
|
|
148
|
+
# have no value for it. A nil in an $in array matches every document
|
|
149
|
+
# whose queried field is null or absent, which would return documents
|
|
150
|
+
# outside the association. An empty result correctly matches nothing.
|
|
151
|
+
# Mirrors what the eager loader does.
|
|
152
|
+
#
|
|
153
|
+
# @param [ Mongoid::Criteria ] criteria The intermediate criteria.
|
|
154
|
+
# @param [ String ] key The key to pluck.
|
|
155
|
+
#
|
|
156
|
+
# @return [ Array<Object> ] The usable key values.
|
|
157
|
+
def key_values(criteria, key)
|
|
158
|
+
criteria.pluck(key).compact.uniq
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def setup_instance_methods!
|
|
162
|
+
define_through_getter!
|
|
163
|
+
define_through_ids_getter!
|
|
164
|
+
define_readonly_setter!
|
|
165
|
+
define_existence_check!
|
|
166
|
+
self
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def define_through_getter!
|
|
170
|
+
assoc = self
|
|
171
|
+
assoc_name = name
|
|
172
|
+
@owner_class.re_define_method(assoc_name) do |reload = false|
|
|
173
|
+
if reload || !instance_variable_defined?("@_#{assoc_name}")
|
|
174
|
+
set_relation(assoc_name, HasManyThrough::Proxy.new(self, assoc))
|
|
175
|
+
end
|
|
176
|
+
instance_variable_get("@_#{assoc_name}")
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def define_through_ids_getter!
|
|
181
|
+
assoc_name = name
|
|
182
|
+
ids_method = :"#{assoc_name.to_s.singularize}_ids"
|
|
183
|
+
@owner_class.re_define_method(ids_method) do
|
|
184
|
+
send(assoc_name).pluck(:_id)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def define_readonly_setter!
|
|
189
|
+
assoc = self
|
|
190
|
+
@owner_class.re_define_method(:"#{name}=") do |_object|
|
|
191
|
+
raise Mongoid::Errors::ReadonlyAssociation.new(self.class, assoc)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def default_primary_key
|
|
196
|
+
PRIMARY_KEY_DEFAULT
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongoid
|
|
4
|
+
module Association
|
|
5
|
+
module Referenced
|
|
6
|
+
class HasOneThrough
|
|
7
|
+
# Two-query eager preloader for has_one :through associations.
|
|
8
|
+
class Eager < Association::Eager
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def preload
|
|
12
|
+
@docs.each { |d| set_relation(d, nil) }
|
|
13
|
+
|
|
14
|
+
through_assoc = @association.through_association
|
|
15
|
+
source_assoc = @association.source_association
|
|
16
|
+
|
|
17
|
+
owner_pk = through_assoc.primary_key
|
|
18
|
+
through_fk = through_assoc.foreign_key
|
|
19
|
+
|
|
20
|
+
owner_ids = @docs.filter_map { |d| d.public_send(owner_pk) }.uniq
|
|
21
|
+
return if owner_ids.empty?
|
|
22
|
+
|
|
23
|
+
intermediates = through_assoc.klass.where(through_fk => { '$in' => owner_ids }).to_a
|
|
24
|
+
intermediate_to_target = build_intermediate_to_target(intermediates, through_fk, source_assoc)
|
|
25
|
+
|
|
26
|
+
@docs.each do |doc|
|
|
27
|
+
owner_key_val = doc.public_send(owner_pk)
|
|
28
|
+
target = intermediate_to_target[owner_key_val]
|
|
29
|
+
proxy = target ? HasOneThrough::Proxy.new(doc, target, @association) : nil
|
|
30
|
+
doc.set_relation(@association.name, proxy) unless doc.blank?
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def build_intermediate_to_target(intermediates, through_fk, source_assoc)
|
|
35
|
+
if source_assoc.stores_foreign_key?
|
|
36
|
+
# FK is on the intermediate (e.g. belongs_to :store => intermediate.store_id)
|
|
37
|
+
source_fk_values = intermediates.filter_map { |i| i.public_send(source_assoc.foreign_key) }.uniq
|
|
38
|
+
targets = source_assoc.klass.where(source_assoc.primary_key => { '$in' => source_fk_values }).to_a
|
|
39
|
+
targets_by_key = targets.index_by { |t| t.public_send(source_assoc.primary_key) }
|
|
40
|
+
intermediates.to_h do |i|
|
|
41
|
+
[ i.public_send(through_fk), targets_by_key[i.public_send(source_assoc.foreign_key)] ]
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
# FK is on the source (e.g. has_one :store => store.franchise_id)
|
|
45
|
+
source_pk = source_assoc.primary_key
|
|
46
|
+
intermediate_pks = intermediates.filter_map { |i| i.public_send(source_pk) }.uniq
|
|
47
|
+
targets = source_assoc.klass.where(source_assoc.foreign_key => { '$in' => intermediate_pks }).to_a
|
|
48
|
+
targets_by_fk = targets.index_by { |t| t.public_send(source_assoc.foreign_key) }
|
|
49
|
+
intermediates.index_by { |i| i.public_send(through_fk) }.transform_values do |i|
|
|
50
|
+
targets_by_fk[i.public_send(source_pk)]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Required by the base class contract. Not called by this preloader
|
|
56
|
+
# because preload manages document traversal directly without using
|
|
57
|
+
# the grouped_docs / keys_from_docs machinery from the base class.
|
|
58
|
+
def group_by_key
|
|
59
|
+
@association.through_association.primary_key
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mongoid
|
|
4
|
+
module Association
|
|
5
|
+
module Referenced
|
|
6
|
+
class HasOneThrough
|
|
7
|
+
# Read-only proxy for has_one :through associations.
|
|
8
|
+
# Instances are returned by the association getter. Write attempts raise
|
|
9
|
+
# ReadonlyAssociation.
|
|
10
|
+
class Proxy < Association::One
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def eager_loader(association, docs)
|
|
13
|
+
Eager.new(association, docs)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Returns true if the association is an embedded one. In this case
|
|
17
|
+
# always false.
|
|
18
|
+
#
|
|
19
|
+
# @return [ false ] Always false.
|
|
20
|
+
def embedded?
|
|
21
|
+
false
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
extend ClassMethods
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'mongoid/association/referenced/has_one_through/proxy'
|
|
4
|
+
require 'mongoid/association/referenced/has_one_through/eager'
|
|
5
|
+
|
|
6
|
+
module Mongoid
|
|
7
|
+
module Association
|
|
8
|
+
module Referenced
|
|
9
|
+
# Metadata class for has_one :through associations.
|
|
10
|
+
class HasOneThrough
|
|
11
|
+
include Relatable
|
|
12
|
+
|
|
13
|
+
# The options available for this type of association, in addition to the
|
|
14
|
+
# common ones.
|
|
15
|
+
#
|
|
16
|
+
# @return [ Array<Symbol> ] The extra valid options.
|
|
17
|
+
ASSOCIATION_OPTIONS = %i[source through].freeze
|
|
18
|
+
|
|
19
|
+
# The complete list of valid options for this association, including
|
|
20
|
+
# the shared ones.
|
|
21
|
+
#
|
|
22
|
+
# @return [ Array<Symbol> ] The valid options.
|
|
23
|
+
VALID_OPTIONS = (ASSOCIATION_OPTIONS + SHARED_OPTIONS).freeze
|
|
24
|
+
|
|
25
|
+
# The list of association complements.
|
|
26
|
+
#
|
|
27
|
+
# @return [ Array ]
|
|
28
|
+
def relation_complements
|
|
29
|
+
[].freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Setup instance methods on the owner class.
|
|
33
|
+
#
|
|
34
|
+
# @return [ self ]
|
|
35
|
+
def setup!
|
|
36
|
+
setup_instance_methods!
|
|
37
|
+
self
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Is this association embedded?
|
|
41
|
+
#
|
|
42
|
+
# @return [ false ]
|
|
43
|
+
def embedded?
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The proxy class for this association type.
|
|
48
|
+
#
|
|
49
|
+
# @return [ Class ]
|
|
50
|
+
def relation
|
|
51
|
+
Proxy
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The intermediate association metadata on the owner class.
|
|
55
|
+
# Resolved lazily to allow forward references.
|
|
56
|
+
#
|
|
57
|
+
# @return [ Mongoid::Association::Relatable ]
|
|
58
|
+
def through_association
|
|
59
|
+
@through_association ||= begin
|
|
60
|
+
assoc = @owner_class.relations[@options[:through].to_s] ||
|
|
61
|
+
raise(
|
|
62
|
+
Errors::InvalidRelationOption.new(
|
|
63
|
+
@owner_class, name, :through, @options[:through]
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
if assoc.embedded?
|
|
67
|
+
raise(
|
|
68
|
+
Errors::InvalidRelationOption.new(
|
|
69
|
+
@owner_class, name, :through,
|
|
70
|
+
'through association must be a referenced association, not embedded'
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
assoc
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The source association metadata on the intermediate class.
|
|
79
|
+
# Resolved lazily to allow forward references.
|
|
80
|
+
#
|
|
81
|
+
# @return [ Mongoid::Association::Relatable ]
|
|
82
|
+
def source_association
|
|
83
|
+
@source_association ||= begin
|
|
84
|
+
source_name = (@options[:source] || name).to_s
|
|
85
|
+
assoc = through_association.klass.relations[source_name] ||
|
|
86
|
+
raise(
|
|
87
|
+
Errors::InvalidRelationOption.new(
|
|
88
|
+
@owner_class, name, :source, source_name
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
if assoc.is_a?(Referenced::HasAndBelongsToMany)
|
|
92
|
+
raise(
|
|
93
|
+
Errors::InvalidRelationOption.new(
|
|
94
|
+
@owner_class, name, :source,
|
|
95
|
+
'has_and_belongs_to_many is not supported as a :through source'
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
assoc
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Through associations never store a foreign key on the owner document.
|
|
104
|
+
#
|
|
105
|
+
# @return [ false ]
|
|
106
|
+
def stores_foreign_key?
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Resolve the target by delegating through the intermediate proxy.
|
|
111
|
+
# Unlike other association types, this returns a document (or nil)
|
|
112
|
+
# directly rather than a Mongoid::Criteria, because the two-hop
|
|
113
|
+
# traversal is performed eagerly via the existing association proxy.
|
|
114
|
+
#
|
|
115
|
+
# @param [ Document ] base The owner document.
|
|
116
|
+
#
|
|
117
|
+
# @return [ Document | nil ]
|
|
118
|
+
def criteria(base)
|
|
119
|
+
through_target = base.public_send(through_association.name)
|
|
120
|
+
return nil if through_target.nil?
|
|
121
|
+
|
|
122
|
+
through_target.public_send(source_association.name)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The default for validating the association object.
|
|
126
|
+
#
|
|
127
|
+
# @return [ false ]
|
|
128
|
+
def validation_default
|
|
129
|
+
false
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def setup_instance_methods!
|
|
135
|
+
define_through_getter!
|
|
136
|
+
define_readonly_setter!
|
|
137
|
+
define_existence_check!
|
|
138
|
+
self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def define_through_getter!
|
|
142
|
+
assoc = self
|
|
143
|
+
assoc_name = name
|
|
144
|
+
@owner_class.re_define_method(assoc_name) do |reload = false|
|
|
145
|
+
if reload || !instance_variable_defined?("@_#{assoc_name}")
|
|
146
|
+
doc = assoc.criteria(self)
|
|
147
|
+
proxy = doc ? HasOneThrough::Proxy.new(self, doc, assoc) : nil
|
|
148
|
+
set_relation(assoc_name, proxy)
|
|
149
|
+
end
|
|
150
|
+
instance_variable_get("@_#{assoc_name}")
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def define_readonly_setter!
|
|
155
|
+
assoc = self
|
|
156
|
+
@owner_class.re_define_method("#{name}=") do |_object|
|
|
157
|
+
raise Mongoid::Errors::ReadonlyAssociation.new(self.class, assoc)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def default_primary_key
|
|
162
|
+
PRIMARY_KEY_DEFAULT
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -7,3 +7,5 @@ require 'mongoid/association/referenced/belongs_to'
|
|
|
7
7
|
require 'mongoid/association/referenced/has_many'
|
|
8
8
|
require 'mongoid/association/referenced/has_and_belongs_to_many'
|
|
9
9
|
require 'mongoid/association/referenced/has_one'
|
|
10
|
+
require 'mongoid/association/referenced/has_one_through'
|
|
11
|
+
require 'mongoid/association/referenced/has_many_through'
|
|
@@ -177,6 +177,23 @@ module Mongoid
|
|
|
177
177
|
end
|
|
178
178
|
alias klass relation_class
|
|
179
179
|
|
|
180
|
+
# The class of the association target, or nil when the named class is
|
|
181
|
+
# not defined.
|
|
182
|
+
#
|
|
183
|
+
# An association may name a class that never gets defined. The
|
|
184
|
+
# association is then unusable, but its owner still has to be. Callers
|
|
185
|
+
# that walk every association of every model, rather than following the
|
|
186
|
+
# one the application asked for, use this instead of relation_class.
|
|
187
|
+
#
|
|
188
|
+
# @return [ Class | nil ] The association objects' class.
|
|
189
|
+
#
|
|
190
|
+
# @api private
|
|
191
|
+
def try_relation_class
|
|
192
|
+
relation_class
|
|
193
|
+
rescue NameError
|
|
194
|
+
nil
|
|
195
|
+
end
|
|
196
|
+
|
|
180
197
|
# The class name of the object owning this association.
|
|
181
198
|
#
|
|
182
199
|
# @return [ String ] The owning objects' class name.
|
|
@@ -342,6 +359,15 @@ module Mongoid
|
|
|
342
359
|
[ Referenced::BelongsTo, Embedded::EmbeddedIn ].any? { |a| is_a?(a) }
|
|
343
360
|
end
|
|
344
361
|
|
|
362
|
+
# Is this association a many-to-many association? Such an association stores
|
|
363
|
+
# its foreign keys as an array on the document itself.
|
|
364
|
+
#
|
|
365
|
+
# @return [ true | false ] true if it is a has_and_belongs_to_many
|
|
366
|
+
# association, false if not.
|
|
367
|
+
def many_to_many?
|
|
368
|
+
is_a?(Referenced::HasAndBelongsToMany)
|
|
369
|
+
end
|
|
370
|
+
|
|
345
371
|
private
|
|
346
372
|
|
|
347
373
|
# Gets the model classes with inverse associations of this model. This is used to determine
|
|
@@ -413,6 +439,20 @@ module Mongoid
|
|
|
413
439
|
end
|
|
414
440
|
end
|
|
415
441
|
|
|
442
|
+
if @options.key?(:fallback)
|
|
443
|
+
unless @options[:fallback].respond_to?(:call)
|
|
444
|
+
raise ArgumentError,
|
|
445
|
+
"The :fallback option for association '#{name}' on " \
|
|
446
|
+
"#{@owner_class} must be a Proc or lambda."
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
if @options.key?(:autobuild)
|
|
450
|
+
raise ArgumentError,
|
|
451
|
+
"The :fallback option for association '#{name}' on " \
|
|
452
|
+
"#{@owner_class} cannot be combined with :autobuild."
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
416
456
|
[ name, :"#{name}?", :"#{name}=" ].each do |n|
|
|
417
457
|
raise Errors::InvalidRelation.new(@owner_class, n) if Mongoid.destructive_fields.include?(n)
|
|
418
458
|
end
|
data/lib/mongoid/association.rb
CHANGED
|
@@ -46,6 +46,13 @@ module Mongoid
|
|
|
46
46
|
belongs_to: Association::Referenced::BelongsTo
|
|
47
47
|
}.freeze
|
|
48
48
|
|
|
49
|
+
# Internal mapping used when :through option is present. Not exposed as
|
|
50
|
+
# callable macros.
|
|
51
|
+
THROUGH_MACRO_MAPPING = {
|
|
52
|
+
has_one: Association::Referenced::HasOneThrough,
|
|
53
|
+
has_many: Association::Referenced::HasManyThrough
|
|
54
|
+
}.freeze
|
|
55
|
+
|
|
49
56
|
attr_accessor :_association
|
|
50
57
|
|
|
51
58
|
included do
|
|
@@ -111,10 +111,6 @@ module Mongoid
|
|
|
111
111
|
version: VERSION
|
|
112
112
|
}.freeze
|
|
113
113
|
|
|
114
|
-
def driver_version
|
|
115
|
-
Mongo::VERSION.split('.')[0...2].map(&:to_i)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
114
|
# Prepare options for Mongo::Client based on Mongoid client configuration.
|
|
119
115
|
#
|
|
120
116
|
# @param [ Hash ] opts Parameters from options section of Mongoid client configuration.
|
|
@@ -125,16 +121,14 @@ module Mongoid
|
|
|
125
121
|
options = opts.dup
|
|
126
122
|
options[:platform] = PLATFORM_DETAILS
|
|
127
123
|
options[:app_name] = Mongoid::Config.app_name if Mongoid::Config.app_name
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
wrap << { name: 'Rails', version: ::Rails.version } if defined?(::Rails) && ::Rails.respond_to?(:version)
|
|
135
|
-
end
|
|
136
|
-
options[:wrapping_libraries] = wrap_lib
|
|
124
|
+
wrap_lib = if options[:wrapping_libraries]
|
|
125
|
+
[ MONGOID_WRAPPING_LIBRARY ] + options[:wrapping_libraries]
|
|
126
|
+
else
|
|
127
|
+
[ MONGOID_WRAPPING_LIBRARY ]
|
|
128
|
+
end.tap do |wrap|
|
|
129
|
+
wrap << { name: 'Rails', version: ::Rails.version } if defined?(::Rails) && ::Rails.respond_to?(:version)
|
|
137
130
|
end
|
|
131
|
+
options[:wrapping_libraries] = wrap_lib
|
|
138
132
|
options.reject { |k, _v| k == :hosts }.to_hash.symbolize_keys!
|
|
139
133
|
end
|
|
140
134
|
end
|
|
@@ -20,6 +20,14 @@ module Mongoid
|
|
|
20
20
|
# @raise [ Errors::CreateCollectionFailure ] If collection creation failed.
|
|
21
21
|
# @raise [ Errors::DropCollectionFailure ] If an attempt to drop collection failed.
|
|
22
22
|
def create_collection(force: false)
|
|
23
|
+
Threaded.with_collection_management do
|
|
24
|
+
perform_create_collection(force: force)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def perform_create_collection(force:)
|
|
23
31
|
if collection_name.empty?
|
|
24
32
|
# This is most probably an anonymous class, we ignore them.
|
|
25
33
|
return
|