mingo 0.2.0 → 0.3.2

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.
@@ -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: