mongo_doc_rails2 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. data/.document +5 -0
  2. data/.gitignore +8 -0
  3. data/HISTORY.md +11 -0
  4. data/LICENSE +20 -0
  5. data/README.textile +185 -0
  6. data/Rakefile +188 -0
  7. data/TODO +40 -0
  8. data/VERSION +1 -0
  9. data/data/.gitignore +2 -0
  10. data/examples/simple_document.rb +46 -0
  11. data/examples/simple_object.rb +34 -0
  12. data/features/collections.feature +9 -0
  13. data/features/embed_hash.feature +16 -0
  14. data/features/finders.feature +76 -0
  15. data/features/indexes.feature +28 -0
  16. data/features/mongodb.yml +7 -0
  17. data/features/mongodoc_base.feature +128 -0
  18. data/features/new_record.feature +36 -0
  19. data/features/partial_updates.feature +95 -0
  20. data/features/removing_documents.feature +68 -0
  21. data/features/saving_an_object.feature +15 -0
  22. data/features/scopes.feature +66 -0
  23. data/features/step_definitions/collection_steps.rb +17 -0
  24. data/features/step_definitions/document_steps.rb +149 -0
  25. data/features/step_definitions/documents.rb +40 -0
  26. data/features/step_definitions/embed_hash_steps.rb +6 -0
  27. data/features/step_definitions/finder_steps.rb +15 -0
  28. data/features/step_definitions/index_steps.rb +10 -0
  29. data/features/step_definitions/json_steps.rb +9 -0
  30. data/features/step_definitions/object_steps.rb +50 -0
  31. data/features/step_definitions/objects.rb +24 -0
  32. data/features/step_definitions/partial_update_steps.rb +31 -0
  33. data/features/step_definitions/query_steps.rb +66 -0
  34. data/features/step_definitions/removing_documents_steps.rb +14 -0
  35. data/features/step_definitions/scope_steps.rb +18 -0
  36. data/features/step_definitions/string_casting_steps.rb +29 -0
  37. data/features/step_definitions/util_steps.rb +7 -0
  38. data/features/string_casting.feature +10 -0
  39. data/features/support/support.rb +10 -0
  40. data/features/using_criteria.feature +142 -0
  41. data/lib/mongo_doc.rb +12 -0
  42. data/lib/mongo_doc/associations.rb +109 -0
  43. data/lib/mongo_doc/associations/collection_proxy.rb +121 -0
  44. data/lib/mongo_doc/associations/document_proxy.rb +65 -0
  45. data/lib/mongo_doc/associations/hash_proxy.rb +102 -0
  46. data/lib/mongo_doc/associations/proxy_base.rb +48 -0
  47. data/lib/mongo_doc/attributes.rb +84 -0
  48. data/lib/mongo_doc/bson.rb +31 -0
  49. data/lib/mongo_doc/collection.rb +82 -0
  50. data/lib/mongo_doc/connection.rb +88 -0
  51. data/lib/mongo_doc/contexts.rb +31 -0
  52. data/lib/mongo_doc/contexts/ids.rb +41 -0
  53. data/lib/mongo_doc/contexts/mongo.rb +272 -0
  54. data/lib/mongo_doc/criteria.rb +70 -0
  55. data/lib/mongo_doc/cursor.rb +32 -0
  56. data/lib/mongo_doc/document.rb +205 -0
  57. data/lib/mongo_doc/ext.rb +16 -0
  58. data/lib/mongo_doc/ext/array.rb +5 -0
  59. data/lib/mongo_doc/ext/binary.rb +7 -0
  60. data/lib/mongo_doc/ext/boolean_class.rb +17 -0
  61. data/lib/mongo_doc/ext/date.rb +19 -0
  62. data/lib/mongo_doc/ext/date_time.rb +17 -0
  63. data/lib/mongo_doc/ext/dbref.rb +7 -0
  64. data/lib/mongo_doc/ext/hash.rb +7 -0
  65. data/lib/mongo_doc/ext/min_max_keys.rb +13 -0
  66. data/lib/mongo_doc/ext/nil_class.rb +5 -0
  67. data/lib/mongo_doc/ext/numeric.rb +17 -0
  68. data/lib/mongo_doc/ext/object.rb +19 -0
  69. data/lib/mongo_doc/ext/object_id.rb +7 -0
  70. data/lib/mongo_doc/ext/regexp.rb +5 -0
  71. data/lib/mongo_doc/ext/string.rb +5 -0
  72. data/lib/mongo_doc/ext/symbol.rb +5 -0
  73. data/lib/mongo_doc/ext/time.rb +9 -0
  74. data/lib/mongo_doc/finders.rb +38 -0
  75. data/lib/mongo_doc/index.rb +46 -0
  76. data/lib/mongo_doc/matchers.rb +35 -0
  77. data/lib/mongo_doc/root.rb +26 -0
  78. data/lib/mongo_doc/scope.rb +64 -0
  79. data/lib/mongo_doc/validations.rb +12 -0
  80. data/lib/mongo_doc/validations/macros.rb +11 -0
  81. data/lib/mongo_doc/validations/validates_embedded.rb +13 -0
  82. data/lib/mongoid/contexts/enumerable.rb +151 -0
  83. data/lib/mongoid/contexts/paging.rb +42 -0
  84. data/lib/mongoid/criteria.rb +239 -0
  85. data/lib/mongoid/criterion/complex.rb +21 -0
  86. data/lib/mongoid/criterion/exclusion.rb +65 -0
  87. data/lib/mongoid/criterion/inclusion.rb +93 -0
  88. data/lib/mongoid/criterion/optional.rb +136 -0
  89. data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
  90. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  91. data/lib/mongoid/matchers/all.rb +11 -0
  92. data/lib/mongoid/matchers/default.rb +26 -0
  93. data/lib/mongoid/matchers/exists.rb +13 -0
  94. data/lib/mongoid/matchers/gt.rb +11 -0
  95. data/lib/mongoid/matchers/gte.rb +11 -0
  96. data/lib/mongoid/matchers/in.rb +11 -0
  97. data/lib/mongoid/matchers/lt.rb +11 -0
  98. data/lib/mongoid/matchers/lte.rb +11 -0
  99. data/lib/mongoid/matchers/ne.rb +11 -0
  100. data/lib/mongoid/matchers/nin.rb +11 -0
  101. data/lib/mongoid/matchers/size.rb +11 -0
  102. data/mongo_doc_rails2.gemspec +237 -0
  103. data/mongod.example.yml +2 -0
  104. data/mongodb.example.yml +14 -0
  105. data/perf/mongo_doc_object.rb +83 -0
  106. data/perf/mongo_document.rb +84 -0
  107. data/perf/ruby_driver.rb +49 -0
  108. data/script/console +8 -0
  109. data/spec/array_including_argument_matcher.rb +62 -0
  110. data/spec/associations/collection_proxy_spec.rb +233 -0
  111. data/spec/associations/document_proxy_spec.rb +45 -0
  112. data/spec/associations/hash_proxy_spec.rb +181 -0
  113. data/spec/associations/proxy_base_spec.rb +92 -0
  114. data/spec/associations_spec.rb +218 -0
  115. data/spec/attributes_accessor_spec.rb +33 -0
  116. data/spec/attributes_spec.rb +145 -0
  117. data/spec/bson_matchers.rb +54 -0
  118. data/spec/bson_spec.rb +196 -0
  119. data/spec/collection_spec.rb +169 -0
  120. data/spec/connection_spec.rb +147 -0
  121. data/spec/contexts/ids_spec.rb +49 -0
  122. data/spec/contexts/mongo_spec.rb +235 -0
  123. data/spec/contexts_spec.rb +56 -0
  124. data/spec/criteria_spec.rb +69 -0
  125. data/spec/cursor_spec.rb +91 -0
  126. data/spec/document_ext.rb +9 -0
  127. data/spec/document_spec.rb +553 -0
  128. data/spec/embedded_save_spec.rb +73 -0
  129. data/spec/ext_spec.rb +89 -0
  130. data/spec/finders_spec.rb +61 -0
  131. data/spec/hash_matchers.rb +27 -0
  132. data/spec/index_spec.rb +79 -0
  133. data/spec/matchers_spec.rb +342 -0
  134. data/spec/mongodb.yml +6 -0
  135. data/spec/mongodb_pairs.yml +8 -0
  136. data/spec/new_record_spec.rb +128 -0
  137. data/spec/root_spec.rb +41 -0
  138. data/spec/scope_spec.rb +79 -0
  139. data/spec/spec.opts +2 -0
  140. data/spec/spec_helper.rb +14 -0
  141. data/spec/validations_spec.rb +30 -0
  142. metadata +346 -0
@@ -0,0 +1,19 @@
1
+ class Object
2
+ define_method :singleton_class, instance_method(:metaclass) unless respond_to?(:singleton_class)
3
+
4
+ def to_bson(*args)
5
+ {MongoDoc::BSON::CLASS_KEY => self.class.name}.tap do |bson_hash|
6
+ instance_variables.each do |name|
7
+ bson_hash[name[1..-1]] = instance_variable_get(name).to_bson(args)
8
+ end
9
+ end
10
+ end
11
+
12
+ def self.bson_create(bson_hash, options = {})
13
+ allocate.tap do |obj|
14
+ bson_hash.each do |name, value|
15
+ obj.instance_variable_set("@#{name}", MongoDoc::BSON.decode(value, options))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ module BSON
2
+ class ObjectID
3
+ def to_bson(*args)
4
+ self
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class Regexp
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class Symbol
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class Time
2
+ def to_bson(*args)
3
+ self
4
+ end
5
+
6
+ def self.cast_from_string(string)
7
+ Time.parse(string) unless string.blank?
8
+ end
9
+ end
@@ -0,0 +1,38 @@
1
+ require 'mongo_doc/criteria'
2
+
3
+ module MongoDoc
4
+ module Finders
5
+ def self.extended(base)
6
+ base.extend(Criteria) unless base === Criteria
7
+ end
8
+
9
+ # Find a +Document+ based on id (+String+ or +BSON::ObjectID+)
10
+ #
11
+ # <tt>Person.find('1')</tt>
12
+ # <tt>Person.find(obj_id_1, obj_id_2)</tt>
13
+ def find(*args)
14
+ criteria.id(*args)
15
+ end
16
+ #
17
+ # Find all +Document+s in the collections
18
+ #
19
+ # <tt>Person.find_all</tt>
20
+ def find_all
21
+ criteria
22
+ end
23
+
24
+ # Find a +Document+ based on id (+String+ or +BSON::ObjectID+)
25
+ # or conditions
26
+ #
27
+ # <tt>Person.find_one('1')</tt>
28
+ # <tt>Person.find_one(:where => {:age.gt > 25})</tt>
29
+ def find_one(conditions_or_id)
30
+ if Hash === conditions_or_id
31
+ Mongoid::Criteria.translate(self, conditions_or_id).one
32
+ else
33
+ Mongoid::Criteria.translate(self, conditions_or_id)
34
+ end
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,46 @@
1
+ module MongoDoc
2
+ module Index
3
+
4
+ DIRECTION = { :asc => Mongo::ASCENDING,
5
+ :desc => Mongo::DESCENDING,
6
+ :geo2d => Mongo::GEO2D }
7
+ OPTIONS = [:min, :max, :background, :unique, :dropDups]
8
+
9
+ # Create an index on a collection.
10
+ #
11
+ # For compound indexes, pass pairs of fields and
12
+ # directions (+:asc+, +:desc+) as a hash.
13
+ #
14
+ # For a unique index, pass the option +:unique => true+.
15
+ # To create the index in the background, pass the options +:background => true+.
16
+ # If you want to remove duplicates from existing records when creating the
17
+ # unique index, pass the option +:dropDups => true+
18
+ #
19
+ # For GeoIndexing, specify the minimum and maximum longitude and latitude
20
+ # values with the +:min+ and +:max+ options.
21
+ #
22
+ # <tt>Person.index(:last_name)</tt>
23
+ # <tt>Person.index(:ssn, :unique => true)</tt>
24
+ # <tt>Person.index(:first_name => :asc, :last_name => :asc)</tt>
25
+ # <tt>Person.index(:first_name => :asc, :last_name => :asc, :unique => true)</tt>
26
+ def index(*args)
27
+ options_and_fields = args.extract_options!
28
+ if args.any?
29
+ collection.create_index(args.first, options_and_fields)
30
+ else
31
+ fields = options_and_fields.except(*OPTIONS)
32
+ options = options_and_fields.slice(*OPTIONS)
33
+ collection.create_index(to_mongo_direction(fields), options)
34
+ end
35
+ end
36
+
37
+ protected
38
+ def to_mongo_direction(fields_hash)
39
+ fields_hash.to_a.map {|field| [field.first, direction(field.last)]}
40
+ end
41
+
42
+ def direction(dir)
43
+ DIRECTION[dir] || Mongo::ASCENDING
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ require "mongoid/matchers/default"
2
+ require "mongoid/matchers/all"
3
+ require "mongoid/matchers/exists"
4
+ require "mongoid/matchers/gt"
5
+ require "mongoid/matchers/gte"
6
+ require "mongoid/matchers/in"
7
+ require "mongoid/matchers/lt"
8
+ require "mongoid/matchers/lte"
9
+ require "mongoid/matchers/ne"
10
+ require "mongoid/matchers/nin"
11
+ require "mongoid/matchers/size"
12
+
13
+ module MongoDoc #:nodoc:
14
+ module Matchers
15
+ # Determines if this document has the attributes to match the supplied
16
+ # MongoDB selector. Used for matching on embedded associations.
17
+ def matches?(selector)
18
+ selector.each_pair do |key, value|
19
+ return false unless matcher(key, value).matches?(value)
20
+ end
21
+ true
22
+ end
23
+
24
+ protected
25
+ # Get the matcher for the supplied key and value. Will determine the class
26
+ # name from the key.
27
+ def matcher(key, value)
28
+ if value.is_a?(Hash)
29
+ name = "Mongoid::Matchers::#{value.keys.first.gsub("$", "").camelize}"
30
+ return name.constantize.new(send(key))
31
+ end
32
+ Mongoid::Matchers::Default.new(send(key))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,26 @@
1
+ module MongoDoc
2
+ module Root
3
+
4
+ attr_reader :_root
5
+
6
+ %w(_modifier_path _selector_path).each do |getter|
7
+ module_eval(<<-RUBY, __FILE__, __LINE__)
8
+ def #{getter}
9
+ @#{getter} ||= ''
10
+ end
11
+ RUBY
12
+ end
13
+
14
+ %w(_modifier_path _root _selector_path).each do |setter|
15
+ module_eval(<<-RUBY, __FILE__, __LINE__)
16
+ def #{setter}=(value)
17
+ @#{setter} = value
18
+ _associations.each do|a|
19
+ association = send(a)
20
+ association.#{setter} = value if association
21
+ end
22
+ end
23
+ RUBY
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,64 @@
1
+ # Based on ActiveRecord::NamedScope
2
+ module MongoDoc
3
+ module Scope
4
+ def scopes
5
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
6
+ end
7
+
8
+ def scope(name, *args, &block)
9
+ options = args.extract_options!
10
+ raise ArgumentError if args.size != 1
11
+ criteria = args.first
12
+ name = name.to_sym
13
+ scopes[name] = lambda do |parent_scope, *args|
14
+ CriteriaProxy.new(parent_scope, Mongoid::Criteria === criteria ? criteria : criteria.call(*args), options, &block)
15
+ end
16
+ (class << self; self; end).class_eval <<-EOT
17
+ def #{name}(*args)
18
+ scopes[:#{name}].call(self, *args)
19
+ end
20
+ EOT
21
+ end
22
+
23
+ class CriteriaProxy
24
+ attr_accessor :criteria, :klass, :parent_scope
25
+
26
+ delegate :scopes, :to => :parent_scope
27
+
28
+ def initialize(parent_scope, criteria, options, &block)
29
+ [options.delete(:extend)].flatten.each { |extension| extend extension } if options.include?(:extend)
30
+ extend Module.new(&block) if block_given?
31
+ if CriteriaProxy === parent_scope
32
+ chained = Mongoid::Criteria.new(klass)
33
+ chained.merge(parent_scope)
34
+ chained.merge(criteria)
35
+ self.criteria = chained
36
+ self.klass = criteria.klass
37
+ else
38
+ self.criteria = criteria
39
+ self.klass = parent_scope
40
+ end
41
+
42
+ self.parent_scope = parent_scope
43
+ end
44
+
45
+ def respond_to?(method, include_private = false)
46
+ return true if scopes.include?(method)
47
+ criteria.respond_to?(method, include_private)
48
+ end
49
+
50
+ private
51
+
52
+ def method_missing(method, *args, &block)
53
+ if scopes.include?(method)
54
+ scopes[method].call(self, *args)
55
+ else
56
+ chained = Mongoid::Criteria.new(klass)
57
+ chained.merge(criteria)
58
+ chained.send(method, *args, &block)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,12 @@
1
+ require 'mongo_doc/validations/macros'
2
+
3
+ module MongoDoc
4
+ module Validations
5
+ def self.included(klass)
6
+ klass.class_eval do
7
+ include ::Validatable
8
+ extend Macros
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ require 'mongo_doc/validations/validates_embedded'
2
+
3
+ module MongoDoc
4
+ module Validations
5
+ module Macros
6
+ def validates_embedded(*args)
7
+ add_validations(args, MongoDoc::Validations::ValidatesEmbedded)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module MongoDoc
2
+ module Validations
3
+ class ValidatesEmbedded < ::Validatable::ValidationBase
4
+ def valid?(instance)
5
+ instance.send(attribute).valid?
6
+ end
7
+
8
+ def message(instance)
9
+ super || "is invalid"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+ module Contexts #:nodoc:
4
+ class Enumerable
5
+ include Ids, Paging
6
+ attr_reader :criteria
7
+
8
+ delegate :blank?, :empty?, :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
+ # Get the average value for the supplied field.
24
+ #
25
+ # Example:
26
+ #
27
+ # <tt>context.avg(:age)</tt>
28
+ #
29
+ # Returns:
30
+ #
31
+ # A numeric value that is the average.
32
+ def avg(field)
33
+ total = sum(field)
34
+ total ? (total.to_f / count) : nil
35
+ end
36
+
37
+ # Gets the number of documents in the array. Delegates to size.
38
+ def count
39
+ @count ||= documents.size
40
+ end
41
+
42
+ # Gets an array of distinct values for the supplied field across the
43
+ # entire array or the susbset given the criteria.
44
+ #
45
+ # Example:
46
+ #
47
+ # <tt>context.distinct(:title)</tt>
48
+ def distinct(field)
49
+ execute.collect { |doc| doc.send(field) }.uniq
50
+ end
51
+
52
+ # Enumerable implementation of execute. Returns matching documents for
53
+ # the selector, and adds options if supplied.
54
+ #
55
+ # Returns:
56
+ #
57
+ # An +Array+ of documents that matched the selector.
58
+ def execute(paginating = false)
59
+ limit(documents.select { |document| document.matches?(selector) })
60
+ end
61
+
62
+ # Groups the documents by the first field supplied in the field options.
63
+ #
64
+ # Returns:
65
+ #
66
+ # A +Hash+ with field values as keys, arrays of documents as values.
67
+ def group
68
+ field = options[:fields].first
69
+ documents.group_by { |doc| doc.send(field) }
70
+ end
71
+
72
+ # Create the new enumerable context. This will need the selector and
73
+ # options from a +Criteria+ and a documents array that is the underlying
74
+ # array of embedded documents from a has many association.
75
+ #
76
+ # Example:
77
+ #
78
+ # <tt>Mongoid::Contexts::Enumerable.new(criteria)</tt>
79
+ def initialize(criteria)
80
+ @criteria = criteria
81
+ end
82
+
83
+ # Iterate over each +Document+ in the results. This can take an optional
84
+ # block to pass to each argument in the results.
85
+ #
86
+ # Example:
87
+ #
88
+ # <tt>context.iterate { |doc| p doc }</tt>
89
+ def iterate(&block)
90
+ execute.each(&block)
91
+ end
92
+
93
+ # Get the largest value for the field in all the documents.
94
+ #
95
+ # Returns:
96
+ #
97
+ # The numerical largest value.
98
+ def max(field)
99
+ determine(field, :>=)
100
+ end
101
+
102
+ # Get the smallest value for the field in all the documents.
103
+ #
104
+ # Returns:
105
+ #
106
+ # The numerical smallest value.
107
+ def min(field)
108
+ determine(field, :<=)
109
+ end
110
+
111
+ # Get one document.
112
+ #
113
+ # Returns:
114
+ #
115
+ # The first document in the +Array+
116
+ alias :one :first
117
+
118
+ # Get the sum of the field values for all the documents.
119
+ #
120
+ # Returns:
121
+ #
122
+ # The numerical sum of all the document field values.
123
+ def sum(field)
124
+ sum = documents.inject(nil) do |memo, doc|
125
+ value = doc.send(field)
126
+ memo ? memo += value : value
127
+ end
128
+ end
129
+
130
+ protected
131
+ # If the field exists, perform the comparison and set if true.
132
+ def determine(field, operator)
133
+ matching = documents.inject(nil) do |memo, doc|
134
+ value = doc.send(field)
135
+ (memo && memo.send(operator, value)) ? memo : value
136
+ end
137
+ end
138
+
139
+ # Limits the result set if skip and limit options.
140
+ def limit(documents)
141
+ skip, limit = options[:skip], options[:limit]
142
+ if skip && limit
143
+ return documents.slice(skip, limit)
144
+ elsif limit
145
+ return documents.first(limit)
146
+ end
147
+ documents
148
+ end
149
+ end
150
+ end
151
+ end