mingo 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mingo.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  require 'active_support/core_ext/hash/conversions'
2
2
  require 'mongo'
3
3
  require 'active_model'
4
- require 'hashie/dash'
5
4
 
6
5
  BSON::ObjectId.class_eval do
7
6
  def self.[](id)
@@ -13,14 +12,15 @@ BSON::ObjectId.class_eval do
13
12
  end
14
13
  end
15
14
 
16
- class Mingo < Hashie::Dash
15
+ class Mingo < Hash
17
16
  include ActiveModel::Conversion
18
17
  extend ActiveModel::Translation
19
18
 
19
+ autoload :Properties, 'mingo/properties'
20
20
  autoload :Cursor, 'mingo/cursor'
21
21
  autoload :Connection, 'mingo/connection'
22
22
  autoload :Finders, 'mingo/finders'
23
- autoload :ManyProxy, 'mingo/many_proxy'
23
+ autoload :Many, 'mingo/many_proxy'
24
24
  autoload :Persistence, 'mingo/persistence'
25
25
  autoload :Callbacks, 'mingo/callbacks'
26
26
  autoload :Changes, 'mingo/changes'
@@ -29,29 +29,20 @@ class Mingo < Hashie::Dash
29
29
 
30
30
  extend Connection
31
31
  extend Finders
32
-
33
32
  # highly experimental stuff
34
- def self.many(property, *args, &block)
35
- proxy_class = block_given?? Class.new(ManyProxy, &block) : ManyProxy
36
- ivar = "@#{property}"
37
-
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
- }
42
- end
43
-
44
- include Module.new {
45
- def initialize(obj = nil)
46
- if obj and obj['_id'].is_a? BSON::ObjectId
47
- # a doc loaded straight from the db
48
- merge!(obj)
49
- else
50
- super
33
+ extend Many
34
+
35
+ def initialize(obj = nil)
36
+ super()
37
+ if obj
38
+ # a doc loaded straight from the db?
39
+ if obj['_id'].is_a? BSON::ObjectId then merge!(obj)
40
+ else obj.each { |prop, value| self.send("#{prop}=", value) }
51
41
  end
52
42
  end
53
- }
54
-
43
+ end
44
+
45
+ include Properties
55
46
  include Persistence
56
47
  include Callbacks
57
48
  include Changes
@@ -60,219 +51,7 @@ class Mingo < Hashie::Dash
60
51
  self['_id']
61
52
  end
62
53
 
63
- # overwrite these to avoid checking for declared properties
64
- # (which is default behavior in Dash)
65
- def [](property)
66
- _regular_reader(property.to_s)
67
- end
68
-
69
- def []=(property, value)
70
- _regular_writer(property.to_s, value)
71
- end
72
-
73
- # keys are already strings
74
- def stringify_keys() self end
75
- alias :stringify_keys! :stringify_keys
76
-
77
54
  def ==(other)
78
55
  other.is_a?(self.class) and other.id == self.id
79
56
  end
80
57
  end
81
-
82
- if $0 == __FILE__
83
- require 'rspec'
84
-
85
- Mingo.connect('mingo')
86
-
87
- class User < Mingo
88
- property :name
89
- property :age
90
- end
91
-
92
- describe User do
93
- before :all do
94
- User.collection.remove
95
- end
96
-
97
- it "obtains an ID by saving" do
98
- user = build :name => 'Mislav'
99
- user.should_not be_persisted
100
- user.id.should be_nil
101
- user.save
102
- raw_doc(user.id)['name'].should == 'Mislav'
103
- user.should be_persisted
104
- user.id.should be_a(BSON::ObjectId)
105
- end
106
-
107
- it "tracks changes attribute" do
108
- user = build
109
- user.should_not be_changed
110
- user.name = 'Mislav'
111
- user.should be_changed
112
- user.changes.keys.should include(:name)
113
- user.name = 'Mislav2'
114
- user.changes[:name].should == [nil, 'Mislav2']
115
- user.save
116
- user.should_not be_changed
117
- end
118
-
119
- it "forgets changed attribute when reset to original value" do
120
- user = create :name => 'Mislav'
121
- user.name = 'Mislav2'
122
- user.should be_changed
123
- user.name = 'Mislav'
124
- user.should_not be_changed
125
- end
126
-
127
- it "has a human model name" do
128
- described_class.model_name.human.should == 'User'
129
- end
130
-
131
- it "can reload values from the db" do
132
- user = create :name => 'Mislav'
133
- user.update '$unset' => {:name => 1}, '$set' => {:age => 26}
134
- user.age.should be_nil
135
- user.reload
136
- user.age.should == 26
137
- user.name.should be_nil
138
- end
139
-
140
- it "saves only changed values" do
141
- user = create :name => 'Mislav', :age => 26
142
- user.update '$inc' => {:age => 1}
143
- user.name = 'Mislav2'
144
- user.save
145
- user.reload
146
- user.name.should == 'Mislav2'
147
- user.age.should == 27
148
- end
149
-
150
- it "unsets values set to nil" do
151
- user = create :name => 'Mislav', :age => 26
152
- user.age = nil
153
- user.save
154
-
155
- raw_doc(user.id).tap do |doc|
156
- doc.should_not have_key('age')
157
- doc.should have_key('name')
158
- end
159
- end
160
-
161
- context "existing doc" do
162
- before do
163
- @id = described_class.collection.insert :name => 'Mislav', :age => 26
164
- end
165
-
166
- it "finds a doc by string ID" do
167
- user = described_class.first(@id.to_s)
168
- user.id.should == @id
169
- user.name.should == 'Mislav'
170
- user.age.should == 26
171
- end
172
-
173
- it "is unchanged after loading" do
174
- user = described_class.first(@id)
175
- user.should_not be_changed
176
- user.age = 27
177
- user.should be_changed
178
- user.changes.keys.should == [:age]
179
- end
180
-
181
- it "doesn't get changed by an inspect" do
182
- user = described_class.first(@id)
183
- # triggers AS stringify_keys, which dups the Dash and writes to it,
184
- # which mutates the @changes hash from the original Dash
185
- user.inspect
186
- user.should_not be_changed
187
- end
188
- end
189
-
190
- it "returns nil for non-existing doc" do
191
- doc = described_class.first('nonexist' => 1)
192
- doc.should be_nil
193
- end
194
-
195
- it "compares with another record" do
196
- one = create :name => "One"
197
- two = create :name => "Two"
198
- one.should_not == two
199
-
200
- one_dup = described_class.first(one.id)
201
- one_dup.should == one
202
- end
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
-
266
- def build(*args)
267
- described_class.new(*args)
268
- end
269
-
270
- def create(*args)
271
- described_class.create(*args)
272
- end
273
-
274
- def raw_doc(selector)
275
- described_class.first(selector, :transformer => nil)
276
- end
277
- end
278
- end
data/lib/mingo/changes.rb CHANGED
@@ -10,20 +10,20 @@ class Mingo
10
10
  @changes = {}
11
11
  super
12
12
  end
13
+
14
+ def []=(key, value)
15
+ record_change(key, value)
16
+ super
17
+ end
13
18
 
14
19
  def changed?
15
20
  changes.any?
16
21
  end
17
22
 
18
23
  private
19
-
20
- def _regular_writer(key, value)
21
- track_change(key, value)
22
- super
23
- end
24
24
 
25
- def track_change(key, value)
26
- old_value = _regular_reader(key)
25
+ def record_change(key, value)
26
+ old_value = self[key]
27
27
  unless value == old_value
28
28
  memo = (changes[key.to_sym] ||= [old_value])
29
29
  memo[0] == value ? changes.delete(key.to_sym) : (memo[1] = value)
@@ -34,8 +34,6 @@ class Mingo
34
34
  changes.clear
35
35
  end
36
36
 
37
- private
38
-
39
37
  def values_for_update
40
38
  changes.inject('$set' => {}, '$unset' => {}) do |doc, (key, values)|
41
39
  value = values[1]
@@ -1,4 +1,16 @@
1
1
  class Mingo
2
+ module Many
3
+ def many(property, *args, &block)
4
+ proxy_class = block_given?? Class.new(ManyProxy, &block) : ManyProxy
5
+ ivar = "@#{property}"
6
+
7
+ define_method(property) {
8
+ (instance_variable_defined?(ivar) && instance_variable_get(ivar)) ||
9
+ instance_variable_set(ivar, proxy_class.new(self, property, *args))
10
+ }
11
+ end
12
+ end
13
+
2
14
  class ManyProxy
3
15
  def self.decorate_with(mod = nil, &block)
4
16
  if mod or block_given?
@@ -0,0 +1,64 @@
1
+ require 'active_support/concern'
2
+
3
+ class Mingo
4
+ module Properties
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ instance_variable_set('@properties', Array.new)
9
+ end
10
+
11
+ module ClassMethods
12
+ attr_reader :properties
13
+
14
+ def property(name, options = {})
15
+ self.properties << name.to_sym
16
+
17
+ setter_name = "#{name}="
18
+ unless method_defined?(setter_name)
19
+ class_eval <<-RUBY, __FILE__, __LINE__
20
+ def #{name}(&block)
21
+ self.[](#{name.to_s.inspect}, &block)
22
+ end
23
+
24
+ def #{setter_name}(value)
25
+ self.[]=(#{name.to_s.inspect}, value)
26
+ end
27
+ RUBY
28
+ end
29
+
30
+ if defined? @subclasses
31
+ @subclasses.each { |klass| klass.property(property_name, options) }
32
+ end
33
+ end
34
+
35
+ def inherited(klass)
36
+ super
37
+ (@subclasses ||= Array.new) << klass
38
+ klass.instance_variable_set('@properties', self.properties.dup)
39
+ end
40
+ end
41
+
42
+ def inspect
43
+ str = "<##{self.class.to_s}"
44
+ str << self.class.properties.map { |p| " #{p}=#{self.send(p).inspect}" }.join('')
45
+ str << '>'
46
+ end
47
+
48
+ def [](field, &block)
49
+ super(field.to_s, &block)
50
+ end
51
+
52
+ def []=(field, value)
53
+ super(field.to_s, value)
54
+ end
55
+
56
+ def to_hash
57
+ Hash.new.replace(self)
58
+ end
59
+
60
+ # keys are already strings
61
+ def stringify_keys!() self end
62
+ def stringify_keys() stringify_keys!.dup end
63
+ end
64
+ end
@@ -0,0 +1,196 @@
1
+ require 'rspec/autorun'
2
+ require 'mingo'
3
+
4
+ Mingo.connect('mingo')
5
+
6
+ class User < Mingo
7
+ property :name
8
+ property :age
9
+ end
10
+
11
+ describe User do
12
+ before :all do
13
+ User.collection.remove
14
+ end
15
+
16
+ it "obtains an ID by saving" do
17
+ user = build :name => 'Mislav'
18
+ user.should_not be_persisted
19
+ user.id.should be_nil
20
+ user.save
21
+ raw_doc(user.id)['name'].should == 'Mislav'
22
+ user.should be_persisted
23
+ user.id.should be_a(BSON::ObjectId)
24
+ end
25
+
26
+ it "tracks changes attribute" do
27
+ user = build
28
+ user.should_not be_changed
29
+ user.name = 'Mislav'
30
+ user.should be_changed
31
+ user.changes.keys.should include(:name)
32
+ user.name = 'Mislav2'
33
+ user.changes[:name].should == [nil, 'Mislav2']
34
+ user.save
35
+ user.should_not be_changed
36
+ end
37
+
38
+ it "forgets changed attribute when reset to original value" do
39
+ user = create :name => 'Mislav'
40
+ user.name = 'Mislav2'
41
+ user.should be_changed
42
+ user.name = 'Mislav'
43
+ user.should_not be_changed
44
+ end
45
+
46
+ it "has a human model name" do
47
+ described_class.model_name.human.should == 'User'
48
+ end
49
+
50
+ it "can reload values from the db" do
51
+ user = create :name => 'Mislav'
52
+ user.update '$unset' => {:name => 1}, '$set' => {:age => 26}
53
+ user.age.should be_nil
54
+ user.reload
55
+ user.age.should == 26
56
+ user.name.should be_nil
57
+ end
58
+
59
+ it "saves only changed values" do
60
+ user = create :name => 'Mislav', :age => 26
61
+ user.update '$inc' => {:age => 1}
62
+ user.name = 'Mislav2'
63
+ user.save
64
+ user.reload
65
+ user.name.should == 'Mislav2'
66
+ user.age.should == 27
67
+ end
68
+
69
+ it "unsets values set to nil" do
70
+ user = create :name => 'Mislav', :age => 26
71
+ user.age = nil
72
+ user.save
73
+
74
+ raw_doc(user.id).tap do |doc|
75
+ doc.should_not have_key('age')
76
+ doc.should have_key('name')
77
+ end
78
+ end
79
+
80
+ context "existing doc" do
81
+ before do
82
+ @id = described_class.collection.insert :name => 'Mislav', :age => 26
83
+ end
84
+
85
+ it "finds a doc by string ID" do
86
+ user = described_class.first(@id.to_s)
87
+ user.id.should == @id
88
+ user.name.should == 'Mislav'
89
+ user.age.should == 26
90
+ end
91
+
92
+ it "is unchanged after loading" do
93
+ user = described_class.first(@id)
94
+ user.should_not be_changed
95
+ user.age = 27
96
+ user.should be_changed
97
+ user.changes.keys.should == [:age]
98
+ end
99
+
100
+ it "doesn't get changed by an inspect" do
101
+ user = described_class.first(@id)
102
+ # triggers AS stringify_keys, which dups the Dash and writes to it,
103
+ # which mutates the @changes hash from the original Dash
104
+ user.inspect
105
+ user.should_not be_changed
106
+ end
107
+ end
108
+
109
+ it "returns nil for non-existing doc" do
110
+ doc = described_class.first('nonexist' => 1)
111
+ doc.should be_nil
112
+ end
113
+
114
+ it "compares with another record" do
115
+ one = create :name => "One"
116
+ two = create :name => "Two"
117
+ one.should_not == two
118
+
119
+ one_dup = described_class.first(one.id)
120
+ one_dup.should == one
121
+ end
122
+
123
+ it "returns a custom cursor" do
124
+ cursor = described_class.collection.find({})
125
+ cursor.should respond_to(:empty?)
126
+ end
127
+
128
+ context "cursor reverse" do
129
+ it "can't reverse no order" do
130
+ lambda {
131
+ described_class.find({}).reverse
132
+ }.should raise_error
133
+ end
134
+
135
+ it "reverses simple order" do
136
+ cursor = described_class.find({}, :sort => :name).reverse
137
+ cursor.order.should == [[:name, -1]]
138
+ end
139
+
140
+ it "reverses simple desc order" do
141
+ cursor = described_class.find({}, :sort => [:name, :desc]).reverse
142
+ cursor.order.should == [[:name, 1]]
143
+ end
144
+
145
+ it "reverses simple nested desc order" do
146
+ cursor = described_class.find({}, :sort => [[:name, :desc]]).reverse
147
+ cursor.order.should == [[:name, 1]]
148
+ end
149
+
150
+ it "can't reverse complex order" do
151
+ lambda {
152
+ described_class.find({}, :sort => [[:name, :desc], [:other, :asc]]).reverse
153
+ }.should raise_error
154
+ end
155
+
156
+ it "reverses find by ids" do
157
+ cursor = described_class.find([1,3,5]).reverse
158
+ cursor.selector.should == {:_id => {"$in" => [5,3,1]}}
159
+ end
160
+ end
161
+
162
+ context "find by ids" do
163
+ before :all do
164
+ @docs = [create, create, create]
165
+ @doc1, @doc2, @doc3 = *@docs
166
+ end
167
+
168
+ it "orders results by ids" do
169
+ results = described_class.find([@doc3.id, @doc1.id, @doc2.id]).to_a
170
+ results.should == [@doc3, @doc1, @doc2]
171
+ end
172
+
173
+ it "handles limit + skip" do
174
+ cursor = described_class.find([@doc3.id, @doc1.id, @doc2.id]).limit(1).skip(2)
175
+ cursor.to_a.should == [@doc2]
176
+ end
177
+
178
+ it "returns correct count" do
179
+ cursor = described_class.find([@doc3.id, @doc1.id, @doc2.id]).limit(1).skip(2)
180
+ cursor.next_document
181
+ cursor.count.should == 3
182
+ end
183
+ end
184
+
185
+ def build(*args)
186
+ described_class.new(*args)
187
+ end
188
+
189
+ def create(*args)
190
+ described_class.create(*args)
191
+ end
192
+
193
+ def raw_doc(selector)
194
+ described_class.first(selector, :transformer => nil)
195
+ end
196
+ end
metadata CHANGED
@@ -1,44 +1,37 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: mingo
3
- version: !ruby/object:Gem::Version
4
- version: 0.3.2
3
+ version: !ruby/object:Gem::Version
5
4
  prerelease:
5
+ version: 0.4.0
6
6
  platform: ruby
7
- authors:
8
- - Mislav Marohnić
7
+ authors:
8
+ - "Mislav Marohni\xC4\x87"
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-06-19 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
12
+
13
+ date: 2011-07-24 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
15
16
  name: mongo
16
- requirement: &70206780980160 !ruby/object:Gem::Requirement
17
- none: false
18
- requirements:
19
- - - ! '>='
20
- - !ruby/object:Gem::Version
21
- version: '1.3'
22
- type: :runtime
23
17
  prerelease: false
24
- version_requirements: *70206780980160
25
- - !ruby/object:Gem::Dependency
26
- name: hashie
27
- requirement: &70206780979680 !ruby/object:Gem::Requirement
18
+ requirement: &id001 !ruby/object:Gem::Requirement
28
19
  none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: 0.4.0
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "1.3"
33
24
  type: :runtime
34
- prerelease: false
35
- version_requirements: *70206780979680
25
+ version_requirements: *id001
36
26
  description: Mingo is a minimal document-object mapper for MongoDB.
37
27
  email: mislav.marohnic@gmail.com
38
28
  executables: []
29
+
39
30
  extensions: []
31
+
40
32
  extra_rdoc_files: []
41
- files:
33
+
34
+ files:
42
35
  - Rakefile
43
36
  - lib/mingo/callbacks.rb
44
37
  - lib/mingo/changes.rb
@@ -48,30 +41,36 @@ files:
48
41
  - lib/mingo/many_proxy.rb
49
42
  - lib/mingo/pagination.rb
50
43
  - lib/mingo/persistence.rb
44
+ - lib/mingo/properties.rb
51
45
  - lib/mingo/timestamps.rb
52
46
  - lib/mingo.rb
47
+ - spec/mingo_spec.rb
53
48
  homepage: http://github.com/mislav/mingo
54
49
  licenses: []
50
+
55
51
  post_install_message:
56
52
  rdoc_options: []
57
- require_paths:
53
+
54
+ require_paths:
58
55
  - lib
59
- required_ruby_version: !ruby/object:Gem::Requirement
56
+ required_ruby_version: !ruby/object:Gem::Requirement
60
57
  none: false
61
- requirements:
62
- - - ! '>='
63
- - !ruby/object:Gem::Version
64
- version: '0'
65
- required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
63
  none: false
67
- requirements:
68
- - - ! '>='
69
- - !ruby/object:Gem::Version
70
- version: '0'
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
71
68
  requirements: []
69
+
72
70
  rubyforge_project:
73
71
  rubygems_version: 1.8.5
74
72
  signing_key:
75
73
  specification_version: 3
76
74
  summary: Minimal Mongo
77
75
  test_files: []
76
+