mingo 0.2.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ class Mingo
2
+ module Connection
3
+ attr_writer :db, :collection
4
+
5
+ def db
6
+ @db || superclass.db
7
+ end
8
+
9
+ def connect(dbname_or_uri)
10
+ self.collection = nil
11
+ self.db = if dbname_or_uri.index('mongodb://') == 0
12
+ connection = Mongo::Connection.from_uri(dbname_or_uri)
13
+ connection.db(connection.auths.last['db_name'])
14
+ else
15
+ Mongo::Connection.new.db(dbname_or_uri)
16
+ end
17
+ end
18
+
19
+ def collection_name
20
+ self.name
21
+ end
22
+
23
+ def collection
24
+ @collection ||= db.collection(collection_name).tap { |col|
25
+ col.extend Cursor::CollectionPlugin
26
+ }
27
+ end
28
+ end
29
+ end
data/lib/mingo/cursor.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  class Mingo
2
+ # Custom Cursor subclass.
2
3
  # TODO: contribute this to the official driver
3
4
  class Cursor < Mongo::Cursor
4
5
  module CollectionPlugin
5
- def find(selector={}, opts={})
6
- opts = opts.dup
7
- convert = opts.delete(:convert)
8
- cursor = Cursor.from_mongo(super(selector, opts), convert)
6
+ def find(*args)
7
+ cursor = Cursor.from_mongo(super(*args))
9
8
 
10
9
  if block_given?
11
10
  yield cursor
@@ -17,34 +16,96 @@ class Mingo
17
16
  end
18
17
  end
19
18
 
20
- def self.from_mongo(cursor, convert)
21
- new(cursor.collection, :convert => convert).tap do |sub|
19
+ def self.from_mongo(cursor)
20
+ new(cursor.collection).tap do |sub|
22
21
  cursor.instance_variables.each { |ivar|
23
22
  sub.instance_variable_set(ivar, cursor.instance_variable_get(ivar))
24
23
  }
25
24
  end
26
25
  end
27
26
 
28
- def initialize(collection, options={})
29
- super
30
- @convert = options[:convert]
27
+ def empty?
28
+ !has_next?
29
+ end
30
+
31
+ def by_ids?
32
+ Hash === selector[:_id] && selector[:_id]["$in"]
31
33
  end
32
34
 
33
35
  def next_document
34
- convert_document super
36
+ if !@query_run && by_ids? && !order
37
+ limit_ids do
38
+ preload_cache
39
+ sort_cache_by_ids
40
+ end
41
+ end
42
+ super
35
43
  end
36
44
 
37
- def empty?
38
- !has_next?
45
+ def reverse
46
+ check_modifiable
47
+ if by_ids? and !order
48
+ selector[:_id]["$in"] = selector[:_id]["$in"].reverse
49
+ self
50
+ elsif order && (!(Array === order) || !(Array === order.first) || order.size == 1)
51
+ if Array === order
52
+ field, dir = *order.flatten
53
+ dir = Mongo::Conversions::ASCENDING_CONVERSION.include?(dir.to_s) ? -1 : 1
54
+ else
55
+ field = order
56
+ dir = -1
57
+ end
58
+ sort(field, dir)
59
+ else
60
+ raise "can't reverse complex query"
61
+ end
39
62
  end
40
63
 
41
64
  private
42
65
 
43
- def convert_document(doc)
44
- if @convert.nil? or doc.nil? then doc
45
- elsif @convert.respond_to?(:call) then @convert.call(doc)
46
- else @convert.new(doc)
66
+ def limit_ids
67
+ if @limit > 0 || @skip > 0
68
+ ids = selector[:_id]["$in"]
69
+ old_skip = @skip
70
+ selector[:_id]["$in"] = ids[@skip, @limit > 0 ? @limit : ids.size]
71
+ @skip = 0
72
+ begin
73
+ yield
74
+ ensure
75
+ @skip = old_skip
76
+ selector[:_id]["$in"] = ids
77
+ end
78
+ else
79
+ yield
80
+ end
81
+ end
82
+
83
+ def preload_cache
84
+ begin
85
+ refresh
86
+ end until @cursor_id.zero? || closed? || @n_received.to_i < 1
87
+ end
88
+
89
+ def sort_cache_by_ids
90
+ ids = selector[:_id]["$in"]
91
+ results = []
92
+
93
+ index = @cache.inject({}) do |all, doc|
94
+ if doc["$err"]
95
+ results << doc
96
+ else
97
+ all[doc["_id"]] = doc
98
+ end
99
+ all
100
+ end
101
+
102
+ ids.each do |id|
103
+ if doc = index[id]
104
+ results << doc
105
+ end
47
106
  end
107
+
108
+ @cache = results
48
109
  end
49
110
  end
50
111
  end
@@ -0,0 +1,17 @@
1
+ class Mingo
2
+ module Finders
3
+ def first(id_or_selector = nil, options = {})
4
+ unless id_or_selector.nil? or Hash === id_or_selector
5
+ id_or_selector = BSON::ObjectId[id_or_selector]
6
+ end
7
+ options = { :transformer => lambda {|doc| self.new(doc)} }.update(options)
8
+ collection.find_one(id_or_selector, options)
9
+ end
10
+
11
+ def find(selector = {}, options = {}, &block)
12
+ selector = {:_id => {'$in' => selector}} if Array === selector
13
+ options = { :transformer => lambda {|doc| self.new(doc)} }.update(options)
14
+ collection.find(selector, options, &block)
15
+ end
16
+ end
17
+ end
@@ -71,8 +71,8 @@ class Mingo
71
71
  !!@loaded
72
72
  end
73
73
 
74
- def find_by_ids(ids)
75
- @model.find_by_ids(ids, {}, find_options)
74
+ def find(selector = {}, options = {}, &block)
75
+ @model.find(selector, find_options.merge(options), &block)
76
76
  end
77
77
 
78
78
  def respond_to?(method, priv = false)
@@ -122,7 +122,7 @@ class Mingo
122
122
  decorate_block = self.class.decorate_each
123
123
 
124
124
  if decorator or decorate_block
125
- {:convert => lambda { |doc|
125
+ {:transformer => lambda { |doc|
126
126
  @model.new(doc).tap do |obj|
127
127
  obj.extend decorator if decorator
128
128
  if decorate_block
@@ -152,7 +152,7 @@ class Mingo
152
152
 
153
153
  def load_collection
154
154
  @loaded ||= if self.object_ids.empty? then []
155
- else find_by_ids(self.object_ids)
155
+ else find(self.object_ids)
156
156
  end
157
157
  end
158
158
 
@@ -0,0 +1,29 @@
1
+ require 'will_paginate/finders/base'
2
+
3
+ class Mingo
4
+ module Pagination
5
+ def self.extended(base)
6
+ klass = base::Cursor
7
+ unless klass.instance_methods.map(&:to_sym).include? :paginate
8
+ klass.send(:include, PaginatedCursor)
9
+ end
10
+ end
11
+
12
+ def paginate(options)
13
+ find.paginate(options)
14
+ end
15
+
16
+ module PaginatedCursor
17
+ include WillPaginate::Finders::Base
18
+
19
+ def wp_query(options, pager, args)
20
+ self.limit pager.per_page
21
+ self.skip pager.offset
22
+ pager.replace self.to_a
23
+ pager.total_entries = self.count unless pager.total_entries
24
+ end
25
+ end
26
+ end
27
+
28
+ extend Pagination
29
+ end
@@ -39,7 +39,7 @@ class Mingo
39
39
  end
40
40
 
41
41
  def reload
42
- doc = self.class.first(id, :convert => nil)
42
+ doc = self.class.first(id, :transformer => nil)
43
43
  replace doc
44
44
  end
45
45
 
data/lib/mingo.rb CHANGED
@@ -18,78 +18,27 @@ class Mingo < Hashie::Dash
18
18
  extend ActiveModel::Translation
19
19
 
20
20
  autoload :Cursor, 'mingo/cursor'
21
+ autoload :Connection, 'mingo/connection'
22
+ autoload :Finders, 'mingo/finders'
21
23
  autoload :ManyProxy, 'mingo/many_proxy'
22
24
  autoload :Persistence, 'mingo/persistence'
23
25
  autoload :Callbacks, 'mingo/callbacks'
24
26
  autoload :Changes, 'mingo/changes'
25
27
  autoload :Timestamps, 'mingo/timestamps'
28
+ autoload :Pagination, 'mingo/pagination'
26
29
 
27
- class << self
28
- attr_writer :db, :collection
29
-
30
- def db
31
- @db || superclass.db
32
- end
33
-
34
- def connect(dbname_or_uri)
35
- self.collection = nil
36
- self.db = if dbname_or_uri.index('mongodb://') == 0
37
- connection = Mongo::Connection.from_uri(dbname_or_uri)
38
- connection.db(connection.auths.last['db_name'])
39
- else
40
- Mongo::Connection.new.db(dbname_or_uri)
41
- end
42
- end
43
-
44
- def collection_name
45
- self.name
46
- end
47
-
48
- def collection
49
- @collection ||= db.collection(collection_name).tap { |col|
50
- col.extend Cursor::CollectionPlugin
51
- }
52
- end
53
-
54
- def first(id_or_selector = nil, options = {})
55
- unless id_or_selector.nil? or Hash === id_or_selector
56
- id_or_selector = BSON::ObjectId[id_or_selector]
57
- end
58
- collection.find_one(id_or_selector, {:convert => self}.update(options))
59
- end
60
-
61
- def find(selector = {}, options = {}, &block)
62
- collection.find(selector, {:convert => self}.update(options), &block)
63
- end
64
-
65
- def find_by_ids(object_ids, query = {}, options = {})
66
- find({:_id => {'$in' => object_ids}}.update(query), options)
67
- end
68
-
69
- def find_ordered_ids(object_ids, query = {}, options = {})
70
- indexed = find_by_ids(object_ids, query, options).inject({}) do |hash, object|
71
- hash[object.id] = object
72
- hash
73
- end
74
-
75
- object_ids.map { |id| indexed[id] }
76
- end
77
-
78
- def paginate_ids(object_ids, paginate_options, options = {})
79
- object_ids.paginate(paginate_options).tap do |collection|
80
- collection.replace find_ordered_ids(collection, {}, options)
81
- end
82
- end
30
+ extend Connection
31
+ extend Finders
32
+
33
+ # highly experimental stuff
34
+ def self.many(property, *args, &block)
35
+ proxy_class = block_given?? Class.new(ManyProxy, &block) : ManyProxy
36
+ ivar = "@#{property}"
83
37
 
84
- def many(property, *args, &block)
85
- proxy_class = block_given?? Class.new(ManyProxy, &block) : ManyProxy
86
- ivar = "@#{property}"
87
-
88
- define_method(property) {
89
- (instance_variable_defined?(ivar) && instance_variable_get(ivar)) ||
90
- instance_variable_set(ivar, proxy_class.new(self, property, *args))
91
- }
92
- end
38
+ define_method(property) {
39
+ (instance_variable_defined?(ivar) && instance_variable_get(ivar)) ||
40
+ instance_variable_set(ivar, proxy_class.new(self, property, *args))
41
+ }
93
42
  end
94
43
 
95
44
  include Module.new {
@@ -252,6 +201,68 @@ if $0 == __FILE__
252
201
  one_dup.should == one
253
202
  end
254
203
 
204
+ it "returns a custom cursor" do
205
+ cursor = described_class.collection.find({})
206
+ cursor.should respond_to(:empty?)
207
+ end
208
+
209
+ context "cursor reverse" do
210
+ it "can't reverse no order" do
211
+ lambda {
212
+ described_class.find({}).reverse
213
+ }.should raise_error
214
+ end
215
+
216
+ it "reverses simple order" do
217
+ cursor = described_class.find({}, :sort => :name).reverse
218
+ cursor.order.should == [[:name, -1]]
219
+ end
220
+
221
+ it "reverses simple desc order" do
222
+ cursor = described_class.find({}, :sort => [:name, :desc]).reverse
223
+ cursor.order.should == [[:name, 1]]
224
+ end
225
+
226
+ it "reverses simple nested desc order" do
227
+ cursor = described_class.find({}, :sort => [[:name, :desc]]).reverse
228
+ cursor.order.should == [[:name, 1]]
229
+ end
230
+
231
+ it "can't reverse complex order" do
232
+ lambda {
233
+ described_class.find({}, :sort => [[:name, :desc], [:other, :asc]]).reverse
234
+ }.should raise_error
235
+ end
236
+
237
+ it "reverses find by ids" do
238
+ cursor = described_class.find([1,3,5]).reverse
239
+ cursor.selector.should == {:_id => {"$in" => [5,3,1]}}
240
+ end
241
+ end
242
+
243
+ context "find by ids" do
244
+ before :all do
245
+ @docs = [create, create, create]
246
+ @doc1, @doc2, @doc3 = *@docs
247
+ end
248
+
249
+ it "orders results by ids" do
250
+ results = described_class.find([@doc3.id, @doc1.id, @doc2.id]).to_a
251
+ results.should == [@doc3, @doc1, @doc2]
252
+ end
253
+
254
+ it "handles limit + skip" do
255
+ cursor = described_class.find([@doc3.id, @doc1.id, @doc2.id]).limit(1).skip(2)
256
+ cursor.to_a.should == [@doc2]
257
+ end
258
+
259
+ it "returns correct count" do
260
+ cursor = described_class.find([@doc3.id, @doc1.id, @doc2.id]).limit(1).skip(2)
261
+ cursor.next_document
262
+ cursor.count.should == 3
263
+ end
264
+ end
265
+
255
266
  def build(*args)
256
267
  described_class.new(*args)
257
268
  end
@@ -261,7 +272,7 @@ if $0 == __FILE__
261
272
  end
262
273
 
263
274
  def raw_doc(selector)
264
- described_class.first(selector, :convert => nil)
275
+ described_class.first(selector, :transformer => nil)
265
276
  end
266
277
  end
267
278
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mingo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-04-26 00:00:00.000000000Z
12
+ date: 2011-06-19 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mongo
16
- requirement: &2157911220 !ruby/object:Gem::Requirement
16
+ requirement: &70206780980160 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '1.3'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2157911220
24
+ version_requirements: *70206780980160
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: hashie
27
- requirement: &2157910740 !ruby/object:Gem::Requirement
27
+ requirement: &70206780979680 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: 0.4.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2157910740
35
+ version_requirements: *70206780979680
36
36
  description: Mingo is a minimal document-object mapper for MongoDB.
37
37
  email: mislav.marohnic@gmail.com
38
38
  executables: []
@@ -42,8 +42,11 @@ files:
42
42
  - Rakefile
43
43
  - lib/mingo/callbacks.rb
44
44
  - lib/mingo/changes.rb
45
+ - lib/mingo/connection.rb
45
46
  - lib/mingo/cursor.rb
47
+ - lib/mingo/finders.rb
46
48
  - lib/mingo/many_proxy.rb
49
+ - lib/mingo/pagination.rb
47
50
  - lib/mingo/persistence.rb
48
51
  - lib/mingo/timestamps.rb
49
52
  - lib/mingo.rb
@@ -67,9 +70,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
70
  version: '0'
68
71
  requirements: []
69
72
  rubyforge_project:
70
- rubygems_version: 1.7.2
73
+ rubygems_version: 1.8.5
71
74
  signing_key:
72
75
  specification_version: 3
73
76
  summary: Minimal Mongo
74
77
  test_files: []
75
- has_rdoc: