mongomodel 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source "http://gemcutter.org"
1
+ source "http://rubygems.org"
2
2
  git "git://github.com/rails/rails.git"
3
3
 
4
- gem "activemodel"
5
- gem "activesupport"
4
+ gem "activemodel", ">= 3.0.0.beta3"
5
+ gem "activesupport", ">= 3.0.0.beta3"
6
6
 
7
- gem "mongo"
8
- gem "bson"
9
- gem "bson_ext"
7
+ gem "mongo", '>= 0.20.1'
8
+ gem "bson", '>= 0.20.1'
9
+ gem "bson_ext", '>= 0.20.1'
10
10
 
11
11
  gem "rspec"
data/Rakefile CHANGED
@@ -1,18 +1,28 @@
1
- require 'spec/rake/spectask'
2
1
  require 'rake/rdoctask'
3
2
 
4
3
  task :default => :spec
5
4
 
6
- Spec::Rake::SpecTask.new(:spec) do |t|
7
- t.libs << 'lib'
8
- t.spec_opts = ['--options', "#{File.expand_path(File.dirname(__FILE__))}/spec/spec.opts"]
9
- end
10
-
11
- namespace :spec do
12
- desc "Run specs in nested documenting format"
13
- Spec::Rake::SpecTask.new(:doc) do |t|
5
+ begin
6
+ require 'spec/rake/spectask'
7
+ desc 'Run the specs'
8
+ Spec::Rake::SpecTask.new(:spec) do |t|
14
9
  t.libs << 'lib'
15
- t.spec_opts = ['--options', "#{File.expand_path(File.dirname(__FILE__))}/spec/specdoc.opts"]
10
+ t.spec_opts = ['--options', "#{File.expand_path(File.dirname(__FILE__))}/spec/spec.opts"]
11
+ end
12
+
13
+ namespace :spec do
14
+ desc "Run specs in nested documenting format"
15
+ Spec::Rake::SpecTask.new(:doc) do |t|
16
+ t.libs << 'lib'
17
+ t.spec_opts = ['--options', "#{File.expand_path(File.dirname(__FILE__))}/spec/specdoc.opts"]
18
+ end
19
+ end
20
+ rescue LoadError
21
+ task :spec do
22
+ STDERR.puts "You must have rspec to run the tests"
23
+ end
24
+ namespace :spec do
25
+ task :doc => :spec
16
26
  end
17
27
  end
18
28
 
@@ -40,9 +50,10 @@ begin
40
50
  gem.add_dependency('activemodel', '>= 3.0.0.beta3')
41
51
  gem.add_dependency('mongo', '>= 0.20.1')
42
52
  gem.add_dependency('bson', '>= 0.20.1')
53
+ gem.add_development_dependency('rspec', '>= 1.3.0')
43
54
  end
44
55
 
45
56
  Jeweler::GemcutterTasks.new
46
57
  rescue LoadError
47
- puts "Jeweler not available. Install it with: gem install jeweler"
58
+ STDERR.puts "Jeweler not available. Install it with: gem install jeweler"
48
59
  end
@@ -26,6 +26,7 @@ module MongoModel
26
26
  autoload :PrettyInspect, 'mongomodel/concerns/pretty_inspect'
27
27
  autoload :RecordStatus, 'mongomodel/concerns/record_status'
28
28
  autoload :AbstractClass, 'mongomodel/concerns/abstract_class'
29
+ autoload :DocumentParent, 'mongomodel/concerns/document_parent'
29
30
  autoload :ActiveModelCompatibility, 'mongomodel/concerns/activemodel'
30
31
 
31
32
  autoload :Reference, 'mongomodel/support/reference'
@@ -21,7 +21,8 @@ module MongoModel
21
21
  property = properties_as[k.to_s]
22
22
 
23
23
  if property
24
- self[property.name] = property.from_mongo(v)
24
+ child = self[property.name] = property.from_mongo(v)
25
+ child.parent_document = instance if child.respond_to?(:parent_document=)
25
26
  else
26
27
  self[k.to_sym] = v
27
28
  end
@@ -3,7 +3,10 @@ module MongoModel
3
3
  module Typecasting
4
4
  def []=(key, value)
5
5
  values_before_typecast[key] = value
6
- super(key, typecast(key, value))
6
+
7
+ result = super(key, typecast(key, value))
8
+ result.parent_document = instance if result.respond_to?(:parent_document=)
9
+ result
7
10
  end
8
11
 
9
12
  # Check if key has a value that typecasts to true.
@@ -16,7 +16,7 @@ module MongoModel
16
16
 
17
17
  # Wrap write_attribute to remember original attribute value.
18
18
  def write_attribute(attr, value)
19
- attr = attr.to_sym
19
+ attr = attr.to_s
20
20
 
21
21
  # The attribute already has an unsaved change.
22
22
  if changed_attributes.include?(attr)
@@ -56,9 +56,11 @@ module MongoModel
56
56
 
57
57
  module ClassMethods
58
58
  def from_mongo(hash)
59
- doc = class_for_type(hash['_type']).new
60
- doc.attributes.from_mongo!(hash)
61
- doc
59
+ if hash
60
+ doc = class_for_type(hash['_type']).new
61
+ doc.attributes.from_mongo!(hash)
62
+ doc
63
+ end
62
64
  end
63
65
 
64
66
  private
@@ -286,8 +286,8 @@ module MongoModel
286
286
 
287
287
  private
288
288
  def nest_embedded_callbacks(kind, *args, &block)
289
- embedded_documents.inject(block) do |block, doc|
290
- Proc.new { doc.run_callbacks(kind, *args, &block) }
289
+ embedded_documents.inject(block) do |callback, doc|
290
+ Proc.new { doc.run_callbacks(kind, *args, &callback) }
291
291
  end
292
292
  end
293
293
  end
@@ -0,0 +1,13 @@
1
+ module MongoModel
2
+ module DocumentParent
3
+ extend ActiveSupport::Concern
4
+
5
+ def parent_document
6
+ @_parent_document.is_a?(Proc) ? @_parent_document.call(self) : @_parent_document
7
+ end
8
+
9
+ def parent_document=(doc)
10
+ @_parent_document = doc
11
+ end
12
+ end
13
+ end
@@ -10,10 +10,27 @@ module MongoModel
10
10
  class_inheritable_writer :collection_name
11
11
  end
12
12
 
13
+ # Reload the document from the database. If the document
14
+ # hasn't been saved, this method will raise an error.
15
+ def reload
16
+ reloaded = self.class.unscoped.find(id)
17
+
18
+ attributes.clear
19
+ attributes.from_mongo!(reloaded.attributes.to_mongo)
20
+
21
+ associations.values.each do |association|
22
+ association.proxy.reset
23
+ end
24
+
25
+ self
26
+ end
27
+
28
+ # Save the document to the database. Returns +true+ on success.
13
29
  def save
14
30
  create_or_update
15
31
  end
16
32
 
33
+ # Save the document to the database. Raises a DocumentNotSaved exception if it fails.
17
34
  def save!
18
35
  create_or_update || raise(DocumentNotSaved)
19
36
  end
@@ -23,7 +40,8 @@ module MongoModel
23
40
  set_destroyed(true)
24
41
  freeze
25
42
  end
26
-
43
+
44
+ # Remove the document from the database.
27
45
  def destroy
28
46
  delete
29
47
  end
@@ -50,6 +68,8 @@ module MongoModel
50
68
  self.class.database
51
69
  end
52
70
 
71
+ # Generate a new BSON::ObjectID for the record.
72
+ # Override in subclasses for custom ID generation.
53
73
  def generate_id
54
74
  ::BSON::ObjectID.new.to_s
55
75
  end
@@ -67,7 +87,7 @@ module MongoModel
67
87
 
68
88
  def from_mongo(document)
69
89
  instance = super
70
- instance.send(:instantiate, document)
90
+ instance.send(:instantiate, document) if instance
71
91
  instance
72
92
  end
73
93
 
@@ -109,7 +129,7 @@ module MongoModel
109
129
  def update
110
130
  save_to_collection
111
131
  end
112
-
132
+
113
133
  def save_to_collection
114
134
  collection.save(to_mongo, :safe => self.class.save_safely?)
115
135
  set_new_record(false)
@@ -12,7 +12,7 @@ module MongoModel
12
12
 
13
13
  module ClassMethods
14
14
  delegate :find, :first, :last, :all, :exists?, :count, :to => :scoped
15
- delegate :delete, :delete_all, :destroy, :destroy_all, :to => :scoped
15
+ delegate :update, :update_all, :delete, :delete_all, :destroy, :destroy_all, :to => :scoped
16
16
  delegate :select, :order, :where, :limit, :offset, :from, :to => :scoped
17
17
 
18
18
  def unscoped
@@ -28,9 +28,11 @@ module MongoModel
28
28
  include Timestamps
29
29
  include PrettyInspect
30
30
  include AbstractClass
31
+ include DocumentParent
31
32
 
32
33
  # Allow Collection class to be used in property definitions
33
34
  Collection = MongoModel::Collection
35
+ extend Collection::PropertyDefaults
34
36
 
35
37
  undef_method :type if method_defined?(:type)
36
38
  property :type, String, :as => '_type', :default => lambda { |doc| doc.class.name }, :protected => true
@@ -1,20 +1,40 @@
1
1
  module MongoModel
2
2
  class Collection < Array
3
+ module PropertyDefaults
4
+ def property(name, *args, &block) #:nodoc:
5
+ property = super(name, *args, &block)
6
+
7
+ if property.type <= Collection
8
+ property.options[:default] ||= property.type.new
9
+ end
10
+
11
+ property
12
+ end
13
+ end
14
+
3
15
  ARRAY_CONVERTER = Types.converter_for(Array)
4
16
 
5
17
  class_inheritable_accessor :type
6
18
  self.type = Object
7
19
 
20
+ include DocumentParent
21
+
8
22
  def initialize(array=[])
9
- super(array.map { |i| convert(i) })
23
+ super(array.map { |i| convert_for_add(i) })
10
24
  end
11
25
 
12
26
  def []=(index, value)
13
- super(index, convert(value))
27
+ super(index, convert_for_add(value))
14
28
  end
15
29
 
16
30
  def <<(value)
17
- super(convert(value))
31
+ super(convert_for_add(value))
32
+ end
33
+
34
+ def build (value)
35
+ value = convert(value)
36
+ self << value
37
+ value
18
38
  end
19
39
 
20
40
  def +(other)
@@ -22,7 +42,7 @@ module MongoModel
22
42
  end
23
43
 
24
44
  def concat(values)
25
- super(values.map { |v| convert(v) })
45
+ super(values.map { |v| convert_for_add(v) })
26
46
  end
27
47
 
28
48
  def delete(value)
@@ -38,11 +58,11 @@ module MongoModel
38
58
  end
39
59
 
40
60
  def insert(index, value)
41
- super(index, convert(value))
61
+ super(index, convert_for_add(value))
42
62
  end
43
63
 
44
64
  def push(*values)
45
- super(*values.map { |v| convert(v) })
65
+ super(*values.map { |v| convert_for_add(v) })
46
66
  end
47
67
 
48
68
  def rindex(value)
@@ -50,7 +70,7 @@ module MongoModel
50
70
  end
51
71
 
52
72
  def unshift(*values)
53
- super(*values.map { |v| convert(v) })
73
+ super(*values.map { |v| convert_for_add(v) })
54
74
  end
55
75
 
56
76
  def to_mongo
@@ -69,7 +89,31 @@ module MongoModel
69
89
  "Collection[#{type}]"
70
90
  end
71
91
  end
72
-
92
+
93
+ # Create a new MongoModel::Collection class with the type set to the specified class.
94
+ # This allows you declare arrays of embedded documents like:
95
+ #
96
+ # class Thing < MongoModel::EmbeddedDocument
97
+ # property :name, String
98
+ # end
99
+ #
100
+ # class MyModel < MongoModel::Document
101
+ # property :things, Collection[Thing]
102
+ # end
103
+ #
104
+ # If you don't declare a default on a property that has a Collection type, the
105
+ # default will be automatically set to an empty Collection.
106
+ #
107
+ # This method is aliased as #of, so you can use the alternative syntax:
108
+ # property :things, Collection.of(Thing)
109
+ #
110
+ # Examples:
111
+ #
112
+ # model = MyModel.new
113
+ # model.things # => []
114
+ # model.things << {:name => "Thing One"}
115
+ # model.things # => [#<Thing name: "Thing One">]
116
+ # model.things = [{:name => "Thing Two"}] # => [#<Thing name: "Thing Two">]
73
117
  def [](type)
74
118
  @collection_class_cache ||= {}
75
119
  @collection_class_cache[type] ||= begin
@@ -79,6 +123,8 @@ module MongoModel
79
123
  end
80
124
  end
81
125
 
126
+ alias of []
127
+
82
128
  def from_mongo(array)
83
129
  new(array.map { |i| instantiate(i) })
84
130
  end
@@ -102,6 +148,12 @@ module MongoModel
102
148
  converter.cast(value)
103
149
  end
104
150
 
151
+ def convert_for_add(value)
152
+ result = convert(value)
153
+ result.parent_document = lambda { parent_document } if result.respond_to?(:parent_document=)
154
+ result
155
+ end
156
+
105
157
  def converter
106
158
  self.class.converter
107
159
  end
@@ -83,6 +83,17 @@ module MongoModel
83
83
  reset
84
84
  end
85
85
 
86
+ def update_all(updates)
87
+ selector = MongoOptions.new(klass, :conditions => finder_conditions).selector
88
+ collection.update(selector, { "$set" => updates }, { :multi => true })
89
+ reset
90
+ end
91
+
92
+ def update(ids, updates)
93
+ where(ids_to_conditions(ids)).update_all(updates)
94
+ reset
95
+ end
96
+
86
97
  def loaded?
87
98
  @loaded
88
99
  end
@@ -174,7 +185,7 @@ module MongoModel
174
185
  end
175
186
 
176
187
  def ids_to_conditions(ids)
177
- ids.flatten!
188
+ ids = Array.wrap(ids).flatten
178
189
 
179
190
  if ids.size == 1
180
191
  { :id => ids.first.to_s }
@@ -5,7 +5,11 @@ module MongoModel
5
5
  module Types
6
6
  class Time < Object
7
7
  def cast(value)
8
- value.to_time.utc rescue nil
8
+ time = value.to_time.utc
9
+ # BSON only stores time accurate to the millisecond
10
+ ::Time.at((time.to_f * 1000).floor / 1000.0)
11
+ rescue
12
+ nil
9
13
  end
10
14
  end
11
15
  end
@@ -1,3 +1,3 @@
1
1
  module MongoModel
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{mongomodel}
8
- s.version = "0.2.2"
8
+ s.version = "0.2.3"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Sam Pohlenz"]
12
- s.date = %q{2010-04-26}
12
+ s.date = %q{2010-04-30}
13
13
  s.default_executable = %q{console}
14
14
  s.description = %q{MongoModel is a MongoDB ORM for Ruby/Rails similar to ActiveRecord and DataMapper.}
15
15
  s.email = %q{sam@sampohlenz.com}
@@ -47,6 +47,7 @@ Gem::Specification.new do |s|
47
47
  "lib/mongomodel/concerns/attribute_methods/write.rb",
48
48
  "lib/mongomodel/concerns/attributes.rb",
49
49
  "lib/mongomodel/concerns/callbacks.rb",
50
+ "lib/mongomodel/concerns/document_parent.rb",
50
51
  "lib/mongomodel/concerns/logging.rb",
51
52
  "lib/mongomodel/concerns/pretty_inspect.rb",
52
53
  "lib/mongomodel/concerns/properties.rb",
@@ -145,8 +146,7 @@ Gem::Specification.new do |s|
145
146
  "spec/support/matchers/find_with.rb",
146
147
  "spec/support/matchers/respond_to_boolean.rb",
147
148
  "spec/support/matchers/run_callbacks.rb",
148
- "spec/support/models.rb",
149
- "spec/support/time.rb"
149
+ "spec/support/models.rb"
150
150
  ]
151
151
  s.homepage = %q{http://github.com/spohlenz/mongomodel}
152
152
  s.rdoc_options = ["--charset=UTF-8"]
@@ -202,8 +202,7 @@ Gem::Specification.new do |s|
202
202
  "spec/support/matchers/find_with.rb",
203
203
  "spec/support/matchers/respond_to_boolean.rb",
204
204
  "spec/support/matchers/run_callbacks.rb",
205
- "spec/support/models.rb",
206
- "spec/support/time.rb"
205
+ "spec/support/models.rb"
207
206
  ]
208
207
 
209
208
  if s.respond_to? :specification_version then
@@ -215,17 +214,20 @@ Gem::Specification.new do |s|
215
214
  s.add_runtime_dependency(%q<activemodel>, [">= 3.0.0.beta3"])
216
215
  s.add_runtime_dependency(%q<mongo>, [">= 0.20.1"])
217
216
  s.add_runtime_dependency(%q<bson>, [">= 0.20.1"])
217
+ s.add_development_dependency(%q<rspec>, [">= 1.3.0"])
218
218
  else
219
219
  s.add_dependency(%q<activesupport>, [">= 3.0.0.beta3"])
220
220
  s.add_dependency(%q<activemodel>, [">= 3.0.0.beta3"])
221
221
  s.add_dependency(%q<mongo>, [">= 0.20.1"])
222
222
  s.add_dependency(%q<bson>, [">= 0.20.1"])
223
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
223
224
  end
224
225
  else
225
226
  s.add_dependency(%q<activesupport>, [">= 3.0.0.beta3"])
226
227
  s.add_dependency(%q<activemodel>, [">= 3.0.0.beta3"])
227
228
  s.add_dependency(%q<mongo>, [">= 0.20.1"])
228
229
  s.add_dependency(%q<bson>, [">= 0.20.1"])
230
+ s.add_dependency(%q<rspec>, [">= 1.3.0"])
229
231
  end
230
232
  end
231
233
 
@@ -96,10 +96,10 @@ module MongoModel
96
96
  },
97
97
  :time =>
98
98
  {
99
- Time.local(2008, 5, 14, 1, 2, 3, 4) => Time.local(2008, 5, 14, 1, 2, 3, 4),
100
- Date.civil(2009, 11, 15) => Time.local(2009, 11, 15, 0, 0, 0, 0),
101
- "Sat Jan 01 20:15:01 UTC 2000" => Time.utc(2000, 1, 1, 20, 15, 1, 0),
102
- "2009/3/4" => Time.utc(2009, 3, 4, 0, 0, 0, 0)
99
+ Time.local(2008, 5, 14, 1, 2, 3, 4) => Time.local(2008, 5, 14, 1, 2, 3, 4, 0),
100
+ Date.civil(2009, 11, 15) => Time.local(2009, 11, 15, 0, 0, 0, 0, 0),
101
+ "Sat Jan 01 20:15:01 UTC 2000" => Time.utc(2000, 1, 1, 20, 15, 1, 0, 0),
102
+ "2009/3/4" => Time.utc(2009, 3, 4, 0, 0, 0, 0, 0)
103
103
  }
104
104
  }
105
105
 
@@ -216,7 +216,7 @@ module MongoModel
216
216
  subject[:hash] = { :foo => 'bar', :custom => CustomClass.new('custom in hash') }
217
217
  subject[:array] = [ 123, 'abc', 45.67, true, :bar, CustomClass.new('custom in array') ]
218
218
  subject[:date] = Date.civil(2009, 11, 15)
219
- subject[:time] = Time.local(2008, 5, 14, 1, 2, 3, 4)
219
+ subject[:time] = Time.local(2008, 5, 14, 1, 2, 3, 4, 0.5)
220
220
  subject[:custom] = CustomClass.new('custom')
221
221
  subject[:as] = "As property"
222
222
  subject[:non_property] = "Hello World"
@@ -231,7 +231,7 @@ module MongoModel
231
231
  'hash' => { :foo => 'bar', :custom => { :name => 'custom in hash' } },
232
232
  'array' => [ 123, 'abc', 45.67, true, :bar, { :name => 'custom in array' } ],
233
233
  'date' => "2009/11/15",
234
- 'time' => Time.local(2008, 5, 14, 1, 2, 3, 4),
234
+ 'time' => Time.local(2008, 5, 14, 1, 2, 3, 4, 0),
235
235
  'custom' => { :name => 'custom' },
236
236
  '_custom_as' => "As property",
237
237
  'non_property' => "Hello World",
@@ -249,7 +249,7 @@ module MongoModel
249
249
  'hash' => { :foo => 'bar' },
250
250
  'array' => [ 123, 'abc', 45.67, true, :bar ],
251
251
  'date' => Time.utc(2009, 11, 15),
252
- 'time' => Time.local(2008, 5, 14, 1, 2, 3, 4),
252
+ 'time' => Time.local(2008, 5, 14, 1, 2, 3, 4, 0.5),
253
253
  'custom' => { :name => 'custom' },
254
254
  '_custom_as' => "As property",
255
255
  'custom_non_property' => { :name => 'custom non property' }
@@ -263,7 +263,7 @@ module MongoModel
263
263
  subject[:hash].should == { :foo => 'bar' }.with_indifferent_access
264
264
  subject[:array].should == [ 123, 'abc', 45.67, true, :bar ]
265
265
  subject[:date].should == Date.civil(2009, 11, 15)
266
- subject[:time].should == Time.local(2008, 5, 14, 1, 2, 3, 4)
266
+ subject[:time].should == Time.local(2008, 5, 14, 1, 2, 3, 4, 0)
267
267
  subject[:custom].should == CustomClass.new('custom')
268
268
  subject[:as].should == "As property"
269
269
  subject[:custom_non_property].should == { :name => 'custom non property' }
@@ -31,15 +31,15 @@ module MongoModel
31
31
  end
32
32
 
33
33
  it "should add the old attribute name to the changed attributes" do
34
- subject.changed.should include(:foo)
34
+ subject.changed.should include("foo")
35
35
  end
36
36
 
37
37
  it "should not add other attributes to the changed attributes" do
38
- subject.changed.should_not include(:bar)
38
+ subject.changed.should_not include("bar")
39
39
  end
40
40
 
41
41
  it "should add the changed attribute value to the changes hash" do
42
- subject.changes[:foo].should == ['original foo', 'new foo']
42
+ subject.changes["foo"].should == ['original foo', 'new foo']
43
43
  end
44
44
 
45
45
  context "when called twice" do
@@ -48,7 +48,7 @@ module MongoModel
48
48
  end
49
49
 
50
50
  it "should keep the original value as the old value in the changes hash" do
51
- subject.changes[:foo].should == ['original foo', 'new foo #2']
51
+ subject.changes["foo"].should == ['original foo', 'new foo #2']
52
52
  end
53
53
  end
54
54
  end
@@ -60,6 +60,24 @@ module MongoModel
60
60
  end
61
61
 
62
62
  it { should be_changed }
63
+
64
+ it "should have a changed attribute" do
65
+ subject.foo_changed?.should == true
66
+ end
67
+
68
+ it "should tell what the attribute was" do
69
+ subject.foo_was.should == "original foo"
70
+ end
71
+
72
+ it "should have an attribute change" do
73
+ subject.foo_change.should == ["original foo", "foo changed"]
74
+ end
75
+
76
+ it "should be able to reset an attribute" do
77
+ subject.reset_foo!
78
+ subject.foo.should == "original foo"
79
+ subject.changed?.should == false
80
+ end
63
81
  end
64
82
 
65
83
  context "attribute set to the original value" do
@@ -68,11 +86,47 @@ module MongoModel
68
86
  end
69
87
 
70
88
  it { should_not be_changed }
89
+
90
+ it "should not have a changed attribute" do
91
+ subject.foo_changed?.should == false
92
+ end
93
+
94
+ it "should tell what the attribute was" do
95
+ subject.foo_was.should == "original foo"
96
+ end
97
+
98
+ it "should not have an attribute change" do
99
+ subject.foo_change.should == nil
100
+ end
101
+
102
+ it "should be able to reset an attribute" do
103
+ subject.reset_foo!
104
+ subject.foo.should == "original foo"
105
+ subject.changed?.should == false
106
+ end
71
107
  end
72
108
  end
73
109
 
74
110
  context "without changed attributes" do
75
111
  it { should_not be_changed }
112
+
113
+ it "should not have a changed attribute" do
114
+ subject.foo_changed?.should == false
115
+ end
116
+
117
+ it "should tell what the attribute was" do
118
+ subject.foo_was.should == "original foo"
119
+ end
120
+
121
+ it "should not have an attribute change" do
122
+ subject.foo_change.should == nil
123
+ end
124
+
125
+ it "should be able to reset an attribute" do
126
+ subject.reset_foo!
127
+ subject.foo.should == "original foo"
128
+ subject.changed?.should == false
129
+ end
76
130
  end
77
131
 
78
132
  context "#original_attributes" do
@@ -82,7 +136,7 @@ module MongoModel
82
136
  end
83
137
 
84
138
  it "should return the attributes before changes" do
85
- subject.original_attributes[:foo].should == 'original foo'
139
+ subject.original_attributes['foo'].should == 'original foo'
86
140
  end
87
141
  end
88
142
 
@@ -11,7 +11,7 @@ module MongoModel
11
11
  Array => [ 1, 2, 3, "hello", :world, [99, 100] ],
12
12
  Hash => { :rabbit => 'hat', 'hello' => 12345 }.with_indifferent_access,
13
13
  Date => Date.today,
14
- Time => Time.now,
14
+ Time => Types::Time.new.cast(Time.now),
15
15
  CustomClass => CustomClass.new('hello')
16
16
  }
17
17
 
@@ -2,6 +2,27 @@ require 'spec_helper'
2
2
 
3
3
  module MongoModel
4
4
  specs_for(EmbeddedDocument) do
5
+ describe "callbacks on many embedded documents" do
6
+ define_class(:ChildThingDocument, EmbeddedDocument) do
7
+ include MongoModel::CallbackHelpers
8
+ property :name, String
9
+ end
10
+
11
+ define_class(:ParentDocument, Document) do
12
+ property :things, Collection[ChildThingDocument]
13
+ end
14
+
15
+ it "should call callbacks on all embedded documents when adding a new one" do
16
+ parent = ParentDocument.create!
17
+ parent.things = Collection[ChildThingDocument].new
18
+ parent.things << ChildThingDocument.new(:name => "Thing One")
19
+ parent.save!
20
+ parent = ParentDocument.find(parent.id)
21
+ parent.things << ChildThingDocument.new(:name => "Thing Two")
22
+ parent.save!
23
+ end
24
+ end
25
+
5
26
  describe "callbacks" do
6
27
  define_class(:ChildDocument, EmbeddedDocument) do
7
28
  include MongoModel::CallbackHelpers
@@ -30,5 +30,27 @@ module MongoModel
30
30
  Person.internal_properties.should include(Person.properties[:type])
31
31
  Person.internal_properties.should include(Person.properties[:id]) if described_class == Document
32
32
  end
33
+
34
+ describe "when used as a property inside a document" do
35
+ define_class(:Factory, Document) do
36
+ property :manager, Person
37
+ end
38
+
39
+ let(:person) { SkilledPerson.new(:name => "Joe", :age => 44, :skill => "Management") }
40
+ let(:with_manager) { Factory.create!(:manager => person) }
41
+ let(:without_manager) { Factory.create! }
42
+
43
+ it "should load correctly when property is set" do
44
+ factory = Factory.find(with_manager.id)
45
+ factory.manager.should be_an_instance_of(SkilledPerson)
46
+ factory.manager.name.should == "Joe"
47
+ factory.manager.skill.should == "Management"
48
+ end
49
+
50
+ it "should load correctly when property is nil" do
51
+ factory = Factory.find(without_manager.id)
52
+ factory.manager.should be_nil
53
+ end
54
+ end
33
55
  end
34
56
  end
@@ -38,7 +38,7 @@ module MongoModel
38
38
  subject { TestDocument.new }
39
39
 
40
40
  before(:each) do
41
- @now = Time.now.utc
41
+ @now = Types::Time.new.cast(Time.now)
42
42
  Time.stub!(:now).and_return(@now)
43
43
  end
44
44
 
@@ -91,7 +91,7 @@ module MongoModel
91
91
  subject { TestDocument.new }
92
92
 
93
93
  before(:each) do
94
- @now = Time.now.utc
94
+ @now = Types::Time.new.cast(Time.now)
95
95
  Time.stub!(:now).and_return(@now)
96
96
  end
97
97
 
@@ -300,6 +300,38 @@ module MongoModel
300
300
  user.should_not be_a_new_record
301
301
  end
302
302
  end
303
+
304
+ describe "#reload" do
305
+ define_class(:UserComment, Document) do
306
+ property :title, String
307
+ property :body, String
308
+
309
+ belongs_to :user
310
+ end
311
+
312
+ let(:user) { User.create!(:name => "Bob") }
313
+
314
+ subject { UserComment.create!(:title => "Test", :user => user) }
315
+
316
+ it "should return itself" do
317
+ subject.reload.should == subject
318
+ end
319
+
320
+ it "should reset the attributes" do
321
+ subject.title = "New Value"
322
+ subject.body = "Blah blah blah"
323
+ subject.reload
324
+ subject.title.should == "Test"
325
+ subject.body.should == nil
326
+ end
327
+
328
+ it "should reset the associations" do
329
+ subject.user.should == user
330
+ subject.user = User.new(:name => "Bill")
331
+ subject.reload
332
+ subject.user.should == user
333
+ end
334
+ end
303
335
  end
304
336
  end
305
337
  end
@@ -41,19 +41,20 @@ module MongoModel
41
41
  end
42
42
 
43
43
  specs_for(EmbeddedDocument) do
44
+ define_class(:Event, EmbeddedDocument)
45
+ define_class(:SpecialEvent, :Event)
46
+
47
+ define_class(:Parent, Document) do
48
+ property :event, Event
49
+ property :events, Collection[Event], :default => []
50
+ end
51
+
52
+ let(:event) { Event.new }
53
+ let(:special) { SpecialEvent.new }
54
+ let(:parent) { Parent.new(:event => special) }
55
+ let(:reloaded) { parent.save!; Parent.find(parent.id) }
56
+
44
57
  describe "single collection inheritance" do
45
- define_class(:Event, EmbeddedDocument)
46
- define_class(:SpecialEvent, :Event)
47
-
48
- define_class(:Parent, Document) do
49
- property :event, Event
50
- end
51
-
52
- let(:event) { Event.new }
53
- let(:special) { SpecialEvent.new }
54
- let(:parent) { Parent.new(:event => special) }
55
- let(:reloaded) { parent.save!; Parent.find(parent.id) }
56
-
57
58
  it "should not typecast to parent type when assigning to property" do
58
59
  parent.event.should be_an_instance_of(SpecialEvent)
59
60
  end
@@ -62,5 +63,33 @@ module MongoModel
62
63
  reloaded.event.should be_an_instance_of(SpecialEvent)
63
64
  end
64
65
  end
66
+
67
+ describe "parent document" do
68
+ context "on an embedded document" do
69
+ it "should set the parent document on the embedded document" do
70
+ parent.event.parent_document.should == parent
71
+ end
72
+
73
+ it "should set the parent document on the embedded document when loaded from database" do
74
+ reloaded.event.parent_document.should == reloaded
75
+ end
76
+ end
77
+
78
+ context "on a collection" do
79
+ it "should set the parent document on the collection" do
80
+ parent.events.parent_document.should == parent
81
+ end
82
+
83
+ it "should set the parent document on each item added to a collection" do
84
+ parent.events << special
85
+ parent.events.first.parent_document.should == parent
86
+ end
87
+
88
+ it "should set the parent document on each item in a collection when loaded from database" do
89
+ parent.events << special
90
+ reloaded.events.first.parent_document.should == parent
91
+ end
92
+ end
93
+ end
65
94
  end
66
95
  end
@@ -234,8 +234,14 @@ module MongoModel
234
234
  let(:embedded2) { Embedded.new(:number => 2) }
235
235
  let(:embedded3) { Embedded.new(:number => 3) }
236
236
 
237
+ let(:empty) { TestDocument.new }
237
238
  subject { TestDocument.new(:embedded => embedded1, :embedded_collection => [embedded2, embedded3]) }
238
239
 
240
+ it "should default to an empty collection" do
241
+ empty.embedded_collection.should be_an_instance_of(Collection[Embedded])
242
+ empty.embedded_collection.should be_empty
243
+ end
244
+
239
245
  it "should include the embedded properties in the embedded documents list" do
240
246
  subject.embedded_documents.should include(embedded1)
241
247
  end
@@ -611,6 +611,55 @@ module MongoModel
611
611
  end
612
612
  end
613
613
  end
614
+
615
+ describe "#update_all" do
616
+ it "should update all matching documents" do
617
+ model.should_update(finder_conditions, { :name => "New name" })
618
+ subject.update_all(:name => "New name")
619
+ end
620
+
621
+ subject_loaded do
622
+ it "should reset the scope" do
623
+ subject.update_all(:name => "New name")
624
+ subject.should_not be_loaded
625
+ end
626
+ end
627
+ end
628
+
629
+ describe "#update" do
630
+ context "by single id" do
631
+ let(:post) { posts.first }
632
+
633
+ it "should update the document with the given id" do
634
+ model.should_update(finder_conditions.merge(:id => post.id), { :name => "New name" })
635
+ subject.update(post.id, :name => "New name")
636
+ end
637
+
638
+ subject_loaded do
639
+ it "should reset the scope" do
640
+ subject.update(post.id, {})
641
+ subject.should_not be_loaded
642
+ end
643
+ end
644
+ end
645
+
646
+ context "by multiple ids" do
647
+ let(:post1) { posts.first }
648
+ let(:post2) { posts.last }
649
+
650
+ it "should update the documents with the given ids" do
651
+ model.should_update(finder_conditions.merge(:id.in => [post1.id, post2.id]), { :name => "New name" })
652
+ subject.update([post1.id, post2.id], :name => "New name")
653
+ end
654
+
655
+ subject_loaded do
656
+ it "should reset the scope" do
657
+ subject.update([post1.id, post2.id], {})
658
+ subject.should_not be_loaded
659
+ end
660
+ end
661
+ end
662
+ end
614
663
  end
615
664
 
616
665
 
@@ -3,4 +3,5 @@
3
3
  progress
4
4
  --loadby
5
5
  mtime
6
- --reverse
6
+ --reverse
7
+ --backtrace
@@ -41,4 +41,10 @@ module DocumentFinderStubs
41
41
  collection.should_receive(:remove).once.with(selector)
42
42
  yield if block_given?
43
43
  end
44
+
45
+ def should_update(conditions={}, updates={})
46
+ selector, options = MongoModel::MongoOptions.new(self, :conditions => conditions).to_a
47
+ collection.should_receive(:update).once.with(selector, { "$set" => updates }, { :multi => true })
48
+ yield if block_given?
49
+ end
44
50
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 2
9
- version: 0.2.2
8
+ - 3
9
+ version: 0.2.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Sam Pohlenz
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-26 00:00:00 +09:30
17
+ date: 2010-04-30 00:00:00 +09:30
18
18
  default_executable: console
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -75,6 +75,20 @@ dependencies:
75
75
  version: 0.20.1
76
76
  type: :runtime
77
77
  version_requirements: *id004
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ prerelease: false
81
+ requirement: &id005 !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ segments:
86
+ - 1
87
+ - 3
88
+ - 0
89
+ version: 1.3.0
90
+ type: :development
91
+ version_requirements: *id005
78
92
  description: MongoModel is a MongoDB ORM for Ruby/Rails similar to ActiveRecord and DataMapper.
79
93
  email: sam@sampohlenz.com
80
94
  executables:
@@ -113,6 +127,7 @@ files:
113
127
  - lib/mongomodel/concerns/attribute_methods/write.rb
114
128
  - lib/mongomodel/concerns/attributes.rb
115
129
  - lib/mongomodel/concerns/callbacks.rb
130
+ - lib/mongomodel/concerns/document_parent.rb
116
131
  - lib/mongomodel/concerns/logging.rb
117
132
  - lib/mongomodel/concerns/pretty_inspect.rb
118
133
  - lib/mongomodel/concerns/properties.rb
@@ -212,7 +227,6 @@ files:
212
227
  - spec/support/matchers/respond_to_boolean.rb
213
228
  - spec/support/matchers/run_callbacks.rb
214
229
  - spec/support/models.rb
215
- - spec/support/time.rb
216
230
  has_rdoc: true
217
231
  homepage: http://github.com/spohlenz/mongomodel
218
232
  licenses: []
@@ -293,4 +307,3 @@ test_files:
293
307
  - spec/support/matchers/respond_to_boolean.rb
294
308
  - spec/support/matchers/run_callbacks.rb
295
309
  - spec/support/models.rb
296
- - spec/support/time.rb
@@ -1,6 +0,0 @@
1
- # For the purposes of tests, we ignore fractions of a second when comparing Time objects
2
- class Time
3
- def ==(other)
4
- super(other) || utc.to_s == other.utc.to_s if other
5
- end
6
- end