mongoid 9.0.1 → 9.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +16 -0
  3. data/lib/mongoid/association/accessors.rb +7 -2
  4. data/lib/mongoid/association/nested/one.rb +14 -1
  5. data/lib/mongoid/association/referenced/belongs_to/binding.rb +7 -1
  6. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  7. data/lib/mongoid/association/referenced/belongs_to.rb +15 -0
  8. data/lib/mongoid/association/referenced/has_many.rb +9 -8
  9. data/lib/mongoid/association/referenced/has_one/buildable.rb +3 -8
  10. data/lib/mongoid/association/referenced/with_polymorphic_criteria.rb +41 -0
  11. data/lib/mongoid/attributes/nested.rb +2 -1
  12. data/lib/mongoid/clients/options.rb +14 -1
  13. data/lib/mongoid/clients/sessions.rb +13 -15
  14. data/lib/mongoid/composable.rb +2 -0
  15. data/lib/mongoid/document.rb +2 -0
  16. data/lib/mongoid/errors/unrecognized_model_alias.rb +53 -0
  17. data/lib/mongoid/errors/unrecognized_resolver.rb +27 -0
  18. data/lib/mongoid/errors/unregistered_class.rb +47 -0
  19. data/lib/mongoid/errors.rb +3 -0
  20. data/lib/mongoid/identifiable.rb +28 -0
  21. data/lib/mongoid/matcher.rb +15 -1
  22. data/lib/mongoid/model_resolver.rb +154 -0
  23. data/lib/mongoid/persistence_context.rb +15 -9
  24. data/lib/mongoid/railties/controller_runtime.rb +2 -2
  25. data/lib/mongoid/serializable.rb +7 -7
  26. data/lib/mongoid/threaded.rb +96 -28
  27. data/lib/mongoid/timestamps/timeless.rb +4 -1
  28. data/lib/mongoid/touchable.rb +1 -1
  29. data/lib/mongoid/traversable.rb +11 -2
  30. data/lib/mongoid/validatable/associated.rb +5 -2
  31. data/lib/mongoid/version.rb +1 -1
  32. data/spec/integration/active_job_spec.rb +24 -20
  33. data/spec/integration/app_spec.rb +9 -1
  34. data/spec/integration/associations/belongs_to_spec.rb +129 -0
  35. data/spec/integration/persistence/collection_options_spec.rb +36 -0
  36. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
  37. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +5 -0
  38. data/spec/mongoid/association/referenced/belongs_to_spec.rb +58 -21
  39. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +4 -0
  40. data/spec/mongoid/attributes/nested_spec.rb +1 -0
  41. data/spec/mongoid/clients/options_spec.rb +127 -2
  42. data/spec/mongoid/clients/transactions_spec.rb +2 -2
  43. data/spec/mongoid/interceptable_spec.rb +12 -0
  44. data/spec/mongoid/interceptable_spec_models.rb +12 -0
  45. data/spec/mongoid/model_resolver_spec.rb +167 -0
  46. data/spec/mongoid/monkey_patches_spec.rb +1 -1
  47. data/spec/mongoid/persistence_context_spec.rb +48 -4
  48. data/spec/mongoid/railties/bson_object_id_serializer_spec.rb +18 -12
  49. data/spec/mongoid/serializable_spec.rb +16 -9
  50. data/spec/mongoid/threaded_spec.rb +24 -5
  51. data/spec/mongoid/validatable/associated_spec.rb +14 -4
  52. data/spec/rails/controller_extension/controller_runtime_spec.rb +14 -14
  53. metadata +14 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f756ccb6822c09519cb0e833a2d3bd2edf5b5a0abf3f3c29205a756682e5d954
4
- data.tar.gz: 7d44016955b7e2e90d2dc07b2142cc6c79bacfb83831705f26fdafc773e17e7c
3
+ metadata.gz: 421d53636b0e90abcfa52bf637229588d434ff73f14574b1d316b72009f7236b
4
+ data.tar.gz: fafa07a7cc9b17de0abbb140ca3d021e3041bfe00b67a424c81f6c15018e607d
5
5
  SHA512:
6
- metadata.gz: e69bc7c2b6031330346265b5c6cdc8d11909fd1204e32727845e3220c6f105dde407006635f31491df9857597711f610a688818dfaa3bc32f27525e2574ce29d
7
- data.tar.gz: 259996128cd10553af4972c6b40f5a8ac537158eeaa1cd012bbc9e6d90d67ad745def51fbd47bafaffe9330324967695e45787732446b305d5bdd13338b94075
6
+ metadata.gz: d4f2011d1f85178a8693d653518f06e7ad55c1363958e3577f80d040e3986b595bdcaade0e71696ff762e008c195ee23b4579458708bb9e916f7ddfa8e3b9352
7
+ data.tar.gz: 8b981e1bc4e904085ecedfe459d31c91d06ed353ba0ceef3d67865ea195f31a36539284246b54537fc5af8b54eafcf1a60415eb88c4850d7cdf3af378d449206
@@ -680,6 +680,22 @@ en:
680
680
  resolution: "The _type field is a reserved one used by Mongoid to determine the
681
681
  class for instantiating an object. Please don't save data in this field or ensure
682
682
  that any values in this field correspond to valid models."
683
+ unrecognized_model_alias:
684
+ message: "Cannot find any model with type %{model_alias}"
685
+ summary: "A document is trying to load a polymorphic association, but the data refers to a type of object that can't be resolved (%{model_alias}). It might be that you've renamed the target class."
686
+ resolution: "Register the old name as an alias on the refactored target object, using `identify_as`. This will allow Mongoid to find the target type even if the name no longer matches what was stored in the database."
687
+ unrecognized_resolver:
688
+ message: "The model resolver %{resolver} was referenced, but never registered."
689
+ summary: "A polymorphic association has been configured to use a resolver
690
+ named %{resolver}, but that resolver has not yet been registered. This
691
+ might be a typo. Currently registered resolvers are: %{resolvers}."
692
+ resolution: "Register custom resolvers with
693
+ `Mongoid::ModelResolver.register_resolver` before attempting to query
694
+ a polymorphic association."
695
+ unregistered_class:
696
+ message: "The class %{klass} is not registered with the resolver %{resolver}."
697
+ summary: "A polymorphic association using the resolver %{resolver} has tried to link to a model of type %{klass}, but the resolver has no knowledge of any such model. This can happen if the association is configured to use a different resolver than the target mode."
698
+ resolution: "Make sure the target model is registered with the same resolver as the polymorphic association, using `identify_as`."
683
699
  unsaved_document:
684
700
  message: "Attempted to save %{document} before the parent %{base}."
685
701
  summary: "You cannot call create or create! through the
@@ -42,7 +42,8 @@ module Mongoid
42
42
  #
43
43
  # @return [ Proxy ] The association.
44
44
  def create_relation(object, association, selected_fields = nil)
45
- type = @attributes[association.inverse_type]
45
+ key = @attributes[association.inverse_type]
46
+ type = key ? association.resolver.model_for(key) : nil
46
47
  target = if t = association.build(self, object, type, selected_fields)
47
48
  association.create_relation(self, t)
48
49
  else
@@ -116,7 +117,11 @@ module Mongoid
116
117
  # during binding or when cascading callbacks. Whenever we retrieve
117
118
  # associations within the codebase, we use without_autobuild.
118
119
  if !without_autobuild? && association.embedded? && attribute_missing?(field_name)
119
- raise Mongoid::Errors::AttributeNotLoaded.new(self.class, field_name)
120
+ # We always allow accessing the parent document of an embedded one.
121
+ try_get_parent = association.is_a?(
122
+ Mongoid::Association::Embedded::EmbeddedIn
123
+ ) && field_name == association.key
124
+ raise Mongoid::Errors::AttributeNotLoaded.new(self.class, field_name) unless try_get_parent
120
125
  end
121
126
 
122
127
  if !reload && (value = ivar(name)) != false
@@ -53,12 +53,25 @@ module Mongoid
53
53
  @attributes = attributes.with_indifferent_access
54
54
  @association = association
55
55
  @options = options
56
- @class_name = options[:class_name] ? options[:class_name].constantize : association.klass
56
+ @class_name = class_from(options[:class_name])
57
57
  @destroy = @attributes.delete(:_destroy)
58
58
  end
59
59
 
60
60
  private
61
61
 
62
+ # Coerces the argument into a class, or defaults to the association's class.
63
+ #
64
+ # @param [ String | Mongoid::Document | nil ] name_or_class the value to coerce
65
+ #
66
+ # @return [ Mongoid::Document ] the resulting class
67
+ def class_from(name_or_class)
68
+ case name_or_class
69
+ when nil, false then association.klass
70
+ when String then name_or_class.constantize
71
+ else name_or_class
72
+ end
73
+ end
74
+
62
75
  # Extracts and converts the id to the expected type.
63
76
  #
64
77
  # @return [ BSON::ObjectId | String | Object | nil ] The converted id,
@@ -23,7 +23,13 @@ module Mongoid
23
23
  binding do
24
24
  check_polymorphic_inverses!(_target)
25
25
  bind_foreign_key(_base, record_id(_target))
26
- bind_polymorphic_inverse_type(_base, _target.class.name)
26
+
27
+ # set the inverse type (e.g. "#{name}_type") for new polymorphic associations
28
+ if _association.inverse_type && !_base.frozen?
29
+ key = _association.resolver.default_key_for(_target)
30
+ bind_polymorphic_inverse_type(_base, key)
31
+ end
32
+
27
33
  if inverse = _association.inverse(_target)
28
34
  if set_base_association
29
35
  if _base.referenced_many?
@@ -33,7 +33,7 @@ module Mongoid
33
33
  end
34
34
 
35
35
  def query_criteria(object, type)
36
- cls = type ? type.constantize : relation_class
36
+ cls = type ? (type.is_a?(String) ? type.constantize : type) : relation_class
37
37
  crit = cls.criteria
38
38
  crit = crit.apply_scope(scope)
39
39
  crit.where(primary_key => object)
@@ -103,6 +103,21 @@ module Mongoid
103
103
  @polymorphic ||= !!@options[:polymorphic]
104
104
  end
105
105
 
106
+ # Returns the object responsible for converting polymorphic type references into
107
+ # class objects, and vice versa. This is obtained via the `:polymorphic` option
108
+ # that was given when the association was defined.
109
+ #
110
+ # See Mongoid::ModelResolver.resolver for how the `:polymorphic` option is
111
+ # interpreted here.
112
+ #
113
+ # @raise KeyError if no such resolver has been registered under the given
114
+ # identifier.
115
+ #
116
+ # @return [ nil | Mongoid::ModelResolver ] the resolver to use
117
+ def resolver
118
+ @resolver ||= Mongoid::ModelResolver.resolver(@options[:polymorphic])
119
+ end
120
+
106
121
  # The name of the field used to store the type of polymorphic association.
107
122
  #
108
123
  # @return [ String ] The field used to store the type of polymorphic association.
@@ -6,6 +6,7 @@ require 'mongoid/association/referenced/has_many/buildable'
6
6
  require 'mongoid/association/referenced/has_many/proxy'
7
7
  require 'mongoid/association/referenced/has_many/enumerable'
8
8
  require 'mongoid/association/referenced/has_many/eager'
9
+ require 'mongoid/association/referenced/with_polymorphic_criteria'
9
10
 
10
11
  module Mongoid
11
12
  module Association
@@ -15,6 +16,7 @@ module Mongoid
15
16
  class HasMany
16
17
  include Relatable
17
18
  include Buildable
19
+ include WithPolymorphicCriteria
18
20
 
19
21
  # The options available for this type of association, in addition to the
20
22
  # common ones.
@@ -131,6 +133,12 @@ module Mongoid
131
133
  # @param [ Class ] object_class The object class.
132
134
  #
133
135
  # @return [ Mongoid::Criteria ] The criteria object.
136
+ #
137
+ # @deprecated in 9.0.x
138
+ #
139
+ # It appears as if this method is an artifact left over from a refactoring that renamed it
140
+ # `with_polymorphic_criterion`, and made it private. Regardless, this method isn't referenced
141
+ # anywhere else, and is unlikely to be useful to external clients. We should remove it.
134
142
  def add_polymorphic_criterion(criteria, object_class)
135
143
  if polymorphic?
136
144
  criteria.where(type => object_class.name)
@@ -138,6 +146,7 @@ module Mongoid
138
146
  criteria
139
147
  end
140
148
  end
149
+ Mongoid.deprecate(self, :add_polymorphic_criterion)
141
150
 
142
151
  # Is this association polymorphic?
143
152
  #
@@ -222,14 +231,6 @@ module Mongoid
222
231
  with_ordering(crit)
223
232
  end
224
233
 
225
- def with_polymorphic_criterion(criteria, base)
226
- if polymorphic?
227
- criteria.where(type => base.class.name)
228
- else
229
- criteria
230
- end
231
- end
232
-
233
234
  def with_ordering(criteria)
234
235
  if order
235
236
  criteria.order_by(order)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
  # rubocop:todo all
3
3
 
4
+ require 'mongoid/association/referenced/with_polymorphic_criteria'
5
+
4
6
  module Mongoid
5
7
  module Association
6
8
  module Referenced
@@ -8,6 +10,7 @@ module Mongoid
8
10
 
9
11
  # The Builder behavior for has_one associations.
10
12
  module Buildable
13
+ include WithPolymorphicCriteria
11
14
 
12
15
  # This method either takes an _id or an object and queries for the
13
16
  # inverse side using the id or sets the object after clearing the
@@ -57,14 +60,6 @@ module Mongoid
57
60
  query_criteria(object, base).take
58
61
  end
59
62
 
60
- def with_polymorphic_criterion(criteria, base)
61
- if polymorphic?
62
- criteria.where(type => base.class.name)
63
- else
64
- criteria
65
- end
66
- end
67
-
68
63
  def query?(object)
69
64
  object && !object.is_a?(Mongoid::Document)
70
65
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Association
5
+ module Referenced
6
+ # Implements the `with_polymorphic_criteria` shared behavior.
7
+ #
8
+ # @api private
9
+ module WithPolymorphicCriteria
10
+ # If the receiver represents a polymorphic association, applies
11
+ # the polymorphic search criteria to the given `criteria` object.
12
+ #
13
+ # @param [ Mongoid::Criteria ] criteria the criteria to append to
14
+ # if receiver is polymorphic.
15
+ # @param [ Mongoid::Document ] base the document to use when resolving
16
+ # the polymorphic type keys.
17
+ #
18
+ # @return [ Mongoid::Criteria] the resulting criteria, which may be
19
+ # the same as the input.
20
+ def with_polymorphic_criterion(criteria, base)
21
+ if polymorphic?
22
+ # 1. get the resolver for the inverse association
23
+ resolver = klass.reflect_on_association(as).resolver
24
+
25
+ # 2. look up the list of keys from the resolver, given base
26
+ keys = resolver.keys_for(base)
27
+
28
+ # 3. use equality if there is just one key, `in` if there are multiple
29
+ if keys.many?
30
+ criteria.where(type => { :$in => keys })
31
+ else
32
+ criteria.where(type => keys.first)
33
+ end
34
+ else
35
+ criteria
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -60,7 +60,8 @@ module Mongoid
60
60
  re_define_method(meth) do |attrs|
61
61
  _assigning do
62
62
  if association.polymorphic? and association.inverse_type
63
- options = options.merge!(:class_name => self.send(association.inverse_type))
63
+ klass = association.resolver.model_for(send(association.inverse_type))
64
+ options = options.merge!(:class_name => klass)
64
65
  end
65
66
  association.nested_builder(attrs, options).build(self)
66
67
  end
@@ -86,7 +86,7 @@ module Mongoid
86
86
  else
87
87
  PersistenceContext.get(self) ||
88
88
  PersistenceContext.get(self.class) ||
89
- PersistenceContext.new(self.class, storage_options)
89
+ PersistenceContext.new(self.class, default_storage_options)
90
90
  end
91
91
  end
92
92
 
@@ -112,6 +112,19 @@ module Mongoid
112
112
 
113
113
  private
114
114
 
115
+ def default_storage_options
116
+ # Nothing is overridden, we use either the default storage_options
117
+ # or storage_options defined for the document class.
118
+ return storage_options if Threaded.client_override.nil? && Threaded.database_override.nil?
119
+
120
+ storage_options.tap do |opts|
121
+ # Globally overridden client replaces client defined for the document class.
122
+ opts[:client] = Threaded.client_override unless Threaded.client_override.nil?
123
+ # Globally overridden database replaces database defined for the document class.
124
+ opts[:database] = Threaded.database_override unless Threaded.database_override.nil?
125
+ end
126
+ end
127
+
115
128
  def set_persistence_context(options_or_context)
116
129
  PersistenceContext.set(self, options_or_context)
117
130
  end
@@ -62,6 +62,7 @@ module Mongoid
62
62
  rescue *transactions_not_supported_exceptions
63
63
  raise Mongoid::Errors::TransactionsNotSupported
64
64
  ensure
65
+ Threaded.clear_modified_documents(session)
65
66
  Threaded.clear_session(client: persistence_context.client)
66
67
  end
67
68
 
@@ -89,21 +90,22 @@ module Mongoid
89
90
  def transaction(options = {}, session_options: {})
90
91
  with_session(session_options) do |session|
91
92
  begin
92
- session.start_transaction(options)
93
- yield
94
- commit_transaction(session)
93
+ session.with_transaction(options) do
94
+ yield
95
+ end
96
+ run_commit_callbacks(session)
95
97
  rescue *transactions_not_supported_exceptions
96
98
  raise Mongoid::Errors::TransactionsNotSupported
97
99
  rescue Mongoid::Errors::Rollback
98
- abort_transaction(session)
100
+ run_abort_callbacks(session)
99
101
  rescue Mongoid::Errors::InvalidSessionNesting
100
102
  # Session should be ended here.
101
103
  raise Mongoid::Errors::InvalidTransactionNesting.new
102
104
  rescue Mongo::Error::InvalidSession, Mongo::Error::InvalidTransactionOperation => e
103
- abort_transaction(session)
104
- raise Mongoid::Errors::TransactionError(e)
105
+ run_abort_callbacks(session)
106
+ raise Mongoid::Errors::TransactionError.new(e)
105
107
  rescue StandardError => e
106
- abort_transaction(session)
108
+ run_abort_callbacks(session)
107
109
  raise e
108
110
  end
109
111
  end
@@ -189,25 +191,21 @@ module Mongoid
189
191
  _session&.in_transaction? || false
190
192
  end
191
193
 
192
- # Commits the active transaction on the session, and calls
193
- # after_commit callbacks on modified documents.
194
+ # Runs after_commit callbacks on modified documents.
194
195
  #
195
196
  # @param [ Mongo::Session ] session Session on which
196
197
  # a transaction is started.
197
- def commit_transaction(session)
198
- session.commit_transaction
198
+ def run_commit_callbacks(session)
199
199
  Threaded.clear_modified_documents(session).each do |doc|
200
200
  doc.run_after_callbacks(:commit)
201
201
  end
202
202
  end
203
203
 
204
- # Aborts the active transaction on the session, and calls
205
- # after_rollback callbacks on modified documents.
204
+ # Runs after_rollback callbacks on modified documents.
206
205
  #
207
206
  # @param [ Mongo::Session ] session Session on which
208
207
  # a transaction is started.
209
- def abort_transaction(session)
210
- session.abort_transaction
208
+ def run_abort_callbacks(session)
211
209
  Threaded.clear_modified_documents(session).each do |doc|
212
210
  doc.run_after_callbacks(:rollback)
213
211
  end
@@ -5,6 +5,7 @@ require "mongoid/changeable"
5
5
  require "mongoid/collection_configurable"
6
6
  require "mongoid/encryptable"
7
7
  require "mongoid/findable"
8
+ require 'mongoid/identifiable'
8
9
  require "mongoid/indexable"
9
10
  require "mongoid/inspectable"
10
11
  require "mongoid/interceptable"
@@ -44,6 +45,7 @@ module Mongoid
44
45
  include Attributes
45
46
  include Evolvable
46
47
  include Fields
48
+ include Identifiable
47
49
  include Indexable
48
50
  include Inspectable
49
51
  include Matchable
@@ -17,6 +17,7 @@ require 'mongoid/timestamps'
17
17
  require 'mongoid/association'
18
18
  require 'mongoid/composable'
19
19
  require 'mongoid/touchable'
20
+ require 'mongoid/model_resolver'
20
21
 
21
22
  module Mongoid
22
23
  # This is the base module for all domain objects that need to be persisted to
@@ -31,6 +32,7 @@ module Mongoid
31
32
 
32
33
  included do
33
34
  Mongoid.register_model(self)
35
+ Mongoid::ModelResolver.register(self)
34
36
  end
35
37
 
36
38
  # Regex for matching illegal BSON keys.
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Errors
5
+ # Raised when a polymorphic association is queried, but the type of the
6
+ # association cannot be resolved. This usually happens when the data in
7
+ # the database references a type that no longer exists.
8
+ #
9
+ # For example, consider the following model:
10
+ #
11
+ # class Manager
12
+ # include Mongoid::Document
13
+ # belongs_to :unit, polymorphic: true
14
+ # end
15
+ #
16
+ # Imagine there is a document in the `managers` collection that looks
17
+ # something like this:
18
+ #
19
+ # { _id: ..., unit_id: ..., unit_type: 'Department::Engineering' }
20
+ #
21
+ # If, at some point in your refactoring, you rename the `Department::Engineering`
22
+ # model to something else, Mongoid will no longer be able to resolve the
23
+ # type of this association, and asking for `manager.unit` will raise this
24
+ # exception.
25
+ #
26
+ # To fix this exception, you can add an alias to the model class so that it
27
+ # can still be found, even after renaming it:
28
+ #
29
+ # module Engineering
30
+ # class Department
31
+ # include Mongoid::Document
32
+ #
33
+ # identify_as 'Department::Engineering'
34
+ #
35
+ # # ...
36
+ # end
37
+ # end
38
+ #
39
+ # Better practice would be to use unique strings instead of class names to
40
+ # identify these polymorphic types in the database (e.g. 'dept' instead of
41
+ # 'Department::Engineering').
42
+ class UnrecognizedModelAlias < MongoidError
43
+ def initialize(model_alias)
44
+ super(
45
+ compose_message(
46
+ 'unrecognized_model_alias',
47
+ model_alias: model_alias.inspect
48
+ )
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Errors
5
+ # Raised when a model resolver is referenced, but not registered.
6
+ #
7
+ # class Manager
8
+ # include Mongoid::Document
9
+ # belongs_to :unit, polymorphic: :org
10
+ # end
11
+ #
12
+ # If `:org` has not previously been registered as a model resolver,
13
+ # Mongoid will raise UnrecognizedResolver when it tries to resolve
14
+ # a manager's unit.
15
+ class UnrecognizedResolver < MongoidError
16
+ def initialize(resolver)
17
+ super(
18
+ compose_message(
19
+ 'unrecognized_resolver',
20
+ resolver: resolver.inspect,
21
+ resolvers: [ :default, *Mongoid::ModelResolver.resolvers.keys ].inspect
22
+ )
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Errors
5
+ # Raised when Mongoid tries to query the identifier to use for a given
6
+ # class in a polymorphic association, but the class has not previously
7
+ # been registered by resolver that was used for the query.
8
+ #
9
+ # Here's an exammple:
10
+ #
11
+ # class Department
12
+ # include Mongoid::Document
13
+ # has_many :managers, as: :unit
14
+ # end
15
+ #
16
+ # class Manager
17
+ # include Mongoid::Document
18
+ # belongs_to :unit, polymorphic: :org
19
+ # end
20
+ #
21
+ # The Manager class is configured to use a custom resolver named `:org`
22
+ # when resolving the polymorphic `unit` association. However, the `Department`
23
+ # class is not registered with that resolver. When the program tries to
24
+ # associate a manager record with a department, it will not be able to find
25
+ # the required key in the `:org` resolver, and will fail with this exception.
26
+ #
27
+ # The solution is to make sure the `Department` class is properly registered
28
+ # with the `:org` resolver:
29
+ #
30
+ # class Department
31
+ # include Mongoid::Document
32
+ # identify_as resolver: :org
33
+ # has_many :managers, as: :unit
34
+ # end
35
+ class UnregisteredClass < MongoidError
36
+ def initialize(klass, resolver)
37
+ super(
38
+ compose_message(
39
+ 'unregistered_class',
40
+ klass: klass,
41
+ resolver: resolver.inspect
42
+ )
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -69,6 +69,9 @@ require "mongoid/errors/transaction_error"
69
69
  require "mongoid/errors/transactions_not_supported"
70
70
  require "mongoid/errors/unknown_attribute"
71
71
  require "mongoid/errors/unknown_model"
72
+ require 'mongoid/errors/unrecognized_model_alias'
73
+ require 'mongoid/errors/unrecognized_resolver'
74
+ require 'mongoid/errors/unregistered_class'
72
75
  require "mongoid/errors/unsaved_document"
73
76
  require "mongoid/errors/unsupported_javascript"
74
77
  require "mongoid/errors/validations"
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mongoid/model_resolver'
4
+
5
+ module Mongoid
6
+ # Implements the "identify_as" interface (for specifying type aliases
7
+ # for document classes).
8
+ module Identifiable
9
+ extend ActiveSupport::Concern
10
+
11
+ class_methods do
12
+ # Specifies aliases that may be used to identify this document
13
+ # class in polymorphic situations. By default, classes are identified
14
+ # by their class names, but alternative aliases may be used instead,
15
+ # if desired.
16
+ #
17
+ # @param [ Array<String | Symbol> ] aliases the list of aliases to
18
+ # assign to this class.
19
+ # @param [ Mongoid::ModelResolver::Interface | Symbol | :default ] resolver the
20
+ # resolver instance to use when registering the type. If :default, the default
21
+ # `ModelResolver` instance will be used. If any other symbol, it must identify a
22
+ # previously registered ModelResolver instance.
23
+ def identify_as(*aliases, resolver: :default)
24
+ Mongoid::ModelResolver.resolver(resolver).register(self, *aliases)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -39,11 +39,25 @@ module Mongoid
39
39
  # from and behaves identically to association traversal for the purposes
40
40
  # of, for example, subsequent array element retrieval.
41
41
  #
42
- # @param [ Document | Hash ] document The document to extract from.
42
+ # @param [ Document | Hash | String ] document The document to extract from.
43
43
  # @param [ String ] key The key path to extract.
44
44
  #
45
45
  # @return [ Object | Array ] Field value or values.
46
46
  module_function def extract_attribute(document, key)
47
+ # The matcher system will wind up sending atomic values to this as well,
48
+ # when attepting to match more complex types. If anything other than a
49
+ # Document or a Hash is given, we'll short-circuit the logic and just
50
+ # return an empty array.
51
+ return [] unless document.is_a?(Hash) || document.is_a?(Document)
52
+
53
+ # Performance optimization; if the key does not include a '.' character,
54
+ # it must reference an immediate attribute of the document.
55
+ unless key.include?('.')
56
+ hash = document.respond_to?(:attributes) ? document.attributes : document
57
+ key = find_exact_key(hash, key)
58
+ return key ? [ hash[key] ] : []
59
+ end
60
+
47
61
  if document.respond_to?(:as_attributes, true)
48
62
  # If a document has hash fields, as_attributes would keep those fields
49
63
  # as Hash instances which do not offer indifferent access.