mingo 0.3.2 → 0.4.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/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
+