em-mongo 0.2.14 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.1
@@ -25,10 +25,10 @@ module EM::Mongo
25
25
  end
26
26
  end
27
27
 
28
- def insert(obj)
29
- obj['_id'] ||= BSON::ObjectId.new
30
- @connection.insert(@name, obj)
31
- obj
28
+ def insert(doc)
29
+ sanitize_id!(doc)
30
+ @connection.insert(@name, doc)
31
+ doc[:_id] # mongo-ruby-driver returns ID
32
32
  end
33
33
 
34
34
  def update(selector, updater, opts={})
@@ -36,10 +36,37 @@ module EM::Mongo
36
36
  true
37
37
  end
38
38
 
39
+ # XXX Missing tests
40
+ def save(doc, opts={})
41
+ id = has_id?(doc)
42
+ sanitize_id!(doc)
43
+ if id
44
+ update({:_id => id}, doc, :upsert => true)
45
+ id
46
+ else
47
+ insert(doc)
48
+ end
49
+ end
50
+
39
51
  def remove(obj = {})
40
52
  @connection.delete(@name, obj)
41
53
  true
42
54
  end
43
55
 
56
+ private
57
+
58
+ def has_id?(doc)
59
+ # mongo-ruby-driver seems to take :_id over '_id' for some reason
60
+ id = doc[:_id] || doc['_id']
61
+ return id if id
62
+ nil
63
+ end
64
+
65
+ def sanitize_id!(doc)
66
+ doc[:_id] = has_id?(doc) || BSON::ObjectId.new
67
+ doc.delete('_id')
68
+ doc
69
+ end
70
+
44
71
  end
45
72
  end
@@ -239,25 +239,10 @@ module EM::Mongo
239
239
 
240
240
  end
241
241
 
242
- # Make EM::Mongo look like mongo-ruby-driver
243
242
  module EM::Mongo
244
- class Database
245
- def initialize(name = DEFAULT_DB, connection = nil)
246
- @db_name = name
247
- @em_connection = connection || EM::Mongo::Connection.new
248
- @collection = nil
249
- end
250
-
251
- def collection(name = DEFAULT_NS)
252
- @collection = EM::Mongo::Collection.new(@db_name, name, @em_connection)
253
- end
254
-
255
- def close
256
- @em_connection.close
257
- end
258
- end
259
243
 
260
244
  class Connection
245
+
261
246
  def initialize(host = DEFAULT_IP, port = DEFAULT_PORT, timeout = nil, opts = {})
262
247
  @em_connection = EMConnection.connect(host, port, timeout, opts)
263
248
  @db = {}
@@ -0,0 +1,89 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2010 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+ module Mongo #:nodoc:
19
+
20
+ # Utility module to include when needing to convert certain types of
21
+ # objects to mongo-friendly parameters.
22
+ module Conversions
23
+
24
+ ASCENDING_CONVERSION = ["ascending", "asc", "1"]
25
+ DESCENDING_CONVERSION = ["descending", "desc", "-1"]
26
+
27
+ # Converts the supplied +Array+ to a +Hash+ to pass to mongo as
28
+ # sorting parameters. The returned +Hash+ will vary depending
29
+ # on whether the passed +Array+ is one or two dimensional.
30
+ #
31
+ # Example:
32
+ #
33
+ # <tt>array_as_sort_parameters([["field1", :asc], ["field2", :desc]])</tt> =>
34
+ # <tt>{ "field1" => 1, "field2" => -1}</tt>
35
+ def array_as_sort_parameters(value)
36
+ order_by = BSON::OrderedHash.new
37
+ if value.first.is_a? Array
38
+ value.each do |param|
39
+ if (param.class.name == "String")
40
+ order_by[param] = 1
41
+ else
42
+ order_by[param[0]] = sort_value(param[1]) unless param[1].nil?
43
+ end
44
+ end
45
+ elsif !value.empty?
46
+ if order_by.size == 1
47
+ order_by[value.first] = 1
48
+ else
49
+ order_by[value.first] = sort_value(value[1])
50
+ end
51
+ end
52
+ order_by
53
+ end
54
+
55
+ # Converts the supplied +String+ or +Symbol+ to a +Hash+ to pass to mongo as
56
+ # a sorting parameter with ascending order. If the +String+
57
+ # is empty then an empty +Hash+ will be returned.
58
+ #
59
+ # Example:
60
+ #
61
+ # *DEPRECATED
62
+ #
63
+ # <tt>string_as_sort_parameters("field")</tt> => <tt>{ "field" => 1 }</tt>
64
+ # <tt>string_as_sort_parameters("")</tt> => <tt>{}</tt>
65
+ def string_as_sort_parameters(value)
66
+ return {} if (str = value.to_s).empty?
67
+ { str => 1 }
68
+ end
69
+
70
+ # Converts the +String+, +Symbol+, or +Integer+ to the
71
+ # corresponding sort value in MongoDB.
72
+ #
73
+ # Valid conversions (case-insensitive):
74
+ #
75
+ # <tt>ascending, asc, :ascending, :asc, 1</tt> => <tt>1</tt>
76
+ # <tt>descending, desc, :descending, :desc, -1</tt> => <tt>-1</tt>
77
+ #
78
+ # If the value is invalid then an error will be raised.
79
+ def sort_value(value)
80
+ val = value.to_s.downcase
81
+ return 1 if ASCENDING_CONVERSION.include?(val)
82
+ return -1 if DESCENDING_CONVERSION.include?(val)
83
+ raise InvalidSortValueError.new(
84
+ "#{self} was supplied as a sort direction when acceptable values are: " +
85
+ "Mongo::ASCENDING, 'ascending', 'asc', :ascending, :asc, 1, Mongo::DESCENDING, " +
86
+ "'descending', 'desc', :descending, :desc, -1.")
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,59 @@
1
+ module EM::Mongo
2
+ class Database
3
+
4
+ SYSTEM_NAMESPACE_COLLECTION = "system.namespaces"
5
+ SYSTEM_INDEX_COLLECTION = "system.indexes"
6
+ SYSTEM_PROFILE_COLLECTION = "system.profile"
7
+ SYSTEM_USER_COLLECTION = "system.users"
8
+ SYSTEM_JS_COLLECTION = "system.js"
9
+ SYSTEM_COMMAND_COLLECTION = "$cmd"
10
+
11
+ def initialize(name = DEFAULT_DB, connection = nil)
12
+ @db_name = name
13
+ @em_connection = connection || EM::Mongo::Connection.new
14
+ @collection = nil
15
+ @collections = {}
16
+ end
17
+
18
+ def collection(name = EM::Mongo::DEFAULT_NS)
19
+ @collections[@db_name] ||= EM::Mongo::Collection.new(@db_name, name, @em_connection)
20
+ end
21
+
22
+ def connection
23
+ @em_connection
24
+ end
25
+
26
+ def close
27
+ @em_connection.close
28
+ end
29
+
30
+ def authenticate(username, password)
31
+ self.collection(SYSTEM_COMMAND_COLLECTION).first({'getnonce' => 1}) do |res|
32
+ yield false if not res or not res['nonce']
33
+
34
+ auth = BSON::OrderedHash.new
35
+ auth['authenticate'] = 1
36
+ auth['user'] = username
37
+ auth['nonce'] = res['nonce']
38
+ auth['key'] = Mongo::Support.auth_key(username, password, res['nonce'])
39
+
40
+ self.collection(SYSTEM_COMMAND_COLLECTION).first(auth) do |res|
41
+ if Mongo::Support.ok?(res)
42
+ yield true
43
+ else
44
+ yield res
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def add_user(username, password, &blk)
51
+ self.collection(SYSTEM_USER_COLLECTION).first({:user => username}) do |res|
52
+ user = res || {:user => username}
53
+ user['pwd'] = Mongo::Support.hash_password(username, password)
54
+ yield self.collection(SYSTEM_USER_COLLECTION).save(user)
55
+ end
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2010 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ require 'digest/md5'
20
+
21
+ module Mongo
22
+ module Support
23
+ include Mongo::Conversions
24
+ extend self
25
+
26
+ # Generate an MD5 for authentication.
27
+ #
28
+ # @param [String] username
29
+ # @param [String] password
30
+ # @param [String] nonce
31
+ #
32
+ # @return [String] a key for db authentication.
33
+ def auth_key(username, password, nonce)
34
+ Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
35
+ end
36
+
37
+ # Return a hashed password for auth.
38
+ #
39
+ # @param [String] username
40
+ # @param [String] plaintext
41
+ #
42
+ # @return [String]
43
+ def hash_password(username, plaintext)
44
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
45
+ end
46
+
47
+
48
+ def validate_db_name(db_name)
49
+ unless [String, Symbol].include?(db_name.class)
50
+ raise TypeError, "db_name must be a string or symbol"
51
+ end
52
+
53
+ [" ", ".", "$", "/", "\\"].each do |invalid_char|
54
+ if db_name.include? invalid_char
55
+ raise Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
56
+ end
57
+ end
58
+ raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
59
+ db_name
60
+ end
61
+
62
+ def format_order_clause(order)
63
+ case order
64
+ when String, Symbol then string_as_sort_parameters(order)
65
+ when Array then array_as_sort_parameters(order)
66
+ else
67
+ raise InvalidSortValueError, "Illegal sort clause, '#{order.class.name}'; must be of the form " +
68
+ "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
69
+ end
70
+ end
71
+
72
+ # Determine if a database command has succeeded by
73
+ # checking the document response.
74
+ #
75
+ # @param [Hash] doc
76
+ #
77
+ # @return [Boolean] true if the 'ok' key is either 1 or *true*.
78
+ def ok?(doc)
79
+ doc['ok'] == 1.0 || doc['ok'] == true
80
+ end
81
+ end
82
+ end
data/lib/em-mongo.rb CHANGED
@@ -1,21 +1,32 @@
1
-
2
- require "eventmachine"
3
- begin; require "bson_ext"; rescue LoadError; require "bson"; end
1
+ begin
2
+ require "rubygems"
3
+ require "bundler"
4
+ Bundler.setup(:default)
5
+ rescue LoadError
6
+ ensure
7
+ require "eventmachine"
8
+ begin
9
+ require "bson_ext"
10
+ rescue LoadError
11
+ require "bson"
12
+ end
13
+ end
4
14
 
5
15
  module EM::Mongo
6
16
 
7
17
  module Version
8
- MAJOR = 0
9
- MINOR = 2
10
- TINY = 10
11
- STRING = [MAJOR, MINOR, TINY].join('.')
18
+ STRING = File.read(File.dirname(__FILE__) + '/../VERSION')
19
+ MAJOR, MINOR, TINY = STRING.split('.')
12
20
  end
13
-
21
+
14
22
  NAME = 'em-mongo'
15
23
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
16
24
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
17
25
  end
18
26
 
27
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/conversions")
28
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/support")
29
+ require File.join(EM::Mongo::LIBPATH, "em-mongo/database")
19
30
  require File.join(EM::Mongo::LIBPATH, "em-mongo/connection")
20
31
  require File.join(EM::Mongo::LIBPATH, "em-mongo/collection")
21
32
 
@@ -4,69 +4,79 @@ describe EMMongo::Collection do
4
4
  include EM::Spec
5
5
 
6
6
  it 'should insert an object' do
7
- @conn, @coll = connection_and_collection
7
+ @conn, @coll = connection_and_collection
8
8
 
9
- obj = @coll .insert('hello' => 'world')
10
- obj.keys.should include '_id'
11
- obj['_id'].should be_a_kind_of(BSON::ObjectId)
12
- done
9
+ doc = {'hello' => 'world'}
10
+ id = @coll.insert(doc)
11
+ id.should be_a_kind_of(BSON::ObjectId)
12
+ doc[:_id].should be_a_kind_of(BSON::ObjectId)
13
+ done
13
14
  end
14
15
 
15
16
  it 'should insert an object with a custom _id' do
16
- @conn, @coll = connection_and_collection
17
+ @conn, @coll = connection_and_collection
17
18
 
18
- obj = @coll .insert('_id' => 1234, 'hello' => 'world')
19
- obj.keys.should include '_id'
20
- obj['_id'].should == 1234
21
- r = @coll.find({"hello" => "world"},{}) do |res|
22
- res.size.should >= 1
23
- res[0]['_id'].should == 1234
24
- done
25
- end
19
+ id = @coll.insert(:_id => 1234, 'hello' => 'world')
20
+ id.should == 1234
21
+ @coll.first({'hello' => 'world'}) do |res|
22
+ res['_id'].should == 1234
23
+ done
24
+ end
26
25
  end
27
26
 
28
27
  it 'should find an object by attribute' do
29
- @conn, @coll = connection_and_collection
28
+ @conn, @coll = connection_and_collection
30
29
 
31
- @coll.insert("hello" => 'world')
32
- r = @coll.find({"hello" => "world"},{}) do |res|
33
- res.size.should >= 1
34
- res[0]["hello"].should == "world"
35
- done
36
- end
30
+ @coll.insert("hello" => 'world')
31
+ @coll.find({"hello" => "world"},{}) do |res|
32
+ res.size.should >= 1
33
+ res[0]["hello"].should == "world"
34
+ done
35
+ end
36
+ end
37
+
38
+ it 'should take strings or symbols for hashes' do
39
+ @conn, @coll = connection_and_collection
40
+
41
+ obj = @coll.insert({:_id => 1234, 'foo' => 'bar', :hello => 'world'})
42
+ @coll.first({:_id => 1234},{}) do |res|
43
+ res['hello'].should == 'world'
44
+ res['foo'].should == 'bar'
45
+ done
46
+ end
37
47
  end
38
48
 
39
49
  it 'should find an object by symbol' do
40
- @conn, @coll = connection_and_collection
50
+ @conn, @coll = connection_and_collection
41
51
 
42
- @coll.insert('hello' => 'world')
43
- r = @coll.find({:hello => "world"},{}) do |res|
44
- res.size.should >= 1
45
- res[0]["hello"].should == "world"
46
- done
47
- end
52
+ @coll.insert('hello' => 'world')
53
+ @coll.find({:hello => "world"},{}) do |res|
54
+ res.size.should >= 1
55
+ res[0]["hello"].should == "world"
56
+ done
57
+ end
48
58
  end
49
59
 
50
60
  it 'should find an object by id' do
51
- @conn, @coll = connection_and_collection
61
+ @conn, @coll = connection_and_collection
52
62
 
53
- obj = @coll.insert('hello' => 'world')
54
- @coll.find({'_id' => obj['_id']},{}) do |res|
55
- res.size.should >= 1
56
- res[0]['hello'].should == "world"
57
- done
58
- end
63
+ id = @coll.insert('hello' => 'world')
64
+ @coll.find({:_id => id},{}) do |res|
65
+ res.size.should >= 1
66
+ res[0]['hello'].should == "world"
67
+ done
68
+ end
59
69
  end
60
70
 
61
71
  it 'should find all objects' do
62
- @conn, @coll = connection_and_collection
72
+ @conn, @coll = connection_and_collection
63
73
 
64
- @coll.insert('one' => 'one')
65
- @coll.insert('two' => 'two')
66
- @coll.find do |res|
67
- res.size.should >= 2
68
- done
69
- end
74
+ @coll.insert('one' => 'one')
75
+ @coll.insert('two' => 'two')
76
+ @coll.find do |res|
77
+ res.size.should >= 2
78
+ done
79
+ end
70
80
  end
71
81
 
72
82
  it 'should find large sets of objects' do
@@ -85,9 +95,9 @@ describe EMMongo::Collection do
85
95
  it 'should update an object' do
86
96
  @conn, @coll = connection_and_collection
87
97
 
88
- obj = @coll.insert('hello' => 'world')
98
+ id = @coll.insert('hello' => 'world')
89
99
  @coll.update({'hello' => 'world'}, {'hello' => 'newworld'})
90
- @coll.find({'_id' => obj['_id']},{}) do |res|
100
+ @coll.find({:_id => id},{}) do |res|
91
101
  res[0]['hello'].should == 'newworld'
92
102
  done
93
103
  end
@@ -96,9 +106,9 @@ describe EMMongo::Collection do
96
106
  it 'should update an object wxith $inc' do
97
107
  @conn, @coll = connection_and_collection
98
108
 
99
- obj = @coll.insert('hello' => 'world')
109
+ id = @coll.insert('hello' => 'world')
100
110
  @coll.update({'hello' => 'world'}, {'$inc' => {'count' => 1}})
101
- @coll.find({'_id' => obj['_id']},{}) do |res|
111
+ @coll.find({:_id => id},{}) do |res|
102
112
  res.first['hello'].should == 'world'
103
113
  res.first['count'].should == 1
104
114
  done
@@ -108,8 +118,8 @@ describe EMMongo::Collection do
108
118
  it 'should remove an object' do
109
119
  @conn, @coll = connection_and_collection
110
120
 
111
- obj = @coll.insert('hello' => 'world')
112
- @coll.remove('_id' => obj['_id'])
121
+ id = @coll.insert('hello' => 'world')
122
+ @coll.remove(:_id => id)
113
123
  @coll.find({'hello' => "world"}) do |res|
114
124
  res.size.should == 0
115
125
  done
@@ -153,9 +163,10 @@ describe EMMongo::Collection do
153
163
  'regex' => /abc$/ix
154
164
  }
155
165
  retobj = @coll.insert(obj)
156
- @coll.find({'_id' => obj['_id']}) do |ret|
166
+ @coll.find({:_id => obj[:_id]}) do |ret|
157
167
  ret.size.should == 1
158
168
  ret[0].each_key do |key|
169
+ next if key == '_id'
159
170
  ret[0][key].should == obj[key]
160
171
  end
161
172
  done
@@ -209,7 +220,7 @@ describe EMMongo::Collection do
209
220
  it 'should handle multiple pending queries' do
210
221
  @conn, @coll = connection_and_collection
211
222
 
212
- id = @coll.insert("foo" => "bar")['_id']
223
+ id = @coll.insert("foo" => "bar")
213
224
  received = 0
214
225
 
215
226
  10.times do |n|
@@ -0,0 +1,27 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__) + '/../')
2
+
3
+ describe EMMongo::Database do
4
+ include EM::Spec
5
+
6
+ it 'should add a user' do
7
+ @conn = EM::Mongo::Connection.new
8
+ @db = @conn.db
9
+ @db.add_user('test', 'test') do |res|
10
+ res.should_not == nil
11
+ res.should be_a_kind_of(BSON::ObjectId)
12
+ done
13
+ end
14
+ end
15
+
16
+ # This test requires the above test.
17
+ it 'should authenticate a user' do
18
+ @conn = EM::Mongo::Connection.new
19
+ @db = @conn.db
20
+ @db.authenticate('test', 'test') do |res|
21
+ res.should == true
22
+ done
23
+ end
24
+ end
25
+
26
+
27
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
8
- - 14
9
- version: 0.2.14
7
+ - 3
8
+ - 1
9
+ version: 0.3.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - bcg
@@ -47,7 +47,22 @@ dependencies:
47
47
  version: 0.20.1
48
48
  type: :runtime
49
49
  version_requirements: *id002
50
- description: EventMachine drive for MongoDB.
50
+ - !ruby/object:Gem::Dependency
51
+ name: bson_ext
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 0
60
+ - 20
61
+ - 1
62
+ version: 0.20.1
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ description: EventMachine driver for MongoDB.
51
66
  email: brenden.grace@gmail.com
52
67
  executables: []
53
68
 
@@ -56,11 +71,16 @@ extensions: []
56
71
  extra_rdoc_files: []
57
72
 
58
73
  files:
74
+ - VERSION
59
75
  - lib/em-mongo/collection.rb
60
76
  - lib/em-mongo/connection.rb
77
+ - lib/em-mongo/conversions.rb
78
+ - lib/em-mongo/database.rb
79
+ - lib/em-mongo/support.rb
61
80
  - lib/em-mongo.rb
62
81
  - spec/integration/collection_spec.rb
63
82
  - spec/integration/connection_spec.rb
83
+ - spec/integration/database_spec.rb
64
84
  has_rdoc: true
65
85
  homepage:
66
86
  licenses: []
@@ -96,3 +116,4 @@ summary: EventMachine driver for MongoDB.
96
116
  test_files:
97
117
  - spec/integration/collection_spec.rb
98
118
  - spec/integration/connection_spec.rb
119
+ - spec/integration/database_spec.rb