mongodoc 0.2.2 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +21 -0
- data/TODO +6 -1
- data/VERSION +1 -1
- data/features/finders.feature +1 -1
- data/features/mongodoc_base.feature +11 -2
- data/features/{named_scopes.feature → scopes.feature} +0 -0
- data/features/step_definitions/document_steps.rb +15 -4
- data/features/step_definitions/documents.rb +3 -3
- data/features/step_definitions/query_steps.rb +17 -14
- data/features/step_definitions/{named_scope_steps.rb → scope_steps.rb} +0 -0
- data/features/using_criteria.feature +22 -43
- data/lib/mongodoc.rb +1 -1
- data/lib/mongodoc/associations/collection_proxy.rb +3 -1
- data/lib/mongodoc/associations/document_proxy.rb +4 -1
- data/lib/mongodoc/associations/hash_proxy.rb +3 -1
- data/lib/mongodoc/associations/proxy_base.rb +6 -4
- data/lib/mongodoc/attributes.rb +6 -6
- data/lib/mongodoc/contexts.rb +24 -0
- data/lib/mongodoc/contexts/enumerable.rb +132 -0
- data/lib/mongodoc/contexts/mongo.rb +215 -0
- data/lib/mongodoc/criteria.rb +36 -479
- data/lib/mongodoc/document.rb +3 -2
- data/lib/mongodoc/finders.rb +31 -11
- data/lib/mongodoc/matchers.rb +35 -0
- data/lib/mongodoc/scope.rb +64 -0
- data/lib/mongoid/contexts/paging.rb +42 -0
- data/lib/mongoid/criteria.rb +264 -0
- data/lib/mongoid/criterion/complex.rb +21 -0
- data/lib/mongoid/criterion/exclusion.rb +65 -0
- data/lib/mongoid/criterion/inclusion.rb +92 -0
- data/lib/mongoid/criterion/optional.rb +136 -0
- data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
- data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
- data/lib/mongoid/matchers/all.rb +11 -0
- data/lib/mongoid/matchers/default.rb +26 -0
- data/lib/mongoid/matchers/exists.rb +13 -0
- data/lib/mongoid/matchers/gt.rb +11 -0
- data/lib/mongoid/matchers/gte.rb +11 -0
- data/lib/mongoid/matchers/in.rb +11 -0
- data/lib/mongoid/matchers/lt.rb +11 -0
- data/lib/mongoid/matchers/lte.rb +11 -0
- data/lib/mongoid/matchers/ne.rb +11 -0
- data/lib/mongoid/matchers/nin.rb +11 -0
- data/lib/mongoid/matchers/size.rb +11 -0
- data/mongodoc.gemspec +39 -9
- data/spec/attributes_spec.rb +16 -2
- data/spec/contexts/enumerable_spec.rb +335 -0
- data/spec/contexts/mongo_spec.rb +148 -0
- data/spec/contexts_spec.rb +28 -0
- data/spec/criteria_spec.rb +15 -766
- data/spec/finders_spec.rb +28 -36
- data/spec/matchers_spec.rb +342 -0
- data/spec/scope_spec.rb +79 -0
- metadata +40 -10
- data/features/step_definitions/criteria_steps.rb +0 -42
- data/lib/mongodoc/named_scope.rb +0 -68
- data/spec/named_scope_spec.rb +0 -82
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Criterion #:nodoc:
|
4
|
+
# Complex criterion are used when performing operations on symbols to get
|
5
|
+
# get a shorthand syntax for where clauses.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# <tt>{ :field => { "$lt" => "value" } }</tt>
|
10
|
+
# becomes:
|
11
|
+
# <tt> { :field.lt => "value }</tt>
|
12
|
+
class Complex
|
13
|
+
attr_accessor :key, :operator
|
14
|
+
|
15
|
+
# Create the new complex criterion.
|
16
|
+
def initialize(opts = {})
|
17
|
+
@key, @operator = opts[:key], opts[:operator]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Criterion #:nodoc:
|
4
|
+
module Exclusion
|
5
|
+
# Adds a criterion to the +Criteria+ that specifies values that are not allowed
|
6
|
+
# to match any document in the database. The MongoDB conditional operator that
|
7
|
+
# will be used is "$ne".
|
8
|
+
#
|
9
|
+
# Options:
|
10
|
+
#
|
11
|
+
# attributes: A +Hash+ where the key is the field name and the value is a
|
12
|
+
# value that must not be equal to the corresponding field value in the database.
|
13
|
+
#
|
14
|
+
# Example:
|
15
|
+
#
|
16
|
+
# <tt>criteria.excludes(:field => "value1")</tt>
|
17
|
+
#
|
18
|
+
# <tt>criteria.excludes(:field1 => "value1", :field2 => "value1")</tt>
|
19
|
+
#
|
20
|
+
# Returns: <tt>self</tt>
|
21
|
+
def excludes(attributes = {})
|
22
|
+
mongo_id = attributes.delete(:id)
|
23
|
+
attributes = attributes.merge(:_id => mongo_id) if mongo_id
|
24
|
+
update_selector(attributes, "$ne")
|
25
|
+
end
|
26
|
+
|
27
|
+
# Adds a criterion to the +Criteria+ that specifies values where none
|
28
|
+
# should match in order to return results. This is similar to an SQL "NOT IN"
|
29
|
+
# clause. The MongoDB conditional operator that will be used is "$nin".
|
30
|
+
#
|
31
|
+
# Options:
|
32
|
+
#
|
33
|
+
# exclusions: A +Hash+ where the key is the field name and the value is an
|
34
|
+
# +Array+ of values that none can match.
|
35
|
+
#
|
36
|
+
# Example:
|
37
|
+
#
|
38
|
+
# <tt>criteria.not_in(:field => ["value1", "value2"])</tt>
|
39
|
+
#
|
40
|
+
# <tt>criteria.not_in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
41
|
+
#
|
42
|
+
# Returns: <tt>self</tt>
|
43
|
+
def not_in(exclusions)
|
44
|
+
exclusions.each { |key, value| @selector[key] = { "$nin" => value } }; self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Adds a criterion to the +Criteria+ that specifies the fields that will
|
48
|
+
# get returned from the Document. Used mainly for list views that do not
|
49
|
+
# require all fields to be present. This is similar to SQL "SELECT" values.
|
50
|
+
#
|
51
|
+
# Options:
|
52
|
+
#
|
53
|
+
# args: A list of field names to retrict the returned fields to.
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
#
|
57
|
+
# <tt>criteria.only(:field1, :field2, :field3)</tt>
|
58
|
+
#
|
59
|
+
# Returns: <tt>self</tt>
|
60
|
+
def only(*args)
|
61
|
+
@options[:fields] = args.flatten if args.any?; self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Criterion #:nodoc:
|
4
|
+
module Inclusion
|
5
|
+
# Adds a criterion to the +Criteria+ that specifies values that must all
|
6
|
+
# be matched in order to return results. Similar to an "in" clause but the
|
7
|
+
# underlying conditional logic is an "AND" and not an "OR". The MongoDB
|
8
|
+
# conditional operator that will be used is "$all".
|
9
|
+
#
|
10
|
+
# Options:
|
11
|
+
#
|
12
|
+
# attributes: A +Hash+ where the key is the field name and the value is an
|
13
|
+
# +Array+ of values that must all match.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# <tt>criteria.all(:field => ["value1", "value2"])</tt>
|
18
|
+
#
|
19
|
+
# <tt>criteria.all(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
20
|
+
#
|
21
|
+
# Returns: <tt>self</tt>
|
22
|
+
def all(attributes = {})
|
23
|
+
update_selector(attributes, "$all")
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds a criterion to the +Criteria+ that specifies values that must
|
27
|
+
# be matched in order to return results. This is similar to a SQL "WHERE"
|
28
|
+
# clause. This is the actual selector that will be provided to MongoDB,
|
29
|
+
# similar to the Javascript object that is used when performing a find()
|
30
|
+
# in the MongoDB console.
|
31
|
+
#
|
32
|
+
# Options:
|
33
|
+
#
|
34
|
+
# selectior: A +Hash+ that must match the attributes of the +Document+.
|
35
|
+
#
|
36
|
+
# Example:
|
37
|
+
#
|
38
|
+
# <tt>criteria.and(:field1 => "value1", :field2 => 15)</tt>
|
39
|
+
#
|
40
|
+
# Returns: <tt>self</tt>
|
41
|
+
def and(selector = nil)
|
42
|
+
where(selector)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a criterion to the +Criteria+ that specifies values where any can
|
46
|
+
# be matched in order to return results. This is similar to an SQL "IN"
|
47
|
+
# clause. The MongoDB conditional operator that will be used is "$in".
|
48
|
+
#
|
49
|
+
# Options:
|
50
|
+
#
|
51
|
+
# attributes: A +Hash+ where the key is the field name and the value is an
|
52
|
+
# +Array+ of values that any can match.
|
53
|
+
#
|
54
|
+
# Example:
|
55
|
+
#
|
56
|
+
# <tt>criteria.in(:field => ["value1", "value2"])</tt>
|
57
|
+
#
|
58
|
+
# <tt>criteria.in(:field1 => ["value1", "value2"], :field2 => ["value1"])</tt>
|
59
|
+
#
|
60
|
+
# Returns: <tt>self</tt>
|
61
|
+
def in(attributes = {})
|
62
|
+
update_selector(attributes, "$in")
|
63
|
+
end
|
64
|
+
alias any_in in
|
65
|
+
|
66
|
+
# Adds a criterion to the +Criteria+ that specifies values that must
|
67
|
+
# be matched in order to return results. This is similar to a SQL "WHERE"
|
68
|
+
# clause. This is the actual selector that will be provided to MongoDB,
|
69
|
+
# similar to the Javascript object that is used when performing a find()
|
70
|
+
# in the MongoDB console.
|
71
|
+
#
|
72
|
+
# Options:
|
73
|
+
#
|
74
|
+
# selectior: A +Hash+ that must match the attributes of the +Document+.
|
75
|
+
#
|
76
|
+
# Example:
|
77
|
+
#
|
78
|
+
# <tt>criteria.where(:field1 => "value1", :field2 => 15)</tt>
|
79
|
+
#
|
80
|
+
# Returns: <tt>self</tt>
|
81
|
+
def where(selector = nil)
|
82
|
+
case selector
|
83
|
+
when String
|
84
|
+
@selector.update("$where" => selector)
|
85
|
+
else
|
86
|
+
@selector.update(selector ? selector.expand_complex_criteria : {})
|
87
|
+
end
|
88
|
+
self
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Criterion #:nodoc:
|
4
|
+
module Optional
|
5
|
+
# Tells the criteria that the cursor that gets returned needs to be
|
6
|
+
# cached. This is so multiple iterations don't hit the database multiple
|
7
|
+
# times, however this is not advisable when working with large data sets
|
8
|
+
# as the entire results will get stored in memory.
|
9
|
+
#
|
10
|
+
# Example:
|
11
|
+
#
|
12
|
+
# <tt>criteria.cache</tt>
|
13
|
+
def cache
|
14
|
+
@options.merge!(:cache => true); self
|
15
|
+
end
|
16
|
+
|
17
|
+
# Will return true if the cache option has been set.
|
18
|
+
#
|
19
|
+
# Example:
|
20
|
+
#
|
21
|
+
# <tt>criteria.cached?</tt>
|
22
|
+
def cached?
|
23
|
+
@options[:cache] == true
|
24
|
+
end
|
25
|
+
|
26
|
+
# Flags the criteria to execute against a read-only slave in the pool
|
27
|
+
# instead of master.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
#
|
31
|
+
# <tt>criteria.enslave</tt>
|
32
|
+
def enslave
|
33
|
+
@options.merge!(:enslave => true); self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Will return true if the criteria is enslaved.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
#
|
40
|
+
# <tt>criteria.enslaved?</tt>
|
41
|
+
def enslaved?
|
42
|
+
@options[:enslave] == true
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds a criterion to the +Criteria+ that specifies additional options
|
46
|
+
# to be passed to the Ruby driver, in the exact format for the driver.
|
47
|
+
#
|
48
|
+
# Options:
|
49
|
+
#
|
50
|
+
# extras: A +Hash+ that gets set to the driver options.
|
51
|
+
#
|
52
|
+
# Example:
|
53
|
+
#
|
54
|
+
# <tt>criteria.extras(:limit => 20, :skip => 40)</tt>
|
55
|
+
#
|
56
|
+
# Returns: <tt>self</tt>
|
57
|
+
def extras(extras)
|
58
|
+
@options.merge!(extras); filter_options; self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Adds a criterion to the +Criteria+ that specifies an id that must be matched.
|
62
|
+
#
|
63
|
+
# Options:
|
64
|
+
#
|
65
|
+
# object_id: A +String+ representation of a <tt>Mongo::ObjectID</tt>
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
#
|
69
|
+
# <tt>criteria.id("4ab2bc4b8ad548971900005c")</tt>
|
70
|
+
#
|
71
|
+
# Returns: <tt>self</tt>
|
72
|
+
def id(*args)
|
73
|
+
(args.flatten.size > 1) ? self.in(:_id => args.flatten) : (@selector[:_id] = args.first)
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Adds a criterion to the +Criteria+ that specifies the maximum number of
|
78
|
+
# results to return. This is mostly used in conjunction with <tt>skip()</tt>
|
79
|
+
# to handle paginated results.
|
80
|
+
#
|
81
|
+
# Options:
|
82
|
+
#
|
83
|
+
# value: An +Integer+ specifying the max number of results. Defaults to 20.
|
84
|
+
#
|
85
|
+
# Example:
|
86
|
+
#
|
87
|
+
# <tt>criteria.limit(100)</tt>
|
88
|
+
#
|
89
|
+
# Returns: <tt>self</tt>
|
90
|
+
def limit(value = 20)
|
91
|
+
@options[:limit] = value; self
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the offset option. If a per_page option is in the list then it
|
95
|
+
# will replace it with a skip parameter and return the same value. Defaults
|
96
|
+
# to 20 if nothing was provided.
|
97
|
+
def offset(*args)
|
98
|
+
args.size > 0 ? skip(args.first) : @options[:skip]
|
99
|
+
end
|
100
|
+
|
101
|
+
# Adds a criterion to the +Criteria+ that specifies the sort order of
|
102
|
+
# the returned documents in the database. Similar to a SQL "ORDER BY".
|
103
|
+
#
|
104
|
+
# Options:
|
105
|
+
#
|
106
|
+
# params: An +Array+ of [field, direction] sorting pairs.
|
107
|
+
#
|
108
|
+
# Example:
|
109
|
+
#
|
110
|
+
# <tt>criteria.order_by([[:field1, :asc], [:field2, :desc]])</tt>
|
111
|
+
#
|
112
|
+
# Returns: <tt>self</tt>
|
113
|
+
def order_by(params = [])
|
114
|
+
@options[:sort] = params; self
|
115
|
+
end
|
116
|
+
|
117
|
+
# Adds a criterion to the +Criteria+ that specifies how many results to skip
|
118
|
+
# when returning Documents. This is mostly used in conjunction with
|
119
|
+
# <tt>limit()</tt> to handle paginated results, and is similar to the
|
120
|
+
# traditional "offset" parameter.
|
121
|
+
#
|
122
|
+
# Options:
|
123
|
+
#
|
124
|
+
# value: An +Integer+ specifying the number of results to skip. Defaults to 0.
|
125
|
+
#
|
126
|
+
# Example:
|
127
|
+
#
|
128
|
+
# <tt>criteria.skip(20)</tt>
|
129
|
+
#
|
130
|
+
# Returns: <tt>self</tt>
|
131
|
+
def skip(value = 0)
|
132
|
+
@options[:skip] = value; self
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Extensions #:nodoc:
|
4
|
+
module Hash #:nodoc:
|
5
|
+
module CriteriaHelpers #:nodoc:
|
6
|
+
def expand_complex_criteria
|
7
|
+
hsh = {}
|
8
|
+
self.each_pair do |k,v|
|
9
|
+
if k.class == Mongoid::Criterion::Complex
|
10
|
+
hsh[k.key] = {"$#{k.operator}" => v}
|
11
|
+
else
|
12
|
+
hsh[k] = v
|
13
|
+
end
|
14
|
+
end
|
15
|
+
hsh
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Extensions #:nodoc:
|
4
|
+
module Symbol #:nodoc:
|
5
|
+
module Inflections #:nodoc:
|
6
|
+
|
7
|
+
REVERSALS = {
|
8
|
+
:asc => :desc,
|
9
|
+
:ascending => :descending,
|
10
|
+
:desc => :asc,
|
11
|
+
:descending => :ascending
|
12
|
+
}
|
13
|
+
|
14
|
+
def invert
|
15
|
+
REVERSALS[self]
|
16
|
+
end
|
17
|
+
|
18
|
+
def singular?
|
19
|
+
to_s.singular?
|
20
|
+
end
|
21
|
+
|
22
|
+
def plural?
|
23
|
+
to_s.plural?
|
24
|
+
end
|
25
|
+
|
26
|
+
["gt", "lt", "gte", "lte", "ne", "in", "nin", "mod", "all", "size", "exists"].each do |oper|
|
27
|
+
class_eval <<-OPERATORS
|
28
|
+
def #{oper}
|
29
|
+
Criterion::Complex.new(:key => self, :operator => "#{oper}")
|
30
|
+
end
|
31
|
+
OPERATORS
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Matchers #:nodoc:
|
4
|
+
class Default
|
5
|
+
# Creating a new matcher only requires the value.
|
6
|
+
def initialize(attribute)
|
7
|
+
@attribute = attribute
|
8
|
+
end
|
9
|
+
# Return true if the attribute and value are equal.
|
10
|
+
def matches?(value)
|
11
|
+
@attribute == value
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
# Return the first value in the hash.
|
16
|
+
def first(value)
|
17
|
+
value.values.first
|
18
|
+
end
|
19
|
+
|
20
|
+
# If object exists then compare, else return false
|
21
|
+
def determine(value, operator)
|
22
|
+
@attribute ? @attribute.send(operator, first(value)) : false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Mongoid #:nodoc:
|
3
|
+
module Matchers #:nodoc:
|
4
|
+
class Exists < Default
|
5
|
+
# Return true if the attribute exists and checking for existence or
|
6
|
+
# return true if the attribute does not exist and checking for
|
7
|
+
# non-existence.
|
8
|
+
def matches?(value)
|
9
|
+
@attribute.nil? != value.values.first
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|