mongomatic 0.2.0 → 0.3.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.
- data/README.rdoc +46 -1
- data/lib/mongomatic.rb +4 -0
- data/lib/mongomatic/base.rb +45 -0
- data/lib/mongomatic/cursor.rb +7 -0
- data/lib/mongomatic/expectations.rb +89 -0
- data/lib/mongomatic/expectations/expected.rb +9 -0
- data/lib/mongomatic/expectations/is_number.rb +15 -0
- data/lib/mongomatic/expectations/match.rb +15 -0
- data/lib/mongomatic/expectations/of_length.rb +16 -0
- data/lib/mongomatic/expectations/present.rb +9 -0
- data/lib/mongomatic/modifiers.rb +32 -23
- data/test/helper.rb +1 -0
- data/test/test_mongomatic.rb +165 -0
- metadata +10 -4
data/README.rdoc
CHANGED
@@ -98,7 +98,52 @@ You can add validations to your model by creating a validate method. If your val
|
|
98
98
|
p["address"] = { "zip" => 94107 }
|
99
99
|
p.valid?
|
100
100
|
=> true
|
101
|
-
|
101
|
+
|
102
|
+
=== The Validation Helper (Expectations)
|
103
|
+
|
104
|
+
To make writing your validate method a little simpler you can use Mongomatic::Expectations. You must include Mongomatic::Expectations::Helper on any class you wish to use them. You can use expectations like this:
|
105
|
+
|
106
|
+
class Person < Mongomatic::Base
|
107
|
+
include Mongomatic::Expectations::Helper
|
108
|
+
|
109
|
+
def validate
|
110
|
+
expectations do
|
111
|
+
be_present self['name'], "Name cannot be blank"
|
112
|
+
not_be_match self['nickname'], "Nickname cannot contain an uppercase letter", :with => /[A-Z]/
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
p = Person.new
|
118
|
+
p.valid?
|
119
|
+
=> false
|
120
|
+
p.errors.full_messages
|
121
|
+
=> ["Name cannot be blank"]
|
122
|
+
p['name'] = 'Jordan'
|
123
|
+
p.valid?
|
124
|
+
=> true
|
125
|
+
p['nickname'] = 'Jordan'
|
126
|
+
p.valid?
|
127
|
+
p.errors.full_messages
|
128
|
+
=> ["Name cannot be blank", "Nickname cannot contain an uppercase letter"]=> false
|
129
|
+
|
130
|
+
You can use any of these expectations inside of the expectations block:
|
131
|
+
|
132
|
+
* be_expected value, error_message - validates that value is true
|
133
|
+
* not_be_expected value, error_message - validates that value is false
|
134
|
+
* [not]_be_present value, error_message - validates presence of value
|
135
|
+
* [not]_be_a_number value, error_message, options - validates that value is/is not a number
|
136
|
+
Can be a string containing only a number).
|
137
|
+
Only takes :allow_nil option
|
138
|
+
|
139
|
+
* [not]_be_match value, error_message, options - validates that value matches/does not match
|
140
|
+
regular expression specified by option :with.
|
141
|
+
Also, takes option :allow_nil
|
142
|
+
* be_of_length value, error_message, options - validates that value is any of: less than the number
|
143
|
+
specified in the :minimum option, greater than the number
|
144
|
+
specified in option :maximum, or in range specified by
|
145
|
+
option :range
|
146
|
+
|
102
147
|
== Relationships
|
103
148
|
|
104
149
|
Mongomatic doesn't have any kind of special support for relationship management but it's easy to add this to your models where you need it. Here's an example that models Twitter-like followers on a User model:
|
data/lib/mongomatic.rb
CHANGED
@@ -15,10 +15,13 @@ end
|
|
15
15
|
|
16
16
|
module Mongomatic
|
17
17
|
class << self
|
18
|
+
# Returns an instance of Mongo::DB
|
18
19
|
def db
|
19
20
|
@db
|
20
21
|
end
|
21
22
|
|
23
|
+
# Set to an instance of Mongo::DB to be used for all models:
|
24
|
+
# Mongomatic.db = Mongo::Connection.new().db('mydb')
|
22
25
|
def db=(obj)
|
23
26
|
unless obj.is_a?(Mongo::DB)
|
24
27
|
raise(ArgumentError, "Must supply a Mongo::DB object")
|
@@ -30,4 +33,5 @@ end
|
|
30
33
|
require "#{File.dirname(__FILE__)}/mongomatic/cursor"
|
31
34
|
require "#{File.dirname(__FILE__)}/mongomatic/modifiers"
|
32
35
|
require "#{File.dirname(__FILE__)}/mongomatic/errors"
|
36
|
+
require "#{File.dirname(__FILE__)}/mongomatic/expectations"
|
33
37
|
require "#{File.dirname(__FILE__)}/mongomatic/base"
|
data/lib/mongomatic/base.rb
CHANGED
@@ -3,45 +3,56 @@ module Mongomatic
|
|
3
3
|
include Mongomatic::Modifiers
|
4
4
|
|
5
5
|
class << self
|
6
|
+
# Returns this models own db attribute if set, otherwise will return Mongomatic.db
|
6
7
|
def db
|
7
8
|
@db || Mongomatic.db || raise(ArgumentError, "No db supplied")
|
8
9
|
end
|
9
10
|
|
11
|
+
# Override Mongomatic.db with a Mongo::DB instance for this model specifically
|
12
|
+
# MyModel.db = Mongo::Connection.new().db('mydb_mymodel')
|
10
13
|
def db=(obj)
|
11
14
|
unless obj.is_a?(Mongo::DB)
|
12
15
|
raise(ArgumentError, "Must supply a Mongo::DB object")
|
13
16
|
end; @db = obj
|
14
17
|
end
|
15
18
|
|
19
|
+
# Override this method on your model if you want to use a different collection name
|
16
20
|
def collection_name
|
17
21
|
self.to_s
|
18
22
|
end
|
19
23
|
|
24
|
+
# Return the raw MongoDB collection for this model
|
20
25
|
def collection
|
21
26
|
@collection ||= self.db.collection(self.collection_name)
|
22
27
|
end
|
23
28
|
|
29
|
+
# Query MongoDB for documents. Same arguments as http://api.mongodb.org/ruby/current/Mongo/Collection.html#find-instance_method
|
24
30
|
def find(query={}, opts={})
|
25
31
|
Mongomatic::Cursor.new(self, collection.find(query, opts))
|
26
32
|
end
|
27
33
|
|
34
|
+
# Query MongoDB and return one document only. Same arguments as http://api.mongodb.org/ruby/current/Mongo/Collection.html#find_one-instance_method
|
28
35
|
def find_one(query={}, opts={})
|
29
36
|
return nil unless doc = self.collection.find_one(query, opts)
|
30
37
|
self.new(doc, false)
|
31
38
|
end
|
32
39
|
|
40
|
+
# Return a Mongomatic::Cursor instance of all documents in the collection.
|
33
41
|
def all
|
34
42
|
find
|
35
43
|
end
|
36
44
|
|
45
|
+
# Iterate over all documents in the collection (uses a Mongomatic::Cursor)
|
37
46
|
def each
|
38
47
|
find.each { |found| yield(found) }
|
39
48
|
end
|
40
49
|
|
50
|
+
# Return the first document in the collection
|
41
51
|
def first
|
42
52
|
find.limit(1).next_document
|
43
53
|
end
|
44
54
|
|
55
|
+
# Return the number of documents in the collection
|
45
56
|
def count
|
46
57
|
find.count
|
47
58
|
end
|
@@ -56,6 +67,12 @@ module Mongomatic
|
|
56
67
|
self.errors = Mongomatic::Errors.new
|
57
68
|
end
|
58
69
|
|
70
|
+
# Override this with your own validate() method for validations.
|
71
|
+
# Simply push your errors into the self.errors property and
|
72
|
+
# if self.errors remains empty your document will be valid.
|
73
|
+
# def validate
|
74
|
+
# self.errors << ["name", "cannot be blank"]
|
75
|
+
# end
|
59
76
|
def validate
|
60
77
|
true
|
61
78
|
end
|
@@ -76,32 +93,48 @@ module Mongomatic
|
|
76
93
|
self.is_new == true
|
77
94
|
end
|
78
95
|
|
96
|
+
# Set a field on this document:
|
97
|
+
# mydoc["name"] = "Ben"
|
98
|
+
# mydoc["address"] = { "city" => "San Francisco" }
|
79
99
|
def []=(k,v)
|
80
100
|
@doc[k.to_s] = v
|
81
101
|
end
|
82
102
|
|
103
|
+
# Fetch a field (just like a hash):
|
104
|
+
# mydoc["name"]
|
105
|
+
# => "Ben"
|
83
106
|
def [](k)
|
84
107
|
@doc[k.to_s]
|
85
108
|
end
|
86
109
|
|
110
|
+
# Merge this document with the supplied hash. Useful for updates:
|
111
|
+
# mydoc.merge(params[:user])
|
87
112
|
def merge(hash)
|
88
113
|
hash.each { |k,v| self[k] = v }; @doc
|
89
114
|
end
|
90
115
|
|
116
|
+
# Will return true if the document has been removed.
|
91
117
|
def removed?
|
92
118
|
self.removed == true
|
93
119
|
end
|
94
120
|
|
121
|
+
# Check equality with another Mongomatic document
|
95
122
|
def ==(obj)
|
96
123
|
obj.is_a?(self.class) && obj.doc["_id"] == @doc["_id"]
|
97
124
|
end
|
98
125
|
|
126
|
+
# Reload the document from the database
|
99
127
|
def reload
|
100
128
|
if obj = self.class.find({"_id" => @doc["_id"]}).next_document
|
101
129
|
@doc = obj.doc; true
|
102
130
|
end
|
103
131
|
end
|
104
132
|
|
133
|
+
# Insert the document into the database. Will return false if the document has
|
134
|
+
# already been inserted or is invalid. Returns the generated BSON::ObjectID
|
135
|
+
# for the new document. Will silently fail if MongoDB is unable to insert the
|
136
|
+
# document, use insert! if you want an error raised instead. Note that this will
|
137
|
+
# require an additional call to the db.
|
105
138
|
def insert(opts={})
|
106
139
|
return false unless new? && valid?
|
107
140
|
self.send(:before_insert) if self.respond_to?(:before_insert)
|
@@ -115,10 +148,16 @@ module Mongomatic
|
|
115
148
|
ret
|
116
149
|
end
|
117
150
|
|
151
|
+
# Calls insert(...) with {:safe => true} passed in as an option. Will check MongoDB
|
152
|
+
# after insert to make sure that the insert was successful, and raise a Mongo::OperationError
|
153
|
+
# if there were any problems.
|
118
154
|
def insert!(opts={})
|
119
155
|
insert(opts.merge(:safe => true))
|
120
156
|
end
|
121
157
|
|
158
|
+
# Will persist any changes you have made to the document. Will silently fail if
|
159
|
+
# MongoDB is unable to update the document, use update! instead if you want an
|
160
|
+
# error raised. Note that this will require an additional call to the db.
|
122
161
|
def update(opts={},update_doc=@doc)
|
123
162
|
return false if new? || removed? || !valid?
|
124
163
|
self.send(:before_update) if self.respond_to?(:before_update)
|
@@ -129,10 +168,13 @@ module Mongomatic
|
|
129
168
|
ret
|
130
169
|
end
|
131
170
|
|
171
|
+
# Same as update(...) but will raise a Mongo::OperationError in case of any issues.
|
132
172
|
def update!(opts={},update_doc=@doc)
|
133
173
|
update(opts.merge(:safe => true),update_doc)
|
134
174
|
end
|
135
175
|
|
176
|
+
# Remove this document from the collection. Silently fails on error, use remove!
|
177
|
+
# if you want an exception raised.
|
136
178
|
def remove(opts={})
|
137
179
|
return false if new?
|
138
180
|
self.send(:before_remove) if self.respond_to?(:before_remove)
|
@@ -143,10 +185,13 @@ module Mongomatic
|
|
143
185
|
ret
|
144
186
|
end
|
145
187
|
|
188
|
+
# Like remove(...) but raises Mongo::OperationError if MongoDB is unable to
|
189
|
+
# remove the document.
|
146
190
|
def remove!(opts={})
|
147
191
|
remove(opts.merge(:safe => true))
|
148
192
|
end
|
149
193
|
|
194
|
+
# Return this document as a hash.
|
150
195
|
def to_hash
|
151
196
|
@doc || {}
|
152
197
|
end
|
data/lib/mongomatic/cursor.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
module Mongomatic
|
2
|
+
# Wraps a Mongo::Cursor for managing result sets from MongoDB:
|
3
|
+
# cursor = User.find({"zip" => 94107})
|
4
|
+
# user1 = cursor.next
|
5
|
+
#
|
6
|
+
# User.find({"zip" => 94107}).each { |u| puts u["name"] }
|
7
|
+
#
|
8
|
+
# User.find({"zip" => 94107}).count
|
2
9
|
class Cursor
|
3
10
|
include Enumerable
|
4
11
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Mongomatic
|
2
|
+
module Expectations
|
3
|
+
module Helper
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def define_expectations
|
8
|
+
Expectation.subclasses.each do |klass|
|
9
|
+
if Expectation.define_to_be?(klass)
|
10
|
+
instance_eval %Q{
|
11
|
+
def be_#{klass.name.downcase}(value, message, opts = {})
|
12
|
+
#{klass}.new(self, value, message, opts).to_be
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
if Expectation.define_to_not_be?(klass)
|
17
|
+
instance_eval %Q{
|
18
|
+
def not_be_#{klass.name.downcase}(value, message, opts = {})
|
19
|
+
#{klass}.new(self, value, message, opts).to_not_be
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def undefine_expectations
|
27
|
+
Expectation.subclasses.each do |klass|
|
28
|
+
instance_eval %Q{
|
29
|
+
if respond_to? "be_#{klass.name.downcase}"
|
30
|
+
class << self
|
31
|
+
remove_method "be_#{klass.name.downcase}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
if respond_to? "not_be_#{klass.name.downcase}"
|
35
|
+
class << self
|
36
|
+
remove_method "not_be_#{klass.name.downcase}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def expectations(&block)
|
44
|
+
define_expectations
|
45
|
+
block.call
|
46
|
+
undefine_expectations
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Expectation
|
51
|
+
|
52
|
+
attr_accessor :instance, :value, :message, :opts
|
53
|
+
|
54
|
+
class << self
|
55
|
+
attr_accessor :subclasses
|
56
|
+
|
57
|
+
def subclasses
|
58
|
+
@subclasses ||= []
|
59
|
+
@subclasses
|
60
|
+
end
|
61
|
+
|
62
|
+
def inherited(klass)
|
63
|
+
subclasses << klass
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_to_be?(klass)
|
67
|
+
klass.new(nil, nil, nil).respond_to? :to_be
|
68
|
+
end
|
69
|
+
|
70
|
+
def define_to_not_be?(klass)
|
71
|
+
klass.new(nil, nil, nil).respond_to? :to_not_be
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def initialize(instance, value, message, opts = {})
|
76
|
+
@value = value
|
77
|
+
@instance = instance
|
78
|
+
@message = message
|
79
|
+
@opts = opts
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_error_msg
|
83
|
+
instance.errors << [message]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
Dir["#{File.dirname(__FILE__)}/expectations/*.rb"].each { |f| require f }
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class IsNumber < Mongomatic::Expectations::Expectation
|
2
|
+
def self.name
|
3
|
+
"a_number"
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_be
|
7
|
+
return true if opts[:allow_nil] && value.nil?
|
8
|
+
|
9
|
+
add_error_msg if (value.to_s =~ /^\d*\.{0,1}\d+$/).nil?
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_not_be
|
13
|
+
add_error_msg unless (value.to_s =~ /^\d*\.{0,1}\d+$/).nil?
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Match < Mongomatic::Expectations::Expectation
|
2
|
+
def self.name
|
3
|
+
"match"
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_be
|
7
|
+
return true if opts[:allow_nil] && value.nil?
|
8
|
+
|
9
|
+
add_error_msg unless opts[:with].match(value.to_s)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_not_be
|
13
|
+
add_error_msg if opts[:with].match(value.to_s)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class OfLength < Mongomatic::Expectations::Expectation
|
2
|
+
def self.name
|
3
|
+
"of_length"
|
4
|
+
end
|
5
|
+
|
6
|
+
def to_be
|
7
|
+
return true if opts[:allow_nil] && value.nil?
|
8
|
+
|
9
|
+
length = (value) ? value.size : value.to_s.size
|
10
|
+
add_error_msg if opts[:minimum] && length < opts[:minimum]
|
11
|
+
add_error_msg if opts[:maximum] && length > opts[:maximum]
|
12
|
+
if opts[:range]
|
13
|
+
add_error_msg unless opts[:range].include?(length)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/mongomatic/modifiers.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
module Mongomatic
|
2
|
+
# Provides convenience methods for atomic MongoDB operations.
|
2
3
|
module Modifiers
|
3
4
|
|
4
|
-
# { $push : { field : value } }
|
5
|
-
#
|
5
|
+
# MongoDB equivalent: { $push : { field : value } }<br/>
|
6
|
+
# Appends value to field, if field is an existing array, otherwise sets field to the array [value]
|
6
7
|
# if field is not present. If field is present but is not an array, an error condition is raised.
|
8
|
+
# user.push("interests", "skydiving")
|
7
9
|
def push(field, val, do_reload=true)
|
8
10
|
field = field.to_s
|
9
11
|
if update({}, { "$push" => { field => val } })
|
@@ -11,10 +13,11 @@ module Mongomatic
|
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
|
-
# { $pushAll : { field : value_array } }
|
15
|
-
#
|
16
|
+
# MongoDB equivalent: { $pushAll : { field : value_array } }<br/>
|
17
|
+
# Appends each value in value_array to field, if field is an existing array, otherwise sets field to
|
16
18
|
# the array value_array if field is not present. If field is present but is not an array, an error
|
17
19
|
# condition is raised.
|
20
|
+
# user.push("interests", ["skydiving", "coding"])
|
18
21
|
def push_all(field, val, do_reload=true)
|
19
22
|
field = field.to_s
|
20
23
|
val = Array(val)
|
@@ -23,9 +26,10 @@ module Mongomatic
|
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
|
-
# { $pull : { field : _value } }
|
27
|
-
#
|
29
|
+
# MongoDB equivalent: { $pull : { field : _value } }<br/>
|
30
|
+
# Removes all occurrences of value from field, if field is an array. If field is present but is not
|
28
31
|
# an array, an error condition is raised.
|
32
|
+
# user.pull("interests", "watching paint dry")
|
29
33
|
def pull(field, val, do_reload=true)
|
30
34
|
field = field.to_s
|
31
35
|
if update({}, { "$pull" => { field => val } })
|
@@ -33,18 +37,20 @@ module Mongomatic
|
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
36
|
-
# { $pullAll : { field : value_array } }
|
37
|
-
#
|
40
|
+
# MongoDB equivalent: { $pullAll : { field : value_array } }<br/>
|
41
|
+
# Removes all occurrences of each value in value_array from field, if field is an array. If field is
|
38
42
|
# present but is not an array, an error condition is raised.
|
39
|
-
|
43
|
+
# user.pull_all("interests", ["watching paint dry", "sitting on my ass"])
|
44
|
+
def pull_all(field, val, do_reload=true)
|
40
45
|
field = field.to_s
|
41
46
|
if update({}, { "$pullAll" => { field => val } })
|
42
47
|
reload if do_reload; true
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
46
|
-
# { $inc : { field : value } }
|
47
|
-
#
|
51
|
+
# MongoDB equivalent: { $inc : { field : value } }<br/>
|
52
|
+
# Increments field by the number value if field is present in the object, otherwise sets field to the number value.
|
53
|
+
# user.inc("cents_in_wallet", 1000)
|
48
54
|
def inc(field, val, do_reload=true)
|
49
55
|
field = field.to_s
|
50
56
|
if update({}, { "$inc" => { field => val } })
|
@@ -52,8 +58,9 @@ module Mongomatic
|
|
52
58
|
end
|
53
59
|
end
|
54
60
|
|
55
|
-
# { $set : { field : value } }
|
56
|
-
#
|
61
|
+
# MongoDB equivalent: { $set : { field : value } }<br/>
|
62
|
+
# Sets field to value. All datatypes are supported with $set.
|
63
|
+
# user.set("name", "Ben")
|
57
64
|
def set(field, val, do_reload=true)
|
58
65
|
field = field.to_s
|
59
66
|
if update({}, { "$set" => { field => val } })
|
@@ -61,8 +68,9 @@ module Mongomatic
|
|
61
68
|
end
|
62
69
|
end
|
63
70
|
|
64
|
-
# { $unset : { field : 1} }
|
71
|
+
# MongoDB equivalent: { $unset : { field : 1} }<br/>
|
65
72
|
# Deletes a given field. v1.3+
|
73
|
+
# user.unset("name")
|
66
74
|
def unset(field, do_reload=true)
|
67
75
|
field = field.to_s
|
68
76
|
if update({}, { "$unset" => { field => 1 } })
|
@@ -70,12 +78,11 @@ module Mongomatic
|
|
70
78
|
end
|
71
79
|
end
|
72
80
|
|
73
|
-
# { $addToSet : { field : value } }
|
74
|
-
# Adds value to the array only if its not in the array already
|
75
|
-
#
|
76
|
-
# To add many valuest.update
|
77
|
-
#
|
81
|
+
# MongoDB equivalent: { $addToSet : { field : value } }<br/>
|
82
|
+
# Adds value to the array only if its not in the array already.<br/>
|
83
|
+
# Or to add many values:<br/>
|
78
84
|
# { $addToSet : { a : { $each : [ 3 , 5 , 6 ] } } }
|
85
|
+
# user.add_to_set("friend_ids", BSON::ObjectID('...'))
|
79
86
|
def add_to_set(field, val, do_reload=true)
|
80
87
|
field = field.to_s
|
81
88
|
if update({}, { "$addToSet" => { field => val } })
|
@@ -83,8 +90,9 @@ module Mongomatic
|
|
83
90
|
end
|
84
91
|
end
|
85
92
|
|
86
|
-
# { $pop : { field : 1 } }
|
87
|
-
#
|
93
|
+
# MongoDB equivalent: { $pop : { field : 1 } }<br/>
|
94
|
+
# Removes the last element in an array (ADDED in 1.1)
|
95
|
+
# user.pop_last("friend_ids")
|
88
96
|
def pop_last(field, do_reload=true)
|
89
97
|
field = field.to_s
|
90
98
|
if update({}, { "$pop" => { field => 1 } })
|
@@ -92,8 +100,9 @@ module Mongomatic
|
|
92
100
|
end
|
93
101
|
end
|
94
102
|
|
95
|
-
# { $pop : { field : -1 } }
|
96
|
-
#
|
103
|
+
# MongoDB equivalent: { $pop : { field : -1 } }<br/>
|
104
|
+
# Removes the first element in an array (ADDED in 1.1)
|
105
|
+
# user.pop_first("friend_ids")
|
97
106
|
def pop_first(field, do_reload=true)
|
98
107
|
field = field.to_s
|
99
108
|
if update({}, { "$pop" => { field => -1 } })
|
data/test/helper.rb
CHANGED
data/test/test_mongomatic.rb
CHANGED
@@ -212,4 +212,169 @@ class TestMongomatic < Test::Unit::TestCase
|
|
212
212
|
assert_equal found["_id"], "mycustomid"
|
213
213
|
assert_equal 26, found["age"]
|
214
214
|
end
|
215
|
+
|
216
|
+
should "be able to use the be_expect expectation" do
|
217
|
+
p = Person.new
|
218
|
+
class << p
|
219
|
+
def validate
|
220
|
+
expectations do
|
221
|
+
be_expected self['alive'], "Alive must be true"
|
222
|
+
not_be_expected self['dead'], "Dead must be false"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
assert !p.valid?
|
228
|
+
assert_equal ['Alive must be true'], p.errors.full_messages
|
229
|
+
|
230
|
+
p['alive'] = true
|
231
|
+
assert p.valid?
|
232
|
+
|
233
|
+
p['dead'] = true
|
234
|
+
assert !p.valid?
|
235
|
+
assert_equal ['Dead must be false'], p.errors.full_messages
|
236
|
+
end
|
237
|
+
|
238
|
+
should "be able to use the be_present expectation" do
|
239
|
+
p = Person.new
|
240
|
+
class << p
|
241
|
+
def validate
|
242
|
+
expectations do
|
243
|
+
be_present self['name'], 'name cannot be blank'
|
244
|
+
not_be_present self['age'], 'age must be blank'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
assert !p.valid?
|
250
|
+
assert_equal ['name cannot be blank'], p.errors.full_messages
|
251
|
+
|
252
|
+
p['name'] = "Jordan"
|
253
|
+
p['age'] = 21
|
254
|
+
|
255
|
+
|
256
|
+
assert !p.valid?
|
257
|
+
assert_equal ['age must be blank'], p.errors.full_messages
|
258
|
+
|
259
|
+
p['age'] = nil
|
260
|
+
|
261
|
+
assert p.valid?
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
should "be able to use be_a_number expectation" do
|
266
|
+
p = Person.new
|
267
|
+
class << p
|
268
|
+
def validate
|
269
|
+
expectations do
|
270
|
+
be_a_number self['age'], 'Age is not a number'
|
271
|
+
not_be_a_number self['name'], 'Name cannot be a number'
|
272
|
+
be_a_number self['birth_year'], 'Birth year is not a number', :allow_nil => true
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
assert !p.valid?
|
278
|
+
assert_equal ["Age is not a number"], p.errors.full_messages
|
279
|
+
|
280
|
+
p['age'] = 21
|
281
|
+
p['name'] = 65
|
282
|
+
|
283
|
+
assert !p.valid?
|
284
|
+
assert_equal ["Name cannot be a number"], p.errors.full_messages
|
285
|
+
|
286
|
+
p['name'] = 'Jordan'
|
287
|
+
|
288
|
+
assert p.valid?
|
289
|
+
end
|
290
|
+
|
291
|
+
should "be able to use be_match expectation" do
|
292
|
+
p = Person.new
|
293
|
+
class << p
|
294
|
+
def validate
|
295
|
+
expectations do
|
296
|
+
be_match self['name'], "Name must start with uppercase letter", :with => /[A-Z][a-z]*/
|
297
|
+
not_be_match self['nickname'], "Nickname cannot start with uppercase letter", :with => /[A-Z][a-z]*/
|
298
|
+
be_match self['age'], "Age must only contain digits", :with => /\d+/, :allow_nil => true
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
assert !p.valid?
|
304
|
+
assert_equal ["Name must start with uppercase letter"], p.errors.full_messages
|
305
|
+
|
306
|
+
p['name'] = 'Jordan'
|
307
|
+
p['nickname'] = 'Jordan'
|
308
|
+
|
309
|
+
assert !p.valid?
|
310
|
+
assert_equal ["Nickname cannot start with uppercase letter"], p.errors.full_messages
|
311
|
+
|
312
|
+
p['nickname'] = 'jordan'
|
313
|
+
|
314
|
+
assert p.valid?
|
315
|
+
|
316
|
+
p['age'] = 'asd'
|
317
|
+
|
318
|
+
assert !p.valid?
|
319
|
+
assert_equal ["Age must only contain digits"], p.errors.full_messages
|
320
|
+
|
321
|
+
p['age'] = '21'
|
322
|
+
|
323
|
+
assert p.valid?
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
should "be able to use be_of_length expectation" do
|
328
|
+
p = Person.new
|
329
|
+
class << p
|
330
|
+
def validate
|
331
|
+
expectations do
|
332
|
+
be_of_length self['name'], "Name must be 3 characters long", :minimum => 3
|
333
|
+
be_of_length self['nickname'], "Nickname must not be longer than 5 characters", :maximum => 5
|
334
|
+
be_of_length self['computers'], "Can only specify between 1 and 3 computers", :range => 1..3
|
335
|
+
be_of_length self['status'], "Status must be a minimum of 1 character", :minumum => 1, :allow_nil => true
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
assert !p.valid?
|
341
|
+
assert_equal ["Name must be 3 characters long",
|
342
|
+
"Can only specify between 1 and 3 computers"], p.errors.full_messages
|
343
|
+
|
344
|
+
p['name'] = 'Jordan'
|
345
|
+
p['nickname'] = 'Jordan'
|
346
|
+
|
347
|
+
assert !p.valid?
|
348
|
+
assert_equal ["Nickname must not be longer than 5 characters",
|
349
|
+
"Can only specify between 1 and 3 computers"], p.errors.full_messages
|
350
|
+
|
351
|
+
p['nickname'] = 'abc'
|
352
|
+
p['computers'] = ['comp_a']
|
353
|
+
|
354
|
+
assert p.valid?
|
355
|
+
end
|
356
|
+
|
357
|
+
should "raise an error if expectations are called outside of helper block" do
|
358
|
+
p = Person.new
|
359
|
+
class << p
|
360
|
+
def validate
|
361
|
+
be_present self['name'], ''
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
assert_raise NoMethodError do
|
366
|
+
p.valid?
|
367
|
+
end
|
368
|
+
|
369
|
+
class << p
|
370
|
+
def validate
|
371
|
+
expectations { }
|
372
|
+
be_present
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
assert_raise NameError do
|
377
|
+
p.valid?
|
378
|
+
end
|
379
|
+
end
|
215
380
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 3
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.3.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ben Myles
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-08-
|
17
|
+
date: 2010-08-14 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -106,13 +106,19 @@ files:
|
|
106
106
|
- lib/mongomatic/base.rb
|
107
107
|
- lib/mongomatic/cursor.rb
|
108
108
|
- lib/mongomatic/errors.rb
|
109
|
+
- lib/mongomatic/expectations.rb
|
110
|
+
- lib/mongomatic/expectations/expected.rb
|
111
|
+
- lib/mongomatic/expectations/is_number.rb
|
112
|
+
- lib/mongomatic/expectations/match.rb
|
113
|
+
- lib/mongomatic/expectations/of_length.rb
|
114
|
+
- lib/mongomatic/expectations/present.rb
|
109
115
|
- lib/mongomatic/modifiers.rb
|
110
116
|
- LICENSE
|
111
117
|
- README.rdoc
|
112
118
|
- test/helper.rb
|
113
119
|
- test/test_mongomatic.rb
|
114
120
|
has_rdoc: true
|
115
|
-
homepage: http://
|
121
|
+
homepage: http://mongomatic.com/
|
116
122
|
licenses: []
|
117
123
|
|
118
124
|
post_install_message:
|