mongoid-multi-db 3.0.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.
Files changed (276) hide show
  1. data/CHANGELOG.md +615 -0
  2. data/LICENSE +20 -0
  3. data/README.md +62 -0
  4. data/Rakefile +49 -0
  5. data/lib/config/locales/bg.yml +54 -0
  6. data/lib/config/locales/de.yml +54 -0
  7. data/lib/config/locales/en-GB.yml +55 -0
  8. data/lib/config/locales/en.yml +55 -0
  9. data/lib/config/locales/es.yml +52 -0
  10. data/lib/config/locales/fr.yml +55 -0
  11. data/lib/config/locales/hi.yml +46 -0
  12. data/lib/config/locales/hu.yml +57 -0
  13. data/lib/config/locales/id.yml +55 -0
  14. data/lib/config/locales/it.yml +52 -0
  15. data/lib/config/locales/ja.yml +50 -0
  16. data/lib/config/locales/kr.yml +47 -0
  17. data/lib/config/locales/nl.yml +52 -0
  18. data/lib/config/locales/pl.yml +52 -0
  19. data/lib/config/locales/pt-BR.yml +53 -0
  20. data/lib/config/locales/pt.yml +53 -0
  21. data/lib/config/locales/ro.yml +59 -0
  22. data/lib/config/locales/ru.yml +54 -0
  23. data/lib/config/locales/sv.yml +53 -0
  24. data/lib/config/locales/vi.yml +55 -0
  25. data/lib/config/locales/zh-CN.yml +46 -0
  26. data/lib/mongoid.rb +148 -0
  27. data/lib/mongoid/atomic.rb +230 -0
  28. data/lib/mongoid/atomic/modifiers.rb +243 -0
  29. data/lib/mongoid/atomic/paths.rb +3 -0
  30. data/lib/mongoid/atomic/paths/embedded.rb +43 -0
  31. data/lib/mongoid/atomic/paths/embedded/many.rb +44 -0
  32. data/lib/mongoid/atomic/paths/embedded/one.rb +43 -0
  33. data/lib/mongoid/atomic/paths/root.rb +40 -0
  34. data/lib/mongoid/attributes.rb +234 -0
  35. data/lib/mongoid/attributes/processing.rb +146 -0
  36. data/lib/mongoid/callbacks.rb +135 -0
  37. data/lib/mongoid/collection.rb +153 -0
  38. data/lib/mongoid/collection_proxy.rb +59 -0
  39. data/lib/mongoid/collections.rb +120 -0
  40. data/lib/mongoid/collections/master.rb +45 -0
  41. data/lib/mongoid/collections/operations.rb +44 -0
  42. data/lib/mongoid/collections/retry.rb +46 -0
  43. data/lib/mongoid/components.rb +96 -0
  44. data/lib/mongoid/config.rb +347 -0
  45. data/lib/mongoid/config/database.rb +186 -0
  46. data/lib/mongoid/config/replset_database.rb +82 -0
  47. data/lib/mongoid/connection_proxy.rb +30 -0
  48. data/lib/mongoid/contexts.rb +25 -0
  49. data/lib/mongoid/contexts/enumerable.rb +288 -0
  50. data/lib/mongoid/contexts/enumerable/sort.rb +43 -0
  51. data/lib/mongoid/contexts/mongo.rb +409 -0
  52. data/lib/mongoid/copyable.rb +48 -0
  53. data/lib/mongoid/criteria.rb +418 -0
  54. data/lib/mongoid/criterion/builder.rb +34 -0
  55. data/lib/mongoid/criterion/complex.rb +84 -0
  56. data/lib/mongoid/criterion/creational.rb +34 -0
  57. data/lib/mongoid/criterion/exclusion.rb +108 -0
  58. data/lib/mongoid/criterion/inclusion.rb +305 -0
  59. data/lib/mongoid/criterion/inspection.rb +22 -0
  60. data/lib/mongoid/criterion/optional.rb +232 -0
  61. data/lib/mongoid/criterion/selector.rb +153 -0
  62. data/lib/mongoid/cursor.rb +86 -0
  63. data/lib/mongoid/database_proxy.rb +97 -0
  64. data/lib/mongoid/default_scope.rb +36 -0
  65. data/lib/mongoid/dirty.rb +110 -0
  66. data/lib/mongoid/document.rb +280 -0
  67. data/lib/mongoid/errors.rb +17 -0
  68. data/lib/mongoid/errors/callback.rb +26 -0
  69. data/lib/mongoid/errors/document_not_found.rb +28 -0
  70. data/lib/mongoid/errors/eager_load.rb +25 -0
  71. data/lib/mongoid/errors/invalid_collection.rb +18 -0
  72. data/lib/mongoid/errors/invalid_database.rb +19 -0
  73. data/lib/mongoid/errors/invalid_field.rb +18 -0
  74. data/lib/mongoid/errors/invalid_find.rb +19 -0
  75. data/lib/mongoid/errors/invalid_options.rb +28 -0
  76. data/lib/mongoid/errors/invalid_time.rb +25 -0
  77. data/lib/mongoid/errors/invalid_type.rb +25 -0
  78. data/lib/mongoid/errors/mixed_relations.rb +37 -0
  79. data/lib/mongoid/errors/mongoid_error.rb +26 -0
  80. data/lib/mongoid/errors/too_many_nested_attribute_records.rb +20 -0
  81. data/lib/mongoid/errors/unsaved_document.rb +23 -0
  82. data/lib/mongoid/errors/unsupported_version.rb +20 -0
  83. data/lib/mongoid/errors/validations.rb +23 -0
  84. data/lib/mongoid/extensions.rb +82 -0
  85. data/lib/mongoid/extensions/array/deletion.rb +29 -0
  86. data/lib/mongoid/extensions/false_class/equality.rb +26 -0
  87. data/lib/mongoid/extensions/hash/criteria_helpers.rb +45 -0
  88. data/lib/mongoid/extensions/hash/scoping.rb +25 -0
  89. data/lib/mongoid/extensions/integer/checks.rb +23 -0
  90. data/lib/mongoid/extensions/nil/collectionization.rb +23 -0
  91. data/lib/mongoid/extensions/object/checks.rb +29 -0
  92. data/lib/mongoid/extensions/object/reflections.rb +48 -0
  93. data/lib/mongoid/extensions/object/substitutable.rb +15 -0
  94. data/lib/mongoid/extensions/object/yoda.rb +44 -0
  95. data/lib/mongoid/extensions/object_id/conversions.rb +60 -0
  96. data/lib/mongoid/extensions/proc/scoping.rb +25 -0
  97. data/lib/mongoid/extensions/string/checks.rb +36 -0
  98. data/lib/mongoid/extensions/string/conversions.rb +22 -0
  99. data/lib/mongoid/extensions/string/inflections.rb +118 -0
  100. data/lib/mongoid/extensions/symbol/checks.rb +23 -0
  101. data/lib/mongoid/extensions/symbol/inflections.rb +66 -0
  102. data/lib/mongoid/extensions/true_class/equality.rb +26 -0
  103. data/lib/mongoid/extras.rb +31 -0
  104. data/lib/mongoid/factory.rb +46 -0
  105. data/lib/mongoid/fields.rb +332 -0
  106. data/lib/mongoid/fields/mappings.rb +41 -0
  107. data/lib/mongoid/fields/serializable.rb +201 -0
  108. data/lib/mongoid/fields/serializable/array.rb +49 -0
  109. data/lib/mongoid/fields/serializable/big_decimal.rb +42 -0
  110. data/lib/mongoid/fields/serializable/bignum.rb +10 -0
  111. data/lib/mongoid/fields/serializable/binary.rb +11 -0
  112. data/lib/mongoid/fields/serializable/boolean.rb +43 -0
  113. data/lib/mongoid/fields/serializable/date.rb +51 -0
  114. data/lib/mongoid/fields/serializable/date_time.rb +28 -0
  115. data/lib/mongoid/fields/serializable/fixnum.rb +10 -0
  116. data/lib/mongoid/fields/serializable/float.rb +32 -0
  117. data/lib/mongoid/fields/serializable/foreign_keys/array.rb +42 -0
  118. data/lib/mongoid/fields/serializable/foreign_keys/object.rb +47 -0
  119. data/lib/mongoid/fields/serializable/hash.rb +11 -0
  120. data/lib/mongoid/fields/serializable/integer.rb +44 -0
  121. data/lib/mongoid/fields/serializable/localized.rb +41 -0
  122. data/lib/mongoid/fields/serializable/nil_class.rb +38 -0
  123. data/lib/mongoid/fields/serializable/object.rb +11 -0
  124. data/lib/mongoid/fields/serializable/object_id.rb +31 -0
  125. data/lib/mongoid/fields/serializable/range.rb +42 -0
  126. data/lib/mongoid/fields/serializable/set.rb +42 -0
  127. data/lib/mongoid/fields/serializable/string.rb +27 -0
  128. data/lib/mongoid/fields/serializable/symbol.rb +27 -0
  129. data/lib/mongoid/fields/serializable/time.rb +23 -0
  130. data/lib/mongoid/fields/serializable/time_with_zone.rb +23 -0
  131. data/lib/mongoid/fields/serializable/timekeeping.rb +106 -0
  132. data/lib/mongoid/finders.rb +152 -0
  133. data/lib/mongoid/hierarchy.rb +120 -0
  134. data/lib/mongoid/identity.rb +92 -0
  135. data/lib/mongoid/identity_map.rb +119 -0
  136. data/lib/mongoid/indexes.rb +54 -0
  137. data/lib/mongoid/inspection.rb +54 -0
  138. data/lib/mongoid/javascript.rb +20 -0
  139. data/lib/mongoid/javascript/functions.yml +63 -0
  140. data/lib/mongoid/json.rb +16 -0
  141. data/lib/mongoid/keys.rb +144 -0
  142. data/lib/mongoid/logger.rb +39 -0
  143. data/lib/mongoid/matchers.rb +32 -0
  144. data/lib/mongoid/matchers/all.rb +21 -0
  145. data/lib/mongoid/matchers/and.rb +30 -0
  146. data/lib/mongoid/matchers/default.rb +70 -0
  147. data/lib/mongoid/matchers/exists.rb +23 -0
  148. data/lib/mongoid/matchers/gt.rb +21 -0
  149. data/lib/mongoid/matchers/gte.rb +21 -0
  150. data/lib/mongoid/matchers/in.rb +21 -0
  151. data/lib/mongoid/matchers/lt.rb +21 -0
  152. data/lib/mongoid/matchers/lte.rb +21 -0
  153. data/lib/mongoid/matchers/ne.rb +21 -0
  154. data/lib/mongoid/matchers/nin.rb +21 -0
  155. data/lib/mongoid/matchers/or.rb +33 -0
  156. data/lib/mongoid/matchers/size.rb +21 -0
  157. data/lib/mongoid/matchers/strategies.rb +93 -0
  158. data/lib/mongoid/multi_database.rb +31 -0
  159. data/lib/mongoid/multi_parameter_attributes.rb +106 -0
  160. data/lib/mongoid/named_scope.rb +146 -0
  161. data/lib/mongoid/nested_attributes.rb +54 -0
  162. data/lib/mongoid/observer.rb +170 -0
  163. data/lib/mongoid/paranoia.rb +158 -0
  164. data/lib/mongoid/persistence.rb +264 -0
  165. data/lib/mongoid/persistence/atomic.rb +223 -0
  166. data/lib/mongoid/persistence/atomic/add_to_set.rb +35 -0
  167. data/lib/mongoid/persistence/atomic/bit.rb +37 -0
  168. data/lib/mongoid/persistence/atomic/inc.rb +31 -0
  169. data/lib/mongoid/persistence/atomic/operation.rb +85 -0
  170. data/lib/mongoid/persistence/atomic/pop.rb +34 -0
  171. data/lib/mongoid/persistence/atomic/pull.rb +34 -0
  172. data/lib/mongoid/persistence/atomic/pull_all.rb +34 -0
  173. data/lib/mongoid/persistence/atomic/push.rb +31 -0
  174. data/lib/mongoid/persistence/atomic/push_all.rb +31 -0
  175. data/lib/mongoid/persistence/atomic/rename.rb +31 -0
  176. data/lib/mongoid/persistence/atomic/sets.rb +30 -0
  177. data/lib/mongoid/persistence/atomic/unset.rb +28 -0
  178. data/lib/mongoid/persistence/deletion.rb +32 -0
  179. data/lib/mongoid/persistence/insertion.rb +41 -0
  180. data/lib/mongoid/persistence/modification.rb +37 -0
  181. data/lib/mongoid/persistence/operations.rb +211 -0
  182. data/lib/mongoid/persistence/operations/embedded/insert.rb +42 -0
  183. data/lib/mongoid/persistence/operations/embedded/remove.rb +40 -0
  184. data/lib/mongoid/persistence/operations/insert.rb +34 -0
  185. data/lib/mongoid/persistence/operations/remove.rb +33 -0
  186. data/lib/mongoid/persistence/operations/update.rb +64 -0
  187. data/lib/mongoid/railtie.rb +126 -0
  188. data/lib/mongoid/railties/database.rake +182 -0
  189. data/lib/mongoid/railties/document.rb +12 -0
  190. data/lib/mongoid/relations.rb +144 -0
  191. data/lib/mongoid/relations/accessors.rb +138 -0
  192. data/lib/mongoid/relations/auto_save.rb +38 -0
  193. data/lib/mongoid/relations/binding.rb +26 -0
  194. data/lib/mongoid/relations/bindings.rb +9 -0
  195. data/lib/mongoid/relations/bindings/embedded/in.rb +69 -0
  196. data/lib/mongoid/relations/bindings/embedded/many.rb +93 -0
  197. data/lib/mongoid/relations/bindings/embedded/one.rb +61 -0
  198. data/lib/mongoid/relations/bindings/referenced/in.rb +76 -0
  199. data/lib/mongoid/relations/bindings/referenced/many.rb +54 -0
  200. data/lib/mongoid/relations/bindings/referenced/many_to_many.rb +51 -0
  201. data/lib/mongoid/relations/bindings/referenced/one.rb +58 -0
  202. data/lib/mongoid/relations/builder.rb +57 -0
  203. data/lib/mongoid/relations/builders.rb +83 -0
  204. data/lib/mongoid/relations/builders/embedded/in.rb +29 -0
  205. data/lib/mongoid/relations/builders/embedded/many.rb +40 -0
  206. data/lib/mongoid/relations/builders/embedded/one.rb +30 -0
  207. data/lib/mongoid/relations/builders/nested_attributes/many.rb +110 -0
  208. data/lib/mongoid/relations/builders/nested_attributes/one.rb +135 -0
  209. data/lib/mongoid/relations/builders/referenced/in.rb +26 -0
  210. data/lib/mongoid/relations/builders/referenced/many.rb +27 -0
  211. data/lib/mongoid/relations/builders/referenced/many_to_many.rb +38 -0
  212. data/lib/mongoid/relations/builders/referenced/one.rb +26 -0
  213. data/lib/mongoid/relations/cascading.rb +56 -0
  214. data/lib/mongoid/relations/cascading/delete.rb +19 -0
  215. data/lib/mongoid/relations/cascading/destroy.rb +26 -0
  216. data/lib/mongoid/relations/cascading/nullify.rb +18 -0
  217. data/lib/mongoid/relations/cascading/strategy.rb +26 -0
  218. data/lib/mongoid/relations/constraint.rb +42 -0
  219. data/lib/mongoid/relations/conversions.rb +35 -0
  220. data/lib/mongoid/relations/cyclic.rb +103 -0
  221. data/lib/mongoid/relations/embedded/atomic.rb +89 -0
  222. data/lib/mongoid/relations/embedded/atomic/operation.rb +63 -0
  223. data/lib/mongoid/relations/embedded/atomic/pull.rb +65 -0
  224. data/lib/mongoid/relations/embedded/atomic/push_all.rb +59 -0
  225. data/lib/mongoid/relations/embedded/atomic/set.rb +61 -0
  226. data/lib/mongoid/relations/embedded/atomic/unset.rb +41 -0
  227. data/lib/mongoid/relations/embedded/in.rb +220 -0
  228. data/lib/mongoid/relations/embedded/many.rb +560 -0
  229. data/lib/mongoid/relations/embedded/one.rb +206 -0
  230. data/lib/mongoid/relations/embedded/sort.rb +31 -0
  231. data/lib/mongoid/relations/macros.rb +310 -0
  232. data/lib/mongoid/relations/many.rb +135 -0
  233. data/lib/mongoid/relations/metadata.rb +919 -0
  234. data/lib/mongoid/relations/nested_builder.rb +75 -0
  235. data/lib/mongoid/relations/one.rb +36 -0
  236. data/lib/mongoid/relations/options.rb +47 -0
  237. data/lib/mongoid/relations/polymorphic.rb +40 -0
  238. data/lib/mongoid/relations/proxy.rb +145 -0
  239. data/lib/mongoid/relations/referenced/batch.rb +72 -0
  240. data/lib/mongoid/relations/referenced/batch/insert.rb +57 -0
  241. data/lib/mongoid/relations/referenced/in.rb +262 -0
  242. data/lib/mongoid/relations/referenced/many.rb +623 -0
  243. data/lib/mongoid/relations/referenced/many_to_many.rb +396 -0
  244. data/lib/mongoid/relations/referenced/one.rb +272 -0
  245. data/lib/mongoid/relations/reflections.rb +62 -0
  246. data/lib/mongoid/relations/synchronization.rb +153 -0
  247. data/lib/mongoid/relations/targets.rb +2 -0
  248. data/lib/mongoid/relations/targets/enumerable.rb +372 -0
  249. data/lib/mongoid/reloading.rb +91 -0
  250. data/lib/mongoid/safety.rb +105 -0
  251. data/lib/mongoid/scope.rb +31 -0
  252. data/lib/mongoid/serialization.rb +134 -0
  253. data/lib/mongoid/sharding.rb +61 -0
  254. data/lib/mongoid/state.rb +97 -0
  255. data/lib/mongoid/threaded.rb +530 -0
  256. data/lib/mongoid/threaded/lifecycle.rb +192 -0
  257. data/lib/mongoid/timestamps.rb +15 -0
  258. data/lib/mongoid/timestamps/created.rb +24 -0
  259. data/lib/mongoid/timestamps/timeless.rb +50 -0
  260. data/lib/mongoid/timestamps/updated.rb +26 -0
  261. data/lib/mongoid/validations.rb +140 -0
  262. data/lib/mongoid/validations/associated.rb +46 -0
  263. data/lib/mongoid/validations/uniqueness.rb +145 -0
  264. data/lib/mongoid/version.rb +4 -0
  265. data/lib/mongoid/versioning.rb +185 -0
  266. data/lib/rack/mongoid.rb +2 -0
  267. data/lib/rack/mongoid/middleware/identity_map.rb +38 -0
  268. data/lib/rails/generators/mongoid/config/config_generator.rb +25 -0
  269. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +20 -0
  270. data/lib/rails/generators/mongoid/model/model_generator.rb +24 -0
  271. data/lib/rails/generators/mongoid/model/templates/model.rb.tt +19 -0
  272. data/lib/rails/generators/mongoid/observer/observer_generator.rb +17 -0
  273. data/lib/rails/generators/mongoid/observer/templates/observer.rb.tt +4 -0
  274. data/lib/rails/generators/mongoid_generator.rb +70 -0
  275. data/lib/rails/mongoid.rb +91 -0
  276. metadata +465 -0
@@ -0,0 +1,262 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+ module Referenced #:nodoc:
5
+
6
+ # This class handles all behaviour for relations that are either
7
+ # one-to-many or one-to-one, where the foreign key is store on this side
8
+ # of the relation and the reference is to document(s) in another
9
+ # collection.
10
+ class In < Relations::One
11
+
12
+ # Instantiate a new referenced_in relation.
13
+ #
14
+ # @example Create the new relation.
15
+ # Referenced::In.new(game, person, metadata)
16
+ #
17
+ # @param [ Document ] base The document this relation hangs off of.
18
+ # @param [ Document, Array<Document> ] target The target (parent) of the
19
+ # relation.
20
+ # @param [ Metadata ] metadata The relation's metadata.
21
+ def initialize(base, target, metadata)
22
+ init(base, target, metadata) do
23
+ characterize_one(target)
24
+ bind_one
25
+ end
26
+ end
27
+
28
+ # Substitutes the supplied target documents for the existing document
29
+ # in the relation.
30
+ #
31
+ # @example Substitute the relation.
32
+ # name.substitute(new_name)
33
+ #
34
+ # @param [ Document, Array<Document> ] new_target The replacement.
35
+ # @param [ true, false ] building Are we in build mode?
36
+ #
37
+ # @return [ In, nil ] The relation or nil.
38
+ #
39
+ # @since 2.0.0.rc.1
40
+ def substitute(replacement)
41
+ tap do |proxy|
42
+ proxy.unbind_one
43
+ return nil unless replacement
44
+ proxy.target = replacement
45
+ proxy.bind_one
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # Instantiate the binding associated with this relation.
52
+ #
53
+ # @example Get the binding object.
54
+ # binding([ address ])
55
+ #
56
+ # @param [ Document, Array<Document> ] new_target The replacement.
57
+ #
58
+ # @return [ Binding ] The binding object.
59
+ #
60
+ # @since 2.0.0.rc.1
61
+ def binding
62
+ Bindings::Referenced::In.new(base, target, metadata)
63
+ end
64
+
65
+ # Are we able to persist this relation?
66
+ #
67
+ # @example Can we persist the relation?
68
+ # relation.persistable?
69
+ #
70
+ # @return [ true, false ] If the relation is persistable.
71
+ #
72
+ # @since 2.1.0
73
+ def persistable?
74
+ target.persisted? && !_binding? && !_building?
75
+ end
76
+
77
+ class << self
78
+
79
+ # Return the builder that is responsible for generating the documents
80
+ # that will be used by this relation.
81
+ #
82
+ # @example Get the builder.
83
+ # Referenced::In.builder(meta, object)
84
+ #
85
+ # @param [ Document ] base The base document.
86
+ # @param [ Metadata ] meta The metadata of the relation.
87
+ # @param [ Document, Hash ] object A document or attributes to build
88
+ # with.
89
+ #
90
+ # @return [ Builder ] A new builder object.
91
+ #
92
+ # @since 2.0.0.rc.1
93
+ def builder(base, meta, object)
94
+ Builders::Referenced::In.new(base, meta, object)
95
+ end
96
+
97
+ # Get the standard criteria used for querying this relation.
98
+ #
99
+ # @example Get the criteria.
100
+ # Proxy.criteria(meta, id, Model)
101
+ #
102
+ # @param [ Metadata ] metadata The metadata.
103
+ # @param [ Object ] object The value of the foreign key.
104
+ # @param [ Class ] type The optional type.
105
+ #
106
+ # @return [ Criteria ] The criteria.
107
+ #
108
+ # @since 2.1.0
109
+ def criteria(metadata, object, type = nil)
110
+ type.where(:_id => object)
111
+ end
112
+
113
+ # Get the criteria that is used to eager load a relation of this
114
+ # type.
115
+ #
116
+ # @example Get the eager load criteria.
117
+ # Proxy.eager_load(metadata, criteria)
118
+ #
119
+ # @param [ Metadata ] metadata The relation metadata.
120
+ # @param [ Criteria ] criteria The criteria being used.
121
+ #
122
+ # @return [ Criteria ] The criteria to eager load the relation.
123
+ #
124
+ # @since 2.2.0
125
+ def eager_load(metadata, criteria)
126
+ raise Errors::EagerLoad.new(metadata.name) if metadata.polymorphic?
127
+ klass, foreign_key = metadata.klass, metadata.foreign_key
128
+ klass.any_in("_id" => criteria.load_ids(foreign_key).uniq).each do |doc|
129
+ IdentityMap.set(doc)
130
+ end
131
+ end
132
+
133
+ # Returns true if the relation is an embedded one. In this case
134
+ # always false.
135
+ #
136
+ # @example Is this relation embedded?
137
+ # Referenced::In.embedded?
138
+ #
139
+ # @return [ false ] Always false.
140
+ #
141
+ # @since 2.0.0.rc.1
142
+ def embedded?
143
+ false
144
+ end
145
+
146
+ # Get the default value for the foreign key.
147
+ #
148
+ # @example Get the default.
149
+ # Referenced::In.foreign_key_default
150
+ #
151
+ # @return [ nil ] Always nil.
152
+ #
153
+ # @since 2.0.0.rc.1
154
+ def foreign_key_default
155
+ nil
156
+ end
157
+
158
+ # Returns the suffix of the foreign key field, either "_id" or "_ids".
159
+ #
160
+ # @example Get the suffix for the foreign key.
161
+ # Referenced::In.foreign_key_suffix
162
+ #
163
+ # @return [ String ] "_id"
164
+ #
165
+ # @since 2.0.0.rc.1
166
+ def foreign_key_suffix
167
+ "_id"
168
+ end
169
+
170
+ # Returns the macro for this relation. Used mostly as a helper in
171
+ # reflection.
172
+ #
173
+ # @example Get the macro.
174
+ # Referenced::In.macro
175
+ #
176
+ # @return [ Symbol ] :referenced_in
177
+ def macro
178
+ :referenced_in
179
+ end
180
+
181
+ # Return the nested builder that is responsible for generating the documents
182
+ # that will be used by this relation.
183
+ #
184
+ # @example Get the nested builder.
185
+ # Referenced::In.builder(attributes, options)
186
+ #
187
+ # @param [ Metadata ] metadata The relation metadata.
188
+ # @param [ Hash ] attributes The attributes to build with.
189
+ # @param [ Hash ] options The options for the builder.
190
+ #
191
+ # @option options [ true, false ] :allow_destroy Can documents be
192
+ # deleted?
193
+ # @option options [ Integer ] :limit Max number of documents to
194
+ # create at once.
195
+ # @option options [ Proc, Symbol ] :reject_if If documents match this
196
+ # option then they are ignored.
197
+ # @option options [ true, false ] :update_only Only existing documents
198
+ # can be modified.
199
+ #
200
+ # @return [ NestedBuilder ] A newly instantiated nested builder object.
201
+ #
202
+ # @since 2.0.0.rc.1
203
+ def nested_builder(metadata, attributes, options)
204
+ Builders::NestedAttributes::One.new(metadata, attributes, options)
205
+ end
206
+
207
+ # Get the path calculator for the supplied document.
208
+ #
209
+ # @example Get the path calculator.
210
+ # Proxy.path(document)
211
+ #
212
+ # @param [ Document ] document The document to calculate on.
213
+ #
214
+ # @return [ Root ] The root atomic path calculator.
215
+ #
216
+ # @since 2.1.0
217
+ def path(document)
218
+ Mongoid::Atomic::Paths::Root.new(document)
219
+ end
220
+
221
+ # Tells the caller if this relation is one that stores the foreign
222
+ # key on its own objects.
223
+ #
224
+ # @example Does this relation store a foreign key?
225
+ # Referenced::In.stores_foreign_key?
226
+ #
227
+ # @return [ true ] Always true.
228
+ #
229
+ # @since 2.0.0.rc.1
230
+ def stores_foreign_key?
231
+ true
232
+ end
233
+
234
+ # Get the valid options allowed with this relation.
235
+ #
236
+ # @example Get the valid options.
237
+ # Relation.valid_options
238
+ #
239
+ # @return [ Array<Symbol> ] The valid options.
240
+ #
241
+ # @since 2.1.0
242
+ def valid_options
243
+ [ :autosave, :foreign_key, :index, :polymorphic ]
244
+ end
245
+
246
+ # Get the default validation setting for the relation. Determines if
247
+ # by default a validates associated will occur.
248
+ #
249
+ # @example Get the validation default.
250
+ # Proxy.validation_default
251
+ #
252
+ # @return [ true, false ] The validation default.
253
+ #
254
+ # @since 2.1.9
255
+ def validation_default
256
+ false
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,623 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Relations #:nodoc:
4
+ module Referenced #:nodoc:
5
+
6
+ # This class defines the behaviour for all relations that are a
7
+ # one-to-many between documents in different collections.
8
+ class Many < Relations::Many
9
+ include Batch
10
+
11
+ delegate :count, :to => :criteria
12
+ delegate :first, :in_memory, :last, :reset, :uniq, :to => :target
13
+
14
+ # Appends a document or array of documents to the relation. Will set
15
+ # the parent and update the index in the process.
16
+ #
17
+ # @example Append a document.
18
+ # person.posts << post
19
+ #
20
+ # @example Push a document.
21
+ # person.posts.push(post)
22
+ #
23
+ # @example Concat with other documents.
24
+ # person.posts.concat([ post_one, post_two ])
25
+ #
26
+ # @param [ Document, Array<Document> ] *args Any number of documents.
27
+ #
28
+ # @return [ Array<Document> ] The loaded docs.
29
+ #
30
+ # @since 2.0.0.beta.1
31
+ def <<(*args)
32
+ batched do
33
+ args.flatten.each do |doc|
34
+ next unless doc
35
+ append(doc)
36
+ doc.save if persistable? && !doc.validated?
37
+ end
38
+ end
39
+ end
40
+ alias :concat :<<
41
+ alias :push :<<
42
+
43
+ # Build a new document from the attributes and append it to this
44
+ # relation without saving.
45
+ #
46
+ # @example Build a new document on the relation.
47
+ # person.posts.build(:title => "A new post")
48
+ #
49
+ # @overload build(attributes = {}, options = {}, type = nil)
50
+ # @param [ Hash ] attributes The attributes of the new document.
51
+ # @param [ Hash ] options The scoped assignment options.
52
+ # @param [ Class ] type The optional subclass to build.
53
+ #
54
+ # @overload build(attributes = {}, type = nil)
55
+ # @param [ Hash ] attributes The attributes of the new document.
56
+ # @param [ Class ] type The optional subclass to build.
57
+ #
58
+ # @return [ Document ] The new document.
59
+ #
60
+ # @since 2.0.0.beta.1
61
+ def build(attributes = {}, options = {}, type = nil)
62
+ if options.is_a? Class
63
+ options, type = {}, options
64
+ end
65
+
66
+ Factory.build(type || klass, attributes, options).tap do |doc|
67
+ append(doc)
68
+ yield(doc) if block_given?
69
+ end
70
+ end
71
+ alias :new :build
72
+
73
+ # Creates a new document on the references many relation. This will
74
+ # save the document if the parent has been persisted.
75
+ #
76
+ # @example Create and save the new document.
77
+ # person.posts.create(:text => "Testing")
78
+ #
79
+ # @overload create(attributes = nil, options = {}, type = nil)
80
+ # @param [ Hash ] attributes The attributes to create with.
81
+ # @param [ Hash ] options The scoped assignment options.
82
+ # @param [ Class ] type The optional type of document to create.
83
+ #
84
+ # @overload create(attributes = nil, type = nil)
85
+ # @param [ Hash ] attributes The attributes to create with.
86
+ # @param [ Class ] type The optional type of document to create.
87
+ #
88
+ # @return [ Document ] The newly created document.
89
+ #
90
+ # @since 2.0.0.beta.1
91
+ def create(attributes = nil, options = {}, type = nil, &block)
92
+ build(attributes, options, type, &block).tap do |doc|
93
+ base.persisted? ? doc.save : raise_unsaved(doc)
94
+ end
95
+ end
96
+
97
+ # Creates a new document on the references many relation. This will
98
+ # save the document if the parent has been persisted and will raise an
99
+ # error if validation fails.
100
+ #
101
+ # @example Create and save the new document.
102
+ # person.posts.create!(:text => "Testing")
103
+ #
104
+ # @overload create!(attributes = nil, options = {}, type = nil)
105
+ # @param [ Hash ] attributes The attributes to create with.
106
+ # @param [ Hash ] options The scoped assignment options.
107
+ # @param [ Class ] type The optional type of document to create.
108
+ #
109
+ # @overload create!(attributes = nil, type = nil)
110
+ # @param [ Hash ] attributes The attributes to create with.
111
+ # @param [ Class ] type The optional type of document to create.
112
+ #
113
+ # @raise [ Errors::Validations ] If validation failed.
114
+ #
115
+ # @return [ Document ] The newly created document.
116
+ #
117
+ # @since 2.0.0.beta.1
118
+ def create!(attributes = nil, options = {}, type = nil, &block)
119
+ build(attributes, options, type, &block).tap do |doc|
120
+ base.persisted? ? doc.save! : raise_unsaved(doc)
121
+ end
122
+ end
123
+
124
+ # Delete the document from the relation. This will set the foreign key
125
+ # on the document to nil. If the dependent options on the relation are
126
+ # :delete or :destroy the appropriate removal will occur.
127
+ #
128
+ # @example Delete the document.
129
+ # person.posts.delete(post)
130
+ #
131
+ # @param [ Document ] document The document to remove.
132
+ #
133
+ # @return [ Document ] The matching document.
134
+ #
135
+ # @since 2.1.0
136
+ def delete(document)
137
+ target.delete(document) do |doc|
138
+ if doc
139
+ unbind_one(doc)
140
+ cascade!(doc)
141
+ end
142
+ end
143
+ end
144
+
145
+ # Deletes all related documents from the database given the supplied
146
+ # conditions.
147
+ #
148
+ # @example Delete all documents in the relation.
149
+ # person.posts.delete_all
150
+ #
151
+ # @example Conditonally delete all documents in the relation.
152
+ # person.posts.delete_all(:conditions => { :title => "Testing" })
153
+ #
154
+ # @param [ Hash ] conditions Optional conditions to delete with.
155
+ #
156
+ # @return [ Integer ] The number of documents deleted.
157
+ #
158
+ # @since 2.0.0.beta.1
159
+ def delete_all(conditions = nil)
160
+ remove_all(conditions, :delete_all)
161
+ end
162
+
163
+ # Destroys all related documents from the database given the supplied
164
+ # conditions.
165
+ #
166
+ # @example Destroy all documents in the relation.
167
+ # person.posts.destroy_all
168
+ #
169
+ # @example Conditonally destroy all documents in the relation.
170
+ # person.posts.destroy_all(:conditions => { :title => "Testing" })
171
+ #
172
+ # @param [ Hash ] conditions Optional conditions to destroy with.
173
+ #
174
+ # @return [ Integer ] The number of documents destroyd.
175
+ #
176
+ # @since 2.0.0.beta.1
177
+ def destroy_all(conditions = nil)
178
+ remove_all(conditions, :destroy_all)
179
+ end
180
+
181
+ # Iterate over each document in the relation and yield to the provided
182
+ # block.
183
+ #
184
+ # @note This will load the entire relation into memory.
185
+ #
186
+ # @example Iterate over the documents.
187
+ # person.posts.each do |post|
188
+ # post.save
189
+ # end
190
+ #
191
+ # @return [ Array<Document> ] The loaded docs.
192
+ #
193
+ # @since 2.1.0
194
+ def each
195
+ target.each { |doc| yield(doc) if block_given? }
196
+ end
197
+
198
+ # Find the matchind document on the association, either based on id or
199
+ # conditions.
200
+ #
201
+ # @example Find by an id.
202
+ # person.posts.find(BSON::ObjectId.new)
203
+ #
204
+ # @example Find by multiple ids.
205
+ # person.posts.find([ BSON::ObjectId.new, BSON::ObjectId.new ])
206
+ #
207
+ # @example Conditionally find all matching documents.
208
+ # person.posts.find(:all, :conditions => { :title => "Sir" })
209
+ #
210
+ # @example Conditionally find the first document.
211
+ # person.posts.find(:first, :conditions => { :title => "Sir" })
212
+ #
213
+ # @example Conditionally find the last document.
214
+ # person.posts.find(:last, :conditions => { :title => "Sir" })
215
+ #
216
+ # @param [ Symbol, BSON::ObjectId, Array<BSON::ObjectId> ] arg The
217
+ # argument to search with.
218
+ # @param [ Hash ] options The options to search with.
219
+ #
220
+ # @return [ Document, Criteria ] The matching document(s).
221
+ #
222
+ # @since 2.0.0.beta.1
223
+ def find(*args)
224
+ criteria.find(*args)
225
+ end
226
+
227
+ # Instantiate a new references_many relation. Will set the foreign key
228
+ # and the base on the inverse object.
229
+ #
230
+ # @example Create the new relation.
231
+ # Referenced::Many.new(base, target, metadata)
232
+ #
233
+ # @param [ Document ] base The document this relation hangs off of.
234
+ # @param [ Array<Document> ] target The target of the relation.
235
+ # @param [ Metadata ] metadata The relation's metadata.
236
+ #
237
+ # @since 2.0.0.beta.1
238
+ def initialize(base, target, metadata)
239
+ init(base, Targets::Enumerable.new(target), metadata) do
240
+ raise_mixed if klass.embedded?
241
+ end
242
+ end
243
+
244
+ # Removes all associations between the base document and the target
245
+ # documents by deleting the foreign keys and the references, orphaning
246
+ # the target documents in the process.
247
+ #
248
+ # @example Nullify the relation.
249
+ # person.posts.nullify
250
+ #
251
+ # @since 2.0.0.rc.1
252
+ def nullify
253
+ criteria.update(metadata.foreign_key => nil)
254
+ target.clear do |doc|
255
+ unbind_one(doc)
256
+ end
257
+ end
258
+ alias :nullify_all :nullify
259
+
260
+ # Clear the relation. Will delete the documents from the db if they are
261
+ # already persisted.
262
+ #
263
+ # @example Clear the relation.
264
+ # person.posts.clear
265
+ #
266
+ # @return [ Many ] The relation emptied.
267
+ #
268
+ # @since 2.0.0.beta.1
269
+ def purge
270
+ unless metadata.destructive?
271
+ nullify
272
+ else
273
+ criteria.delete_all
274
+ target.clear do |doc|
275
+ unbind_one(doc)
276
+ doc.destroyed = true
277
+ end
278
+ end
279
+ end
280
+ alias :clear :purge
281
+
282
+ # Substitutes the supplied target documents for the existing documents
283
+ # in the relation. If the new target is nil, perform the necessary
284
+ # deletion.
285
+ #
286
+ # @example Replace the relation.
287
+ # person.posts.substitute([ new_post ])
288
+ #
289
+ # @param [ Array<Document> ] replacement The replacement target.
290
+ #
291
+ # @return [ Many ] The relation.
292
+ #
293
+ # @since 2.0.0.rc.1
294
+ def substitute(replacement)
295
+ tap do |proxy|
296
+ if replacement != proxy.in_memory
297
+ proxy.purge
298
+ proxy.push(replacement.compact.uniq) if replacement
299
+ end
300
+ end
301
+ end
302
+
303
+ private
304
+
305
+ # Appends the document to the target array, updating the index on the
306
+ # document at the same time.
307
+ #
308
+ # @example Append the document to the relation.
309
+ # relation.append(document)
310
+ #
311
+ # @param [ Document ] document The document to append to the target.
312
+ #
313
+ # @since 2.0.0.rc.1
314
+ def append(document)
315
+ target.push(document)
316
+ characterize_one(document)
317
+ bind_one(document)
318
+ end
319
+
320
+ # Instantiate the binding associated with this relation.
321
+ #
322
+ # @example Get the binding.
323
+ # relation.binding([ address ])
324
+ #
325
+ # @param [ Array<Document> ] new_target The new documents to bind with.
326
+ #
327
+ # @return [ Binding ] The binding.
328
+ #
329
+ # @since 2.0.0.rc.1
330
+ def binding
331
+ Bindings::Referenced::Many.new(base, target, metadata)
332
+ end
333
+
334
+ # Get the collection of the relation in question.
335
+ #
336
+ # @example Get the collection of the relation.
337
+ # relation.collection
338
+ #
339
+ # @return [ Collection ] The collection of the relation.
340
+ #
341
+ # @since 2.0.2
342
+ def collection
343
+ klass.collection
344
+ end
345
+
346
+ # Returns the criteria object for the target class with its documents set
347
+ # to target.
348
+ #
349
+ # @example Get a criteria for the relation.
350
+ # relation.criteria
351
+ #
352
+ # @return [ Criteria ] A new criteria.
353
+ #
354
+ # @since 2.0.0.beta.1
355
+ def criteria
356
+ Many.criteria(metadata, Conversions.flag(base.id, metadata))
357
+ end
358
+
359
+ # Perform the necessary cascade operations for documents that just got
360
+ # deleted or nullified.
361
+ #
362
+ # @example Cascade the change.
363
+ # relation.cascade!(document)
364
+ #
365
+ # @param [ Document ] document The document to cascade on.
366
+ #
367
+ # @return [ true, false ] If the metadata is destructive.
368
+ #
369
+ # @since 2.1.0
370
+ def cascade!(document)
371
+ if persistable?
372
+ if metadata.destructive?
373
+ document.send(metadata.dependent)
374
+ else
375
+ document.save
376
+ end
377
+ end
378
+ end
379
+
380
+ # If the target array does not respond to the supplied method then try to
381
+ # find a named scope or criteria on the class and send the call there.
382
+ #
383
+ # If the method exists on the array, use the default proxy behavior.
384
+ #
385
+ # @param [ Symbol, String ] name The name of the method.
386
+ # @param [ Array ] args The method args
387
+ # @param [ Proc ] block Optional block to pass.
388
+ #
389
+ # @return [ Criteria, Object ] A Criteria or return value from the target.
390
+ #
391
+ # @since 2.0.0.beta.1
392
+ def method_missing(name, *args, &block)
393
+ if target.respond_to?(name)
394
+ target.send(name, *args, &block)
395
+ else
396
+ klass.send(:with_scope, criteria) do
397
+ criteria.send(name, *args, &block)
398
+ end
399
+ end
400
+ end
401
+
402
+ # Are we able to persist this relation?
403
+ #
404
+ # @example Can we persist the relation?
405
+ # relation.persistable?
406
+ #
407
+ # @return [ true, false ] If the relation is persistable.
408
+ #
409
+ # @since 2.1.0
410
+ def persistable?
411
+ _creating? || base.persisted? && !_binding? && !_building?
412
+ end
413
+
414
+ # Deletes all related documents from the database given the supplied
415
+ # conditions.
416
+ #
417
+ # @example Delete all documents in the relation.
418
+ # person.posts.delete_all
419
+ #
420
+ # @example Conditonally delete all documents in the relation.
421
+ # person.posts.delete_all(:conditions => { :title => "Testing" })
422
+ #
423
+ # @param [ Hash ] conditions Optional conditions to delete with.
424
+ # @param [ Symbol ] The deletion method to call.
425
+ #
426
+ # @return [ Integer ] The number of documents deleted.
427
+ #
428
+ # @since 2.1.0
429
+ def remove_all(conditions = nil, method = :delete_all)
430
+ selector = (conditions || {})[:conditions] || {}
431
+ klass.send(method, :conditions => selector.merge!(criteria.selector)).tap do
432
+ target.delete_if do |doc|
433
+ if doc.matches?(selector)
434
+ unbind_one(doc) and true
435
+ end
436
+ end
437
+ end
438
+ end
439
+
440
+ class << self
441
+
442
+ # Return the builder that is responsible for generating the documents
443
+ # that will be used by this relation.
444
+ #
445
+ # @example Get the builder.
446
+ # Referenced::Many.builder(meta, object)
447
+ #
448
+ # @param [ Document ] base The base document.
449
+ # @param [ Metadata ] meta The metadata of the relation.
450
+ # @param [ Document, Hash ] object A document or attributes to build
451
+ # with.
452
+ #
453
+ # @return [ Builder ] A new builder object.
454
+ #
455
+ # @since 2.0.0.rc.1
456
+ def builder(base, meta, object)
457
+ Builders::Referenced::Many.new(base, meta, object || [])
458
+ end
459
+
460
+ # Get the standard criteria used for querying this relation.
461
+ #
462
+ # @example Get the criteria.
463
+ # Proxy.criteria(meta, id, Model)
464
+ #
465
+ # @param [ Metadata ] metadata The metadata.
466
+ # @param [ Object ] object The value of the foreign key.
467
+ # @param [ Class ] type The optional type.
468
+ #
469
+ # @return [ Criteria ] The criteria.
470
+ #
471
+ # @since 2.1.0
472
+ def criteria(metadata, object, type = nil)
473
+ metadata.klass.where(metadata.foreign_key => object)
474
+ end
475
+
476
+ # Eager load the relation based on the criteria.
477
+ #
478
+ # @example Eager load the criteria.
479
+ # Proxy.eager_load(metadata, criteria)
480
+ #
481
+ # @param [ Metadata ] metadata The relation metadata.
482
+ # @param [ Criteria ] criteria The criteria being used.
483
+ #
484
+ # @return [ Criteria ] The criteria to eager load the relation.
485
+ #
486
+ # @since 2.2.0
487
+ def eager_load(metadata, criteria)
488
+ klass, foreign_key = metadata.klass, metadata.foreign_key
489
+ klass.any_in(foreign_key => criteria.load_ids("_id").uniq).each do |doc|
490
+ IdentityMap.set_many(doc, foreign_key => doc.send(foreign_key))
491
+ end
492
+ end
493
+
494
+ # Returns true if the relation is an embedded one. In this case
495
+ # always false.
496
+ #
497
+ # @example Is this relation embedded?
498
+ # Referenced::Many.embedded?
499
+ #
500
+ # @return [ false ] Always false.
501
+ #
502
+ # @since 2.0.0.rc.1
503
+ def embedded?
504
+ false
505
+ end
506
+
507
+ # Get the default value for the foreign key.
508
+ #
509
+ # @example Get the default.
510
+ # Referenced::Many.foreign_key_default
511
+ #
512
+ # @return [ nil ] Always nil.
513
+ #
514
+ # @since 2.0.0.rc.1
515
+ def foreign_key_default
516
+ nil
517
+ end
518
+
519
+ # Returns the suffix of the foreign key field, either "_id" or "_ids".
520
+ #
521
+ # @example Get the suffix for the foreign key.
522
+ # Referenced::Many.foreign_key_suffix
523
+ #
524
+ # @return [ String ] "_id"
525
+ #
526
+ # @since 2.0.0.rc.1
527
+ def foreign_key_suffix
528
+ "_id"
529
+ end
530
+
531
+ # Returns the macro for this relation. Used mostly as a helper in
532
+ # reflection.
533
+ #
534
+ # @example Get the macro.
535
+ # Referenced::Many.macro
536
+ #
537
+ # @return [ Symbol ] :references_many
538
+ def macro
539
+ :references_many
540
+ end
541
+
542
+ # Return the nested builder that is responsible for generating the documents
543
+ # that will be used by this relation.
544
+ #
545
+ # @example Get the nested builder.
546
+ # Referenced::Many.builder(attributes, options)
547
+ #
548
+ # @param [ Metadata ] metadata The relation metadata.
549
+ # @param [ Hash ] attributes The attributes to build with.
550
+ # @param [ Hash ] options The options for the builder.
551
+ #
552
+ # @option options [ true, false ] :allow_destroy Can documents be
553
+ # deleted?
554
+ # @option options [ Integer ] :limit Max number of documents to
555
+ # create at once.
556
+ # @option options [ Proc, Symbol ] :reject_if If documents match this
557
+ # option then they are ignored.
558
+ # @option options [ true, false ] :update_only Only existing documents
559
+ # can be modified.
560
+ #
561
+ # @return [ NestedBuilder ] A newly instantiated nested builder object.
562
+ #
563
+ # @since 2.0.0.rc.1
564
+ def nested_builder(metadata, attributes, options)
565
+ Builders::NestedAttributes::Many.new(metadata, attributes, options)
566
+ end
567
+
568
+ # Get the path calculator for the supplied document.
569
+ #
570
+ # @example Get the path calculator.
571
+ # Proxy.path(document)
572
+ #
573
+ # @param [ Document ] document The document to calculate on.
574
+ #
575
+ # @return [ Root ] The root atomic path calculator.
576
+ #
577
+ # @since 2.1.0
578
+ def path(document)
579
+ Mongoid::Atomic::Paths::Root.new(document)
580
+ end
581
+
582
+ # Tells the caller if this relation is one that stores the foreign
583
+ # key on its own objects.
584
+ #
585
+ # @example Does this relation store a foreign key?
586
+ # Referenced::Many.stores_foreign_key?
587
+ #
588
+ # @return [ false ] Always false.
589
+ #
590
+ # @since 2.0.0.rc.1
591
+ def stores_foreign_key?
592
+ false
593
+ end
594
+
595
+ # Get the valid options allowed with this relation.
596
+ #
597
+ # @example Get the valid options.
598
+ # Relation.valid_options
599
+ #
600
+ # @return [ Array<Symbol> ] The valid options.
601
+ #
602
+ # @since 2.1.0
603
+ def valid_options
604
+ [ :as, :autosave, :dependent, :foreign_key, :order ]
605
+ end
606
+
607
+ # Get the default validation setting for the relation. Determines if
608
+ # by default a validates associated will occur.
609
+ #
610
+ # @example Get the validation default.
611
+ # Proxy.validation_default
612
+ #
613
+ # @return [ true, false ] The validation default.
614
+ #
615
+ # @since 2.1.9
616
+ def validation_default
617
+ true
618
+ end
619
+ end
620
+ end
621
+ end
622
+ end
623
+ end