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.
Files changed (126) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +49 -0
  3. data/lib/mongoid/association/accessors.rb +9 -1
  4. data/lib/mongoid/association/depending.rb +14 -10
  5. data/lib/mongoid/association/eager.rb +9 -0
  6. data/lib/mongoid/association/eager_load/discriminated_inclusion.rb +88 -0
  7. data/lib/mongoid/association/eager_load/embedded_distributor.rb +151 -0
  8. data/lib/mongoid/association/eager_load/inclusion.rb +173 -0
  9. data/lib/mongoid/association/eager_load/inclusion_tree.rb +96 -0
  10. data/lib/mongoid/association/eager_load/lookup_pipeline.rb +121 -0
  11. data/lib/mongoid/association/eager_load/polymorphic_preloader.rb +61 -0
  12. data/lib/mongoid/association/eager_load/polymorphic_targets.rb +151 -0
  13. data/lib/mongoid/association/eager_loadable.rb +57 -100
  14. data/lib/mongoid/association/embedded/embeds_many/proxy.rb +6 -1
  15. data/lib/mongoid/association/embedded/embeds_one.rb +1 -0
  16. data/lib/mongoid/association/macros.rb +13 -1
  17. data/lib/mongoid/association/nested/many.rb +34 -5
  18. data/lib/mongoid/association/nested/nested_buildable.rb +14 -0
  19. data/lib/mongoid/association/nested/one.rb +6 -1
  20. data/lib/mongoid/association/options.rb +17 -0
  21. data/lib/mongoid/association/referenced/belongs_to/eager.rb +6 -1
  22. data/lib/mongoid/association/referenced/belongs_to.rb +1 -0
  23. data/lib/mongoid/association/referenced/counter_cache.rb +3 -3
  24. data/lib/mongoid/association/referenced/has_and_belongs_to_many.rb +5 -0
  25. data/lib/mongoid/association/referenced/has_many/enumerable.rb +3 -1
  26. data/lib/mongoid/association/referenced/has_many/proxy.rb +100 -1
  27. data/lib/mongoid/association/referenced/has_many_through/eager.rb +99 -0
  28. data/lib/mongoid/association/referenced/has_many_through/proxy.rb +68 -0
  29. data/lib/mongoid/association/referenced/has_many_through.rb +201 -0
  30. data/lib/mongoid/association/referenced/has_one.rb +1 -0
  31. data/lib/mongoid/association/referenced/has_one_through/eager.rb +65 -0
  32. data/lib/mongoid/association/referenced/has_one_through/proxy.rb +30 -0
  33. data/lib/mongoid/association/referenced/has_one_through.rb +167 -0
  34. data/lib/mongoid/association/referenced.rb +2 -0
  35. data/lib/mongoid/association/relatable.rb +40 -0
  36. data/lib/mongoid/association.rb +7 -0
  37. data/lib/mongoid/clients/factory.rb +7 -13
  38. data/lib/mongoid/collection_configurable.rb +8 -0
  39. data/lib/mongoid/config/defaults.rb +0 -1
  40. data/lib/mongoid/config/encryption.rb +36 -19
  41. data/lib/mongoid/config.rb +56 -5
  42. data/lib/mongoid/contextual/aggregable/memory.rb +6 -2
  43. data/lib/mongoid/contextual/memory.rb +18 -7
  44. data/lib/mongoid/criteria/includable.rb +35 -11
  45. data/lib/mongoid/criteria/queryable/mergeable.rb +4 -0
  46. data/lib/mongoid/criteria/queryable/selectable.rb +123 -7
  47. data/lib/mongoid/deprecable.rb +45 -2
  48. data/lib/mongoid/encryptable.rb +46 -0
  49. data/lib/mongoid/errors/config_redactor.rb +43 -0
  50. data/lib/mongoid/errors/in_memory_regexp_timeout.rb +26 -0
  51. data/lib/mongoid/errors/mixed_client_configuration.rb +1 -1
  52. data/lib/mongoid/errors/no_client_database.rb +1 -1
  53. data/lib/mongoid/errors/no_client_hosts.rb +1 -1
  54. data/lib/mongoid/errors/no_encryption_schema.rb +28 -0
  55. data/lib/mongoid/errors/readonly_association.rb +29 -0
  56. data/lib/mongoid/errors.rb +4 -0
  57. data/lib/mongoid/extensions/vector.rb +64 -0
  58. data/lib/mongoid/extensions.rb +1 -0
  59. data/lib/mongoid/field_readable.rb +69 -0
  60. data/lib/mongoid/indexable.rb +38 -26
  61. data/lib/mongoid/matchable.rb +6 -1
  62. data/lib/mongoid/matcher/eq_impl_with_regexp.rb +2 -4
  63. data/lib/mongoid/matcher/regex.rb +11 -12
  64. data/lib/mongoid/matcher/regexp_budget.rb +372 -0
  65. data/lib/mongoid/matcher.rb +1 -0
  66. data/lib/mongoid/persistable/creatable.rb +1 -1
  67. data/lib/mongoid/persistable/updatable.rb +9 -11
  68. data/lib/mongoid/persistence_context.rb +35 -0
  69. data/lib/mongoid/search_indexable.rb +64 -23
  70. data/lib/mongoid/serializable.rb +12 -4
  71. data/lib/mongoid/tasks/database.rb +23 -9
  72. data/lib/mongoid/threaded.rb +36 -0
  73. data/lib/mongoid/timestamps/timeless.rb +84 -10
  74. data/lib/mongoid/traversable.rb +2 -2
  75. data/lib/mongoid/validatable/associated.rb +18 -2
  76. data/lib/mongoid/version.rb +1 -1
  77. data/spec/integration/app_spec.rb +7 -1
  78. data/spec/integration/associations/belongs_to_spec.rb +13 -0
  79. data/spec/integration/associations/has_and_belongs_to_many_spec.rb +17 -2
  80. data/spec/integration/dots_and_dollars_spec.rb +12 -2
  81. data/spec/integration/encryption_spec.rb +151 -0
  82. data/spec/integration/matcher_operator_data/regex.yml +21 -0
  83. data/spec/integration/matcher_regexp_timeout_spec.rb +215 -0
  84. data/spec/integration/query_operator_guard_spec.rb +89 -0
  85. data/spec/mongoid/association/eager_loadable_spec.rb +47 -0
  86. data/spec/mongoid/association/eager_spec.rb +2 -2
  87. data/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +33 -0
  88. data/spec/mongoid/association/fallback_spec.rb +425 -0
  89. data/spec/mongoid/association/macros_spec.rb +26 -0
  90. data/spec/mongoid/association/referenced/belongs_to/eager_spec.rb +17 -0
  91. data/spec/mongoid/association/referenced/has_and_belongs_to_many/proxy_spec.rb +48 -0
  92. data/spec/mongoid/association/referenced/has_and_belongs_to_many_spec.rb +50 -0
  93. data/spec/mongoid/association/referenced/has_many/eager_spec.rb +33 -0
  94. data/spec/mongoid/association/referenced/has_many/proxy_spec.rb +145 -0
  95. data/spec/mongoid/association/referenced/has_many_through/eager_spec.rb +58 -0
  96. data/spec/mongoid/association/referenced/has_many_through/proxy_spec.rb +62 -0
  97. data/spec/mongoid/association/referenced/has_many_through_spec.rb +399 -0
  98. data/spec/mongoid/association/referenced/has_one_through/eager_spec.rb +58 -0
  99. data/spec/mongoid/association/referenced/has_one_through/proxy_spec.rb +49 -0
  100. data/spec/mongoid/association/referenced/has_one_through_spec.rb +211 -0
  101. data/spec/mongoid/attributes/nested_spec.rb +249 -8
  102. data/spec/mongoid/config/defaults_spec.rb +33 -0
  103. data/spec/mongoid/config/encryption_spec.rb +228 -0
  104. data/spec/mongoid/contextual/aggregable/memory_spec.rb +91 -0
  105. data/spec/mongoid/contextual/memory_spec.rb +177 -0
  106. data/spec/mongoid/criteria/includable_spec.rb +875 -0
  107. data/spec/mongoid/criteria/queryable/selectable_logical_spec.rb +15 -1
  108. data/spec/mongoid/criteria/queryable/selectable_where_spec.rb +201 -0
  109. data/spec/mongoid/criteria_spec.rb +3 -0
  110. data/spec/mongoid/encryptable_spec.rb +61 -0
  111. data/spec/mongoid/errors/config_redactor_spec.rb +112 -0
  112. data/spec/mongoid/errors/mixed_client_configuration_spec.rb +31 -0
  113. data/spec/mongoid/errors/no_client_database_spec.rb +29 -0
  114. data/spec/mongoid/errors/no_client_hosts_spec.rb +26 -0
  115. data/spec/mongoid/errors/readonly_association_spec.rb +34 -0
  116. data/spec/mongoid/extensions/vector_spec.rb +175 -0
  117. data/spec/mongoid/matcher/regexp_budget_spec.rb +570 -0
  118. data/spec/mongoid/search_indexable_spec.rb +295 -9
  119. data/spec/mongoid/tasks/database_spec.rb +18 -0
  120. data/spec/mongoid/timestamps/timeless_spec.rb +197 -5
  121. data/spec/mongoid/touchable_spec.rb +25 -0
  122. data/spec/mongoid/touchable_spec_models.rb +10 -0
  123. data/spec/mongoid/validatable/associated_spec.rb +19 -0
  124. data/spec/spec_helper.rb +5 -1
  125. data/spec/support/crypt/models.rb +157 -0
  126. 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
@@ -22,6 +22,7 @@ module Mongoid
22
22
  autobuild
23
23
  autosave
24
24
  dependent
25
+ fallback
25
26
  foreign_key
26
27
  primary_key
27
28
  scope
@@ -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
@@ -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
- if (driver_version <=> [ 2, 13 ]) >= 0
129
- wrap_lib = if options[:wrapping_libraries]
130
- [ MONGOID_WRAPPING_LIBRARY ] + options[:wrapping_libraries]
131
- else
132
- [ MONGOID_WRAPPING_LIBRARY ]
133
- end.tap do |wrap|
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
@@ -30,7 +30,6 @@ module Mongoid
30
30
  load_defaults '9.0'
31
31
 
32
32
  when '9.0'
33
- self.allow_reparenting_via_nested_attributes = true
34
33
  self.autosave_saves_unchanged_documents = true
35
34
 
36
35
  load_defaults '9.1'