humanoid 1.2.7

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 (210) hide show
  1. data/.gitignore +6 -0
  2. data/.watchr +29 -0
  3. data/HISTORY +342 -0
  4. data/MIT_LICENSE +20 -0
  5. data/README.rdoc +56 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/caliper.yml +4 -0
  9. data/humanoid.gemspec +374 -0
  10. data/lib/humanoid.rb +111 -0
  11. data/lib/humanoid/associations.rb +258 -0
  12. data/lib/humanoid/associations/belongs_to.rb +64 -0
  13. data/lib/humanoid/associations/belongs_to_related.rb +62 -0
  14. data/lib/humanoid/associations/has_many.rb +180 -0
  15. data/lib/humanoid/associations/has_many_related.rb +109 -0
  16. data/lib/humanoid/associations/has_one.rb +95 -0
  17. data/lib/humanoid/associations/has_one_related.rb +81 -0
  18. data/lib/humanoid/associations/options.rb +57 -0
  19. data/lib/humanoid/associations/proxy.rb +31 -0
  20. data/lib/humanoid/attributes.rb +184 -0
  21. data/lib/humanoid/callbacks.rb +23 -0
  22. data/lib/humanoid/collection.rb +118 -0
  23. data/lib/humanoid/collections/cyclic_iterator.rb +34 -0
  24. data/lib/humanoid/collections/master.rb +28 -0
  25. data/lib/humanoid/collections/mimic.rb +46 -0
  26. data/lib/humanoid/collections/operations.rb +41 -0
  27. data/lib/humanoid/collections/slaves.rb +44 -0
  28. data/lib/humanoid/commands.rb +182 -0
  29. data/lib/humanoid/commands/create.rb +21 -0
  30. data/lib/humanoid/commands/delete.rb +16 -0
  31. data/lib/humanoid/commands/delete_all.rb +23 -0
  32. data/lib/humanoid/commands/deletion.rb +18 -0
  33. data/lib/humanoid/commands/destroy.rb +19 -0
  34. data/lib/humanoid/commands/destroy_all.rb +23 -0
  35. data/lib/humanoid/commands/save.rb +27 -0
  36. data/lib/humanoid/components.rb +24 -0
  37. data/lib/humanoid/config.rb +84 -0
  38. data/lib/humanoid/contexts.rb +25 -0
  39. data/lib/humanoid/contexts/enumerable.rb +117 -0
  40. data/lib/humanoid/contexts/ids.rb +25 -0
  41. data/lib/humanoid/contexts/mongo.rb +224 -0
  42. data/lib/humanoid/contexts/paging.rb +42 -0
  43. data/lib/humanoid/criteria.rb +259 -0
  44. data/lib/humanoid/criterion/complex.rb +21 -0
  45. data/lib/humanoid/criterion/exclusion.rb +65 -0
  46. data/lib/humanoid/criterion/inclusion.rb +91 -0
  47. data/lib/humanoid/criterion/optional.rb +128 -0
  48. data/lib/humanoid/cursor.rb +82 -0
  49. data/lib/humanoid/document.rb +300 -0
  50. data/lib/humanoid/enslavement.rb +38 -0
  51. data/lib/humanoid/errors.rb +77 -0
  52. data/lib/humanoid/extensions.rb +84 -0
  53. data/lib/humanoid/extensions/array/accessors.rb +17 -0
  54. data/lib/humanoid/extensions/array/aliasing.rb +4 -0
  55. data/lib/humanoid/extensions/array/assimilation.rb +26 -0
  56. data/lib/humanoid/extensions/array/conversions.rb +29 -0
  57. data/lib/humanoid/extensions/array/parentization.rb +13 -0
  58. data/lib/humanoid/extensions/boolean/conversions.rb +16 -0
  59. data/lib/humanoid/extensions/date/conversions.rb +15 -0
  60. data/lib/humanoid/extensions/datetime/conversions.rb +17 -0
  61. data/lib/humanoid/extensions/float/conversions.rb +16 -0
  62. data/lib/humanoid/extensions/hash/accessors.rb +38 -0
  63. data/lib/humanoid/extensions/hash/assimilation.rb +30 -0
  64. data/lib/humanoid/extensions/hash/conversions.rb +15 -0
  65. data/lib/humanoid/extensions/hash/criteria_helpers.rb +20 -0
  66. data/lib/humanoid/extensions/hash/scoping.rb +12 -0
  67. data/lib/humanoid/extensions/integer/conversions.rb +16 -0
  68. data/lib/humanoid/extensions/nil/assimilation.rb +13 -0
  69. data/lib/humanoid/extensions/object/conversions.rb +33 -0
  70. data/lib/humanoid/extensions/proc/scoping.rb +12 -0
  71. data/lib/humanoid/extensions/string/conversions.rb +15 -0
  72. data/lib/humanoid/extensions/string/inflections.rb +97 -0
  73. data/lib/humanoid/extensions/symbol/inflections.rb +36 -0
  74. data/lib/humanoid/extensions/time/conversions.rb +18 -0
  75. data/lib/humanoid/factory.rb +19 -0
  76. data/lib/humanoid/field.rb +39 -0
  77. data/lib/humanoid/fields.rb +62 -0
  78. data/lib/humanoid/finders.rb +224 -0
  79. data/lib/humanoid/identity.rb +39 -0
  80. data/lib/humanoid/indexes.rb +30 -0
  81. data/lib/humanoid/matchers.rb +36 -0
  82. data/lib/humanoid/matchers/all.rb +11 -0
  83. data/lib/humanoid/matchers/default.rb +26 -0
  84. data/lib/humanoid/matchers/exists.rb +13 -0
  85. data/lib/humanoid/matchers/gt.rb +11 -0
  86. data/lib/humanoid/matchers/gte.rb +11 -0
  87. data/lib/humanoid/matchers/in.rb +11 -0
  88. data/lib/humanoid/matchers/lt.rb +11 -0
  89. data/lib/humanoid/matchers/lte.rb +11 -0
  90. data/lib/humanoid/matchers/ne.rb +11 -0
  91. data/lib/humanoid/matchers/nin.rb +11 -0
  92. data/lib/humanoid/matchers/size.rb +11 -0
  93. data/lib/humanoid/memoization.rb +27 -0
  94. data/lib/humanoid/named_scope.rb +40 -0
  95. data/lib/humanoid/scope.rb +75 -0
  96. data/lib/humanoid/timestamps.rb +30 -0
  97. data/lib/humanoid/versioning.rb +28 -0
  98. data/perf/benchmark.rb +77 -0
  99. data/spec/integration/humanoid/associations_spec.rb +301 -0
  100. data/spec/integration/humanoid/attributes_spec.rb +22 -0
  101. data/spec/integration/humanoid/commands_spec.rb +216 -0
  102. data/spec/integration/humanoid/contexts/enumerable_spec.rb +33 -0
  103. data/spec/integration/humanoid/criteria_spec.rb +224 -0
  104. data/spec/integration/humanoid/document_spec.rb +587 -0
  105. data/spec/integration/humanoid/extensions_spec.rb +26 -0
  106. data/spec/integration/humanoid/finders_spec.rb +119 -0
  107. data/spec/integration/humanoid/inheritance_spec.rb +137 -0
  108. data/spec/integration/humanoid/named_scope_spec.rb +46 -0
  109. data/spec/models/address.rb +39 -0
  110. data/spec/models/animal.rb +6 -0
  111. data/spec/models/comment.rb +8 -0
  112. data/spec/models/country_code.rb +6 -0
  113. data/spec/models/employer.rb +5 -0
  114. data/spec/models/game.rb +6 -0
  115. data/spec/models/inheritance.rb +56 -0
  116. data/spec/models/location.rb +5 -0
  117. data/spec/models/mixed_drink.rb +4 -0
  118. data/spec/models/name.rb +13 -0
  119. data/spec/models/namespacing.rb +11 -0
  120. data/spec/models/patient.rb +4 -0
  121. data/spec/models/person.rb +98 -0
  122. data/spec/models/pet.rb +7 -0
  123. data/spec/models/pet_owner.rb +6 -0
  124. data/spec/models/phone.rb +7 -0
  125. data/spec/models/post.rb +15 -0
  126. data/spec/models/translation.rb +5 -0
  127. data/spec/models/vet_visit.rb +5 -0
  128. data/spec/spec.opts +3 -0
  129. data/spec/spec_helper.rb +31 -0
  130. data/spec/unit/mongoid/associations/belongs_to_related_spec.rb +141 -0
  131. data/spec/unit/mongoid/associations/belongs_to_spec.rb +193 -0
  132. data/spec/unit/mongoid/associations/has_many_related_spec.rb +387 -0
  133. data/spec/unit/mongoid/associations/has_many_spec.rb +471 -0
  134. data/spec/unit/mongoid/associations/has_one_related_spec.rb +179 -0
  135. data/spec/unit/mongoid/associations/has_one_spec.rb +282 -0
  136. data/spec/unit/mongoid/associations/options_spec.rb +191 -0
  137. data/spec/unit/mongoid/associations_spec.rb +545 -0
  138. data/spec/unit/mongoid/attributes_spec.rb +484 -0
  139. data/spec/unit/mongoid/callbacks_spec.rb +55 -0
  140. data/spec/unit/mongoid/collection_spec.rb +171 -0
  141. data/spec/unit/mongoid/collections/cyclic_iterator_spec.rb +75 -0
  142. data/spec/unit/mongoid/collections/master_spec.rb +41 -0
  143. data/spec/unit/mongoid/collections/mimic_spec.rb +43 -0
  144. data/spec/unit/mongoid/collections/slaves_spec.rb +81 -0
  145. data/spec/unit/mongoid/commands/create_spec.rb +30 -0
  146. data/spec/unit/mongoid/commands/delete_all_spec.rb +58 -0
  147. data/spec/unit/mongoid/commands/delete_spec.rb +35 -0
  148. data/spec/unit/mongoid/commands/destroy_all_spec.rb +23 -0
  149. data/spec/unit/mongoid/commands/destroy_spec.rb +44 -0
  150. data/spec/unit/mongoid/commands/save_spec.rb +105 -0
  151. data/spec/unit/mongoid/commands_spec.rb +282 -0
  152. data/spec/unit/mongoid/config_spec.rb +165 -0
  153. data/spec/unit/mongoid/contexts/enumerable_spec.rb +374 -0
  154. data/spec/unit/mongoid/contexts/mongo_spec.rb +505 -0
  155. data/spec/unit/mongoid/contexts_spec.rb +25 -0
  156. data/spec/unit/mongoid/criteria_spec.rb +769 -0
  157. data/spec/unit/mongoid/criterion/complex_spec.rb +19 -0
  158. data/spec/unit/mongoid/criterion/exclusion_spec.rb +91 -0
  159. data/spec/unit/mongoid/criterion/inclusion_spec.rb +211 -0
  160. data/spec/unit/mongoid/criterion/optional_spec.rb +329 -0
  161. data/spec/unit/mongoid/cursor_spec.rb +74 -0
  162. data/spec/unit/mongoid/document_spec.rb +986 -0
  163. data/spec/unit/mongoid/enslavement_spec.rb +63 -0
  164. data/spec/unit/mongoid/errors_spec.rb +103 -0
  165. data/spec/unit/mongoid/extensions/array/accessors_spec.rb +50 -0
  166. data/spec/unit/mongoid/extensions/array/assimilation_spec.rb +24 -0
  167. data/spec/unit/mongoid/extensions/array/conversions_spec.rb +35 -0
  168. data/spec/unit/mongoid/extensions/array/parentization_spec.rb +20 -0
  169. data/spec/unit/mongoid/extensions/boolean/conversions_spec.rb +49 -0
  170. data/spec/unit/mongoid/extensions/date/conversions_spec.rb +102 -0
  171. data/spec/unit/mongoid/extensions/datetime/conversions_spec.rb +70 -0
  172. data/spec/unit/mongoid/extensions/float/conversions_spec.rb +61 -0
  173. data/spec/unit/mongoid/extensions/hash/accessors_spec.rb +184 -0
  174. data/spec/unit/mongoid/extensions/hash/assimilation_spec.rb +46 -0
  175. data/spec/unit/mongoid/extensions/hash/conversions_spec.rb +21 -0
  176. data/spec/unit/mongoid/extensions/hash/criteria_helpers_spec.rb +17 -0
  177. data/spec/unit/mongoid/extensions/hash/scoping_spec.rb +14 -0
  178. data/spec/unit/mongoid/extensions/integer/conversions_spec.rb +61 -0
  179. data/spec/unit/mongoid/extensions/nil/assimilation_spec.rb +24 -0
  180. data/spec/unit/mongoid/extensions/object/conversions_spec.rb +43 -0
  181. data/spec/unit/mongoid/extensions/proc/scoping_spec.rb +34 -0
  182. data/spec/unit/mongoid/extensions/string/conversions_spec.rb +17 -0
  183. data/spec/unit/mongoid/extensions/string/inflections_spec.rb +208 -0
  184. data/spec/unit/mongoid/extensions/symbol/inflections_spec.rb +91 -0
  185. data/spec/unit/mongoid/extensions/time/conversions_spec.rb +70 -0
  186. data/spec/unit/mongoid/factory_spec.rb +31 -0
  187. data/spec/unit/mongoid/field_spec.rb +81 -0
  188. data/spec/unit/mongoid/fields_spec.rb +158 -0
  189. data/spec/unit/mongoid/finders_spec.rb +368 -0
  190. data/spec/unit/mongoid/identity_spec.rb +88 -0
  191. data/spec/unit/mongoid/indexes_spec.rb +93 -0
  192. data/spec/unit/mongoid/matchers/all_spec.rb +27 -0
  193. data/spec/unit/mongoid/matchers/default_spec.rb +27 -0
  194. data/spec/unit/mongoid/matchers/exists_spec.rb +56 -0
  195. data/spec/unit/mongoid/matchers/gt_spec.rb +39 -0
  196. data/spec/unit/mongoid/matchers/gte_spec.rb +49 -0
  197. data/spec/unit/mongoid/matchers/in_spec.rb +27 -0
  198. data/spec/unit/mongoid/matchers/lt_spec.rb +39 -0
  199. data/spec/unit/mongoid/matchers/lte_spec.rb +49 -0
  200. data/spec/unit/mongoid/matchers/ne_spec.rb +27 -0
  201. data/spec/unit/mongoid/matchers/nin_spec.rb +27 -0
  202. data/spec/unit/mongoid/matchers/size_spec.rb +27 -0
  203. data/spec/unit/mongoid/matchers_spec.rb +329 -0
  204. data/spec/unit/mongoid/memoization_spec.rb +75 -0
  205. data/spec/unit/mongoid/named_scope_spec.rb +123 -0
  206. data/spec/unit/mongoid/scope_spec.rb +240 -0
  207. data/spec/unit/mongoid/timestamps_spec.rb +25 -0
  208. data/spec/unit/mongoid/versioning_spec.rb +41 -0
  209. data/spec/unit/mongoid_spec.rb +37 -0
  210. metadata +431 -0
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Commands
4
+ class Create
5
+ # Performs a create of the supplied Document, with the necessary
6
+ # callbacks. It then delegates to the Save command.
7
+ #
8
+ # Options:
9
+ #
10
+ # doc: A new +Document+ that is going to be persisted.
11
+ #
12
+ # Returns: +Document+.
13
+ def self.execute(doc, validate = true)
14
+ doc.run_callbacks :before_create
15
+ Save.execute(doc, validate)
16
+ doc.run_callbacks :after_create
17
+ return doc
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Commands
4
+ class Delete
5
+ extend Deletion
6
+ # Performs a delete of the supplied +Document+ without any callbacks.
7
+ #
8
+ # Options:
9
+ #
10
+ # doc: A new +Document+ that is going to be deleted.
11
+ def self.execute(doc)
12
+ delete(doc)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Commands
4
+ class DeleteAll
5
+ # Performs a delete of the all the +Documents+ that match the criteria
6
+ # supplied.
7
+ #
8
+ # Options:
9
+ #
10
+ # params: A set of conditions to find the +Documents+ by.
11
+ # klass: The class of the +Document+ to execute the find on.
12
+ #
13
+ # Example:
14
+ #
15
+ # <tt>DeleteAll.execute(Person, :conditions => { :field => "value" })</tt>
16
+ def self.execute(klass, params = {})
17
+ safe = Humanoid.persist_in_safe_mode
18
+ collection = klass.collection
19
+ collection.remove((params[:conditions] || {}).merge(:_type => klass.name), :safe => safe)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc
3
+ module Commands #:nodoc
4
+ module Deletion #:nodoc
5
+ # If the +Document+ has a parent, delete it from the parent's attributes,
6
+ # otherwise delete it from it's collection.
7
+ def delete(doc)
8
+ parent = doc._parent
9
+ if parent
10
+ parent.remove(doc)
11
+ parent.save
12
+ else
13
+ doc.collection.remove(:_id => doc.id)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Commands
4
+ class Destroy
5
+ extend Deletion
6
+ # Performs a destroy of the supplied +Document+, with the necessary
7
+ # callbacks. It then deletes the record from the collection.
8
+ #
9
+ # Options:
10
+ #
11
+ # doc: A new +Document+ that is going to be destroyed.
12
+ def self.execute(doc)
13
+ doc.run_callbacks :before_destroy
14
+ delete(doc)
15
+ doc.run_callbacks :after_destroy
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Commands
4
+ class DestroyAll
5
+ # Performs a destroy of the all the +Documents+ that match the criteria
6
+ # supplied. Will execute all the destroy callbacks for each +Document+.
7
+ #
8
+ # Options:
9
+ #
10
+ # params: A set of conditions to find the +Documents+ by.
11
+ # klass: The class of the +Document+ to execute the find on.
12
+ #
13
+ # Example:
14
+ #
15
+ # <tt>DestroyAll.execute(Person, :conditions => { :field => "value" })</tt>
16
+ def self.execute(klass, params)
17
+ conditions = params[:conditions] || {}
18
+ params[:conditions] = conditions.merge(:_type => klass.name)
19
+ klass.find(:all, params).each { |doc| Destroy.execute(doc) }; true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Commands
4
+ class Save
5
+ # Performs a save of the supplied +Document+, handling all associated
6
+ # callbacks and validation.
7
+ #
8
+ # Options:
9
+ #
10
+ # doc: A +Document+ that is going to be persisted.
11
+ #
12
+ # Returns: +true+ if validation passes, +false+ if not.
13
+ def self.execute(doc, validate = true)
14
+ return false if validate && !doc.valid?
15
+ doc.run_callbacks :before_save
16
+ parent = doc._parent
17
+ if parent ? Save.execute(parent, validate) : doc.collection.save(doc.raw_attributes, :safe => Humanoid.persist_in_safe_mode)
18
+ doc.new_record = false
19
+ doc.run_callbacks :after_save
20
+ return true
21
+ else
22
+ return false
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc
3
+ module Components #:nodoc
4
+ def self.included(base)
5
+ base.class_eval do
6
+ # All modules that a +Document+ is composed of are defined in this
7
+ # module, to keep the document class from getting too cluttered.
8
+ include Associations
9
+ include Attributes
10
+ include Callbacks
11
+ include Commands
12
+ include Enslavement
13
+ include Fields
14
+ include Indexes
15
+ include Matchers
16
+ include Memoization
17
+ include Observable
18
+ include Validatable
19
+ extend Finders
20
+ extend NamedScope
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc
3
+ class Config #:nodoc
4
+ include Singleton
5
+
6
+ attr_accessor \
7
+ :allow_dynamic_fields,
8
+ :reconnect_time,
9
+ :parameterize_keys,
10
+ :persist_in_safe_mode,
11
+ :raise_not_found_error,
12
+ :use_object_ids
13
+
14
+ # Defaults the configuration options to true.
15
+ def initialize
16
+ @allow_dynamic_fields = true
17
+ @parameterize_keys = true
18
+ @persist_in_safe_mode = true
19
+ @raise_not_found_error = true
20
+ @reconnect_time = 3
21
+ @use_object_ids = false
22
+ end
23
+
24
+ # Sets the Mongo::DB master database to be used. If the object trying to me
25
+ # set is not a valid +Mongo::DB+, then an error will be raise.
26
+ #
27
+ # Example:
28
+ #
29
+ # <tt>Config.master = Mongo::Connection.db("test")</tt>
30
+ #
31
+ # Returns:
32
+ #
33
+ # The Master DB instance.
34
+ def master=(db)
35
+ raise Errors::InvalidDatabase.new(db) unless db.kind_of?(Mongo::DB)
36
+ @master = db
37
+ end
38
+
39
+ # Returns the master database, or if none has been set it will raise an
40
+ # error.
41
+ #
42
+ # Example:
43
+ #
44
+ # <tt>Config.master</tt>
45
+ #
46
+ # Returns:
47
+ #
48
+ # The master +Mongo::DB+
49
+ def master
50
+ @master || (raise Errors::InvalidDatabase.new(nil))
51
+ end
52
+
53
+ alias :database :master
54
+ alias :database= :master=
55
+
56
+ # Sets the Mongo::DB slave databases to be used. If the objects trying to me
57
+ # set are not valid +Mongo::DBs+, then an error will be raise.
58
+ #
59
+ # Example:
60
+ #
61
+ # <tt>Config.slaves = [ Mongo::Connection.db("test") ]</tt>
62
+ #
63
+ # Returns:
64
+ #
65
+ # The slaves DB instances.
66
+ def slaves=(dbs)
67
+ dbs.each { |db| raise Errors::InvalidDatabase.new(db) unless db.kind_of?(Mongo::DB) }
68
+ @slaves = dbs
69
+ end
70
+
71
+ # Returns the slave databases, or if none has been set nil
72
+ #
73
+ # Example:
74
+ #
75
+ # <tt>Config.slaves</tt>
76
+ #
77
+ # Returns:
78
+ #
79
+ # The slave +Mongo::DBs+
80
+ def slaves
81
+ @slaves
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ require "humanoid/contexts/ids"
3
+ require "humanoid/contexts/paging"
4
+ require "humanoid/contexts/enumerable"
5
+ require "humanoid/contexts/mongo"
6
+
7
+ module Humanoid
8
+ module Contexts
9
+ # Determines the context to be used for this criteria. If the class is an
10
+ # embedded document, then the context will be the array in the has_many
11
+ # association it is in. If the class is a root, then the database itself
12
+ # will be the context.
13
+ #
14
+ # Example:
15
+ #
16
+ # <tt>Contexts.context_for(criteria)</tt>
17
+ def self.context_for(criteria)
18
+ if criteria.klass.embedded
19
+ return Contexts::Enumerable.new(criteria)
20
+ end
21
+ Contexts::Mongo.new(criteria)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Enumerable
5
+ include Ids, Paging
6
+ attr_reader :criteria
7
+
8
+ delegate :first, :last, :to => :execute
9
+ delegate :documents, :options, :selector, :to => :criteria
10
+
11
+ # Return aggregation counts of the grouped documents. This will count by
12
+ # the first field provided in the fields array.
13
+ #
14
+ # Returns:
15
+ #
16
+ # A +Hash+ with field values as keys, count as values
17
+ def aggregate
18
+ counts = {}
19
+ group.each_pair { |key, value| counts[key] = value.size }
20
+ counts
21
+ end
22
+
23
+ # Gets the number of documents in the array. Delegates to size.
24
+ def count
25
+ @count ||= documents.size
26
+ end
27
+
28
+ # Groups the documents by the first field supplied in the field options.
29
+ #
30
+ # Returns:
31
+ #
32
+ # A +Hash+ with field values as keys, arrays of documents as values.
33
+ def group
34
+ field = options[:fields].first
35
+ documents.group_by { |doc| doc.send(field) }
36
+ end
37
+
38
+ # Enumerable implementation of execute. Returns matching documents for
39
+ # the selector, and adds options if supplied.
40
+ #
41
+ # Returns:
42
+ #
43
+ # An +Array+ of documents that matched the selector.
44
+ def execute(paginating = false)
45
+ limit(documents.select { |document| document.matches?(selector) })
46
+ end
47
+
48
+ # Create the new enumerable context. This will need the selector and
49
+ # options from a +Criteria+ and a documents array that is the underlying
50
+ # array of embedded documents from a has many association.
51
+ #
52
+ # Example:
53
+ #
54
+ # <tt>Humanoid::Contexts::Enumerable.new(criteria)</tt>
55
+ def initialize(criteria)
56
+ @criteria = criteria
57
+ end
58
+
59
+ # Get the largest value for the field in all the documents.
60
+ #
61
+ # Returns:
62
+ #
63
+ # The numerical largest value.
64
+ def max(field)
65
+ determine(field, :>=)
66
+ end
67
+
68
+ # Get the smallest value for the field in all the documents.
69
+ #
70
+ # Returns:
71
+ #
72
+ # The numerical smallest value.
73
+ def min(field)
74
+ determine(field, :<=)
75
+ end
76
+
77
+ # Get one document.
78
+ #
79
+ # Returns:
80
+ #
81
+ # The first document in the +Array+
82
+ alias :one :first
83
+
84
+ # Get the sum of the field values for all the documents.
85
+ #
86
+ # Returns:
87
+ #
88
+ # The numerical sum of all the document field values.
89
+ def sum(field)
90
+ sum = documents.inject(nil) do |memo, doc|
91
+ value = doc.send(field)
92
+ memo ? memo += value : value
93
+ end
94
+ end
95
+
96
+ protected
97
+ # If the field exists, perform the comparison and set if true.
98
+ def determine(field, operator)
99
+ matching = documents.inject(nil) do |memo, doc|
100
+ value = doc.send(field)
101
+ (memo && memo.send(operator, value)) ? memo : value
102
+ end
103
+ end
104
+
105
+ # Limits the result set if skip and limit options.
106
+ def limit(documents)
107
+ skip, limit = options[:skip], options[:limit]
108
+ if skip && limit
109
+ return documents.slice(skip, limit)
110
+ elsif limit
111
+ return documents.first(limit)
112
+ end
113
+ documents
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ module Ids
5
+ # Return documents based on an id search. Will handle if a single id has
6
+ # been passed or mulitple ids.
7
+ #
8
+ # Example:
9
+ #
10
+ # context.id_criteria([1, 2, 3])
11
+ #
12
+ # Returns:
13
+ #
14
+ # The single or multiple documents.
15
+ def id_criteria(params)
16
+ criteria.id(params)
17
+ result = params.is_a?(Array) ? criteria.entries : one
18
+ if Humanoid.raise_not_found_error
19
+ raise Errors::DocumentNotFound.new(klass, params) if result.blank?
20
+ end
21
+ return result
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,224 @@
1
+ # encoding: utf-8
2
+ module Humanoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Mongo
5
+ include Ids, Paging
6
+ attr_reader :criteria
7
+
8
+ delegate :klass, :options, :selector, :to => :criteria
9
+
10
+ AGGREGATE_REDUCE = "function(obj, prev) { prev.count++; }"
11
+ # Aggregate the context. This will take the internally built selector and options
12
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
13
+ # collection itself will be retrieved from the class provided, and once the
14
+ # query has returned it will provided a grouping of keys with counts.
15
+ #
16
+ # Example:
17
+ #
18
+ # <tt>context.aggregate</tt>
19
+ #
20
+ # Returns:
21
+ #
22
+ # A +Hash+ with field values as keys, counts as values
23
+ def aggregate
24
+ klass.collection.group(options[:fields], selector, { :count => 0 }, AGGREGATE_REDUCE, true)
25
+ end
26
+
27
+ # Get the count of matching documents in the database for the context.
28
+ #
29
+ # Example:
30
+ #
31
+ # <tt>context.count</tt>
32
+ #
33
+ # Returns:
34
+ #
35
+ # An +Integer+ count of documents.
36
+ def count
37
+ @count ||= klass.collection.find(selector, process_options).count
38
+ end
39
+
40
+ # Execute the context. This will take the selector and options
41
+ # and pass them on to the Ruby driver's +find()+ method on the collection. The
42
+ # collection itself will be retrieved from the class provided, and once the
43
+ # query has returned new documents of the type of class provided will be instantiated.
44
+ #
45
+ # Example:
46
+ #
47
+ # <tt>mongo.execute</tt>
48
+ #
49
+ # Returns:
50
+ #
51
+ # An enumerable +Cursor+.
52
+ def execute(paginating = false)
53
+ cursor = klass.collection.find(selector, process_options)
54
+ if cursor
55
+ @count = cursor.count if paginating
56
+ cursor
57
+ else
58
+ []
59
+ end
60
+ end
61
+
62
+ GROUP_REDUCE = "function(obj, prev) { prev.group.push(obj); }"
63
+ # Groups the context. This will take the internally built selector and options
64
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
65
+ # collection itself will be retrieved from the class provided, and once the
66
+ # query has returned it will provided a grouping of keys with objects.
67
+ #
68
+ # Example:
69
+ #
70
+ # <tt>context.group</tt>
71
+ #
72
+ # Returns:
73
+ #
74
+ # A +Hash+ with field values as keys, arrays of documents as values.
75
+ def group
76
+ klass.collection.group(
77
+ options[:fields],
78
+ selector,
79
+ { :group => [] },
80
+ GROUP_REDUCE,
81
+ true
82
+ ).collect do |docs|
83
+ docs["group"] = docs["group"].collect do |attrs|
84
+ Humanoid::Factory.build(klass, attrs)
85
+ end
86
+ docs
87
+ end
88
+ end
89
+
90
+ # Create the new mongo context. This will execute the queries given the
91
+ # selector and options against the database.
92
+ #
93
+ # Example:
94
+ #
95
+ # <tt>Humanoid::Contexts::Mongo.new(criteria)</tt>
96
+ def initialize(criteria)
97
+ @criteria = criteria
98
+ if criteria.klass.hereditary
99
+ criteria.in(:_type => criteria.klass._types)
100
+ end
101
+ end
102
+
103
+ # Return the last result for the +Context+. Essentially does a find_one on
104
+ # the collection with the sorting reversed. If no sorting parameters have
105
+ # been provided it will default to ids.
106
+ #
107
+ # Example:
108
+ #
109
+ # <tt>context.last</tt>
110
+ #
111
+ # Returns:
112
+ #
113
+ # The last document in the collection.
114
+ def last
115
+ opts = process_options
116
+ sorting = opts[:sort]
117
+ sorting = [[:_id, :asc]] unless sorting
118
+ opts[:sort] = sorting.collect { |option| [ option[0], option[1].invert ] }
119
+ attributes = klass.collection.find_one(selector, opts)
120
+ attributes ? Humanoid::Factory.build(klass, attributes) : nil
121
+ end
122
+
123
+ MAX_REDUCE = "function(obj, prev) { if (prev.max == 'start') { prev.max = obj.[field]; } " +
124
+ "if (prev.max < obj.[field]) { prev.max = obj.[field]; } }"
125
+ # Return the max value for a field.
126
+ #
127
+ # This will take the internally built selector and options
128
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
129
+ # collection itself will be retrieved from the class provided, and once the
130
+ # query has returned it will provided a grouping of keys with sums.
131
+ #
132
+ # Example:
133
+ #
134
+ # <tt>context.max(:age)</tt>
135
+ #
136
+ # Returns:
137
+ #
138
+ # A numeric max value.
139
+ def max(field)
140
+ grouped(:max, field.to_s, MAX_REDUCE)
141
+ end
142
+
143
+ MIN_REDUCE = "function(obj, prev) { if (prev.min == 'start') { prev.min = obj.[field]; } " +
144
+ "if (prev.min > obj.[field]) { prev.min = obj.[field]; } }"
145
+ # Return the min value for a field.
146
+ #
147
+ # This will take the internally built selector and options
148
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
149
+ # collection itself will be retrieved from the class provided, and once the
150
+ # query has returned it will provided a grouping of keys with sums.
151
+ #
152
+ # Example:
153
+ #
154
+ # <tt>context.min(:age)</tt>
155
+ #
156
+ # Returns:
157
+ #
158
+ # A numeric minimum value.
159
+ def min(field)
160
+ grouped(:min, field.to_s, MIN_REDUCE)
161
+ end
162
+
163
+ # Return the first result for the +Context+.
164
+ #
165
+ # Example:
166
+ #
167
+ # <tt>context.one</tt>
168
+ #
169
+ # Return:
170
+ #
171
+ # The first document in the collection.
172
+ def one
173
+ attributes = klass.collection.find_one(selector, process_options)
174
+ attributes ? Humanoid::Factory.build(klass, attributes) : nil
175
+ end
176
+
177
+ alias :first :one
178
+
179
+ SUM_REDUCE = "function(obj, prev) { if (prev.sum == 'start') { prev.sum = 0; } prev.sum += obj.[field]; }"
180
+ # Sum the context.
181
+ #
182
+ # This will take the internally built selector and options
183
+ # and pass them on to the Ruby driver's +group()+ method on the collection. The
184
+ # collection itself will be retrieved from the class provided, and once the
185
+ # query has returned it will provided a grouping of keys with sums.
186
+ #
187
+ # Example:
188
+ #
189
+ # <tt>context.sum(:age)</tt>
190
+ #
191
+ # Returns:
192
+ #
193
+ # A numeric value that is the sum.
194
+ def sum(field)
195
+ grouped(:sum, field.to_s, SUM_REDUCE)
196
+ end
197
+
198
+ # Common functionality for grouping operations. Currently used by min, max
199
+ # and sum. Will gsub the field name in the supplied reduce function.
200
+ def grouped(start, field, reduce)
201
+ collection = klass.collection.group(
202
+ nil,
203
+ selector,
204
+ { start => "start" },
205
+ reduce.gsub("[field]", field),
206
+ true
207
+ )
208
+ collection.empty? ? nil : collection.first[start.to_s]
209
+ end
210
+
211
+ # Filters the field list. If no fields have been supplied, then it will be
212
+ # empty. If fields have been defined then _type will be included as well.
213
+ def process_options
214
+ fields = options[:fields]
215
+ if fields && fields.size > 0 && !fields.include?(:_type)
216
+ fields << :_type
217
+ options[:fields] = fields
218
+ end
219
+ options.dup
220
+ end
221
+
222
+ end
223
+ end
224
+ end