mongomodel 0.2.2 → 0.2.3

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/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