mm_partial_update 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +23 -5
- data/lib/mm_partial_update/plugins/document.rb +5 -1
- data/lib/mm_partial_update/plugins/embedded_document.rb +9 -0
- data/lib/mm_partial_update/plugins/partial_update.rb +39 -19
- data/lib/mm_partial_update/version.rb +1 -1
- data/test/callbacks_support.rb +27 -0
- data/test/functional/plugins/test_embedded_document.rb +3 -7
- data/test/functional/plugins/test_partial_update.rb +67 -5
- data/test/models.rb +17 -0
- metadata +4 -3
data/README.rdoc
CHANGED
@@ -14,7 +14,11 @@ To activate the plugin, add 'mm_partial_update' to your gemfile
|
|
14
14
|
|
15
15
|
== Usage
|
16
16
|
|
17
|
-
mm-partial-update does not, by default, change MongoMappers normal mode of operation. Although mm-partial-update can be configured, either at a global level or on a model by model basis, to use a partial update strategy for all persistence operations, out of the box it simply adds
|
17
|
+
mm-partial-update does not, by default, change MongoMappers normal mode of operation. Although mm-partial-update can be configured, either at a global level or on a model by model basis, to use a partial update strategy for all persistence operations, out of the box it simply adds #save_changes and #save_changes! methods to both MongoMapper::Document and MongoMapper::EmbeddedDocument. #save_changes(!) will persist any changes to the target document using MongoDB's atomic operators, ideal for making small changes to large documents or for concurrent modification of documents by multiple processes.
|
18
|
+
|
19
|
+
As you might expect, #save_changes and #save_changes! mirror the behavior of #save and #save!, in that #save_changes will return false if the document (or embedded document) having its changes saved fails validation, whereas #save_changes! will raise a MongoMapper::InvalidDocument error.
|
20
|
+
|
21
|
+
Both methods behave identically to their native MongoMapper counterparts with respect to both validations and callbacks.
|
18
22
|
|
19
23
|
=== For example:
|
20
24
|
|
@@ -35,11 +39,25 @@ mm-partial-update does not, by default, change MongoMappers normal mode of opera
|
|
35
39
|
|
36
40
|
person = Person.create! :name=>"Willard"
|
37
41
|
person.name = "Poe"
|
38
|
-
person.save_changes #only saves the changed fields (in this case name=>"Poe"
|
42
|
+
person.save_changes! #only saves the changed fields (in this case name=>"Poe"
|
43
|
+
|
44
|
+
You can also persist only a part of a document:
|
45
|
+
|
46
|
+
person = Person.create! :name=>"Willard"
|
47
|
+
person.name = "Benji"
|
48
|
+
pet = person.pets.build :name=>"Magma"
|
49
|
+
|
50
|
+
person.changed? #= > true
|
51
|
+
pet.changed? # => true
|
52
|
+
|
53
|
+
pet.save_changes
|
54
|
+
|
55
|
+
pet.changed? # => false
|
56
|
+
person.changed? # => true
|
39
57
|
|
40
|
-
|
58
|
+
In addition to #save_changes(!), partial saves can be enabled globally:
|
41
59
|
|
42
|
-
MmPartialUpdate.
|
60
|
+
MmPartialUpdate.default_persistence_strategy = :changes_only
|
43
61
|
|
44
62
|
Or on a model by model basis:
|
45
63
|
|
@@ -50,7 +68,7 @@ Or on a model by model basis:
|
|
50
68
|
many :pets
|
51
69
|
end
|
52
70
|
|
53
|
-
When enabled globally, all calls to save or save! will
|
71
|
+
When enabled globally, all calls to #save or #save! across all models will simply delegate to #save_changes or #save_changes!, resulting in a partial update. When enabled on a particular model, calls to save or save! on instances of that model and any subclasses of that model will result in partial updates.
|
54
72
|
|
55
73
|
== Known Issues & Limitations
|
56
74
|
|
@@ -10,11 +10,15 @@ module MmPartialUpdate
|
|
10
10
|
|
11
11
|
module InstanceMethods
|
12
12
|
|
13
|
+
def create_or_update_changes(options={})
|
14
|
+
new? ? save(:validate=>false, :safe=>options[:safe]) : super
|
15
|
+
end
|
16
|
+
|
13
17
|
def save_to_collection(options={})
|
14
18
|
strategy = determine_persistence_strategy(options)
|
15
19
|
return super if new? || strategy == :full_document
|
16
20
|
|
17
|
-
save_changes(options)
|
21
|
+
save_changes(options.merge(:validate=>false, :callbacks=>false))
|
18
22
|
end
|
19
23
|
|
20
24
|
private
|
@@ -10,6 +10,11 @@ module MmPartialUpdate
|
|
10
10
|
|
11
11
|
module InstanceMethods
|
12
12
|
|
13
|
+
def create_or_update_changes(options={})
|
14
|
+
assert_root_saved
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
13
18
|
def database_selector
|
14
19
|
selector = @_association_name.to_s
|
15
20
|
selector = "#{_parent_document.database_selector}.#{selector}" if
|
@@ -34,6 +39,10 @@ module MmPartialUpdate
|
|
34
39
|
end
|
35
40
|
end
|
36
41
|
|
42
|
+
def assert_root_saved
|
43
|
+
raise "You are attempting to save changes to an embedded document, but the root document has not yet been saved. You must save changes to the root document before you can call save_changes any of its embedded documents" if _root_document.new?
|
44
|
+
end
|
45
|
+
|
37
46
|
end
|
38
47
|
|
39
48
|
end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
require "mongo_mapper/exceptions.rb"
|
2
|
+
|
2
3
|
module MmPartialUpdate
|
3
4
|
module Plugins
|
4
5
|
module PartialUpdate
|
@@ -27,25 +28,21 @@ module MmPartialUpdate
|
|
27
28
|
module InstanceMethods
|
28
29
|
|
29
30
|
def save_changes(options={})
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
return _root_document.save_to_collection(options).tap {clear_changes} if
|
35
|
-
_root_document.new?
|
36
|
-
|
37
|
-
#persist changes to self and descendents
|
38
|
-
update_command = prepare_update_command
|
39
|
-
update_command.execute()
|
31
|
+
options.assert_valid_keys(:validate, :callbacks, :safe, :changes_only)
|
32
|
+
options.reverse_merge!(:validate=>true, :callbacks=>true)
|
33
|
+
!options[:validate] || valid? ? create_or_update_changes(options) : false
|
34
|
+
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
36
|
+
def save_changes!(options={})
|
37
|
+
options.assert_valid_keys(:callbacks, :safe, :changes_only)
|
38
|
+
save_changes(options) || raise(MongoMapper::DocumentNotValid.new(self))
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_or_update_changes(options={})
|
42
|
+
#assert_root_saved
|
43
|
+
update_command = prepare_update_command
|
44
|
+
execute_command(update_command, options)
|
45
|
+
clear_changes_to_subtree(options)
|
49
46
|
end
|
50
47
|
|
51
48
|
def prepare_update_command
|
@@ -86,6 +83,29 @@ module MmPartialUpdate
|
|
86
83
|
proxy
|
87
84
|
end
|
88
85
|
|
86
|
+
def clear_changes_to_subtree(options)
|
87
|
+
@_new = false
|
88
|
+
clear_changes
|
89
|
+
associations.each do |_, association|
|
90
|
+
proxy = get_proxy(association)
|
91
|
+
proxy.save_to_collection(options) if
|
92
|
+
proxy.proxy_respond_to?(:save_to_collection)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def execute_command(update_command,options)
|
97
|
+
if options[:callbacks]
|
98
|
+
context = new? ? :create : :update
|
99
|
+
run_callbacks(:save) do
|
100
|
+
run_callbacks(context) do
|
101
|
+
update_command.execute()
|
102
|
+
end
|
103
|
+
end
|
104
|
+
else
|
105
|
+
update_command.execute()
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
89
109
|
end
|
90
110
|
|
91
111
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#from MongoMapper test suite
|
2
|
+
module CallbacksSupport
|
3
|
+
def self.included base
|
4
|
+
base.key :name, String
|
5
|
+
|
6
|
+
[ :after_find, :after_initialize,
|
7
|
+
:before_validation, :after_validation,
|
8
|
+
:before_create, :after_create,
|
9
|
+
:before_update, :after_update,
|
10
|
+
:before_save, :after_save,
|
11
|
+
:before_destroy, :after_destroy
|
12
|
+
].each do |callback|
|
13
|
+
base.send(callback) do
|
14
|
+
history << callback.to_sym
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def history
|
20
|
+
@history ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def clear_history
|
24
|
+
embedded_associations.each { |a| self.send(a.name).each(&:clear_history) }
|
25
|
+
@history = nil
|
26
|
+
end
|
27
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require "models"
|
3
3
|
|
4
|
+
|
4
5
|
class TestEmbeddedDocumentPlugin < Test::Unit::TestCase
|
5
6
|
|
6
7
|
context "#database_selector" do
|
@@ -73,15 +74,10 @@ class TestEmbeddedDocumentPlugin < Test::Unit::TestCase
|
|
73
74
|
|
74
75
|
context "#save_changes" do
|
75
76
|
|
76
|
-
should "
|
77
|
+
should "fails when a descendent is saved with an unsaved root" do
|
77
78
|
person = Person.new :name=>"Willard"
|
78
79
|
pet = person.pets.build :name=>"Magma"
|
79
|
-
pet.save_changes
|
80
|
-
person = Person.find(person.id)
|
81
|
-
person.should_not be_nil
|
82
|
-
person.name.should == "Willard"
|
83
|
-
person.pets.count.should == 1
|
84
|
-
person.pets[0].name.should == "Magma"
|
80
|
+
assert_raises(RuntimeError) { pet.save_changes }
|
85
81
|
end
|
86
82
|
|
87
83
|
should "create an embedded document that doesn't already exist" do
|
@@ -91,11 +91,73 @@ class TestPartialUpdate < Test::Unit::TestCase
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
94
|
+
end
|
95
|
+
|
96
|
+
context "#save_changes!" do
|
97
|
+
|
98
|
+
should "raise an exception if the document isn't valid" do
|
99
|
+
person = ValidatedPerson.new
|
100
|
+
assert_raise(MongoMapper::DocumentNotValid) { person.save_changes! }
|
101
|
+
end
|
102
|
+
|
103
|
+
should "not raise an exception if the document is valid" do
|
104
|
+
person = ValidatedPerson.new :name=>"Willard"
|
105
|
+
assert_nothing_thrown { person.save_changes! }
|
106
|
+
end
|
107
|
+
|
108
|
+
should "raise an exception when saving an embedded document that isn't valid" do
|
109
|
+
person = ValidatedPerson.create! :name=>"Willard"
|
110
|
+
pet = person.validated_pets.build
|
111
|
+
assert_raise(MongoMapper::DocumentNotValid) {pet.save_changes!}
|
112
|
+
end
|
113
|
+
|
114
|
+
should "raise an exception when saving an embedded 'one' document that isn't valid" do
|
115
|
+
person = ValidatedPerson.create! :name=>"Willard"
|
116
|
+
pet = person.validated_pet.build
|
117
|
+
assert_raise(MongoMapper::DocumentNotValid) {pet.save_changes!}
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
context "callbacks" do
|
123
|
+
|
124
|
+
should "happen for new top level documents" do
|
125
|
+
p = Person.new :name=>"Willard"
|
126
|
+
p.clear_history
|
127
|
+
p.save_changes
|
128
|
+
p.history.should == [:before_validation, :after_validation,
|
129
|
+
:before_save, :before_create, :after_create, :after_save]
|
130
|
+
end
|
131
|
+
|
132
|
+
should "happen for updated top level documents" do
|
133
|
+
p = Person.create :name=>"Willard"
|
134
|
+
p.clear_history
|
135
|
+
p.name = "Timmy"
|
136
|
+
p.save_changes
|
137
|
+
p.history.should == [:before_validation, :after_validation,
|
138
|
+
:before_save, :before_update, :after_update, :after_save]
|
139
|
+
end
|
140
|
+
|
141
|
+
should "happen for new embedded documents" do
|
142
|
+
p = Person.create :name=>"Willard"
|
143
|
+
pet = p.pets.build :name=>"Magma"
|
144
|
+
pet.clear_history
|
145
|
+
pet.save_changes
|
146
|
+
pet.history.should == [:before_validation, :after_validation,
|
147
|
+
:before_save, :before_create, :after_create, :after_save]
|
148
|
+
end
|
149
|
+
|
150
|
+
should "happen for updated embedded documents" do
|
151
|
+
p = Person.new :name=>"Willard"
|
152
|
+
pet = p.pets.build :name=>"Magma"
|
153
|
+
p.save!
|
154
|
+
pet.clear_history
|
155
|
+
pet.name = "Debris"
|
156
|
+
pet.save_changes
|
157
|
+
pet.history.should == [:before_validation, :after_validation,
|
158
|
+
:before_save, :before_update, :after_update, :after_save]
|
159
|
+
end
|
99
160
|
|
100
161
|
end
|
162
|
+
|
101
163
|
end
|
data/test/models.rb
CHANGED
@@ -1,18 +1,35 @@
|
|
1
|
+
require "callbacks_support"
|
2
|
+
|
1
3
|
class Person
|
2
4
|
include MongoMapper::Document
|
5
|
+
include CallbacksSupport
|
6
|
+
|
3
7
|
key :name, String
|
4
8
|
many :pets
|
5
9
|
one :favorite_pet, :class_name=>'Pet'
|
10
|
+
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
class ValidatedPerson < Person
|
15
|
+
validates_presence_of :name
|
16
|
+
one :validated_pet
|
17
|
+
many :validated_pets
|
6
18
|
end
|
7
19
|
|
8
20
|
class Pet
|
9
21
|
include MongoMapper::EmbeddedDocument
|
22
|
+
include CallbacksSupport
|
10
23
|
key :name, String
|
11
24
|
key :age, Integer
|
12
25
|
many :fleas
|
13
26
|
one :favorite_flea, :class_name=>'Flea'
|
14
27
|
end
|
15
28
|
|
29
|
+
class ValidatedPet < Pet
|
30
|
+
validates_presence_of :name
|
31
|
+
end
|
32
|
+
|
16
33
|
class Flea
|
17
34
|
include MongoMapper::EmbeddedDocument
|
18
35
|
key :name, String
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mm_partial_update
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 1
|
9
|
-
-
|
10
|
-
version: 0.1.
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Nathan Stults
|
@@ -128,6 +128,7 @@ files:
|
|
128
128
|
- lib/mm_partial_update/one_embedded_proxy.rb
|
129
129
|
- lib/mm_partial_update/version.rb
|
130
130
|
- lib/mm_partial_update/update_command.rb
|
131
|
+
- test/callbacks_support.rb
|
131
132
|
- test/test_helper.rb
|
132
133
|
- test/test_update_command.rb
|
133
134
|
- test/functional/plugins/test_embedded_document.rb
|