mongoid_versioned_atomic 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/Rakefile +34 -0
- data/lib/mongoid_versioned_atomic.rb +4 -0
- data/lib/mongoid_versioned_atomic/v_atomic.rb +336 -0
- data/lib/mongoid_versioned_atomic/version.rb +3 -0
- data/lib/tasks/mongoid_versioned_atomic_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/images/facebook.png +0 -0
- data/test/dummy/app/assets/images/keratoscope.jpg +0 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/entry.rb +11 -0
- data/test/dummy/app/models/thing.rb +5 -0
- data/test/dummy/app/models/user.rb +51 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +29 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +38 -0
- data/test/dummy/config/environments/production.rb +76 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/mongoid.yml +137 -0
- data/test/dummy/config/routes.rb +56 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/log/development.log +21 -0
- data/test/dummy/log/test.log +21976 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/test/fixtures/users.yml +9 -0
- data/test/dummy/test/models/user_test.rb +7 -0
- data/test/mongoid_versioned_atomic_test.rb +7 -0
- data/test/test_helper.rb +19 -0
- data/test/v_atomic_test.rb +569 -0
- metadata +212 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6859e384eb204360d022be2f91b8057abbe852eb
|
4
|
+
data.tar.gz: 84f61d00dd36b314e501ecaba475943becea95cc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f17e52372a7ee52beac5f27daed48cdbeca51e3199642788f540e0089fc3894bd7ac42677d9a566605c2e4b08a6b59d7552cc186c207948d7b672f239ea8bc8
|
7
|
+
data.tar.gz: 5adb80e64c304229f049b4b20fec32b66e6c935f49757d8c0546d5f4b89cfebac4f5e9bb12c2e6ee46bb5017de0a681df8e13f53ea94220b8fb90e8a5ce38ed2
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2016 bhargav
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'MongoidVersionedAtomic'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1,336 @@
|
|
1
|
+
module MongoidVersionedAtomic
|
2
|
+
module VAtomic
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
field :version, type: Integer, default: 0
|
8
|
+
field :op_success, type: Boolean
|
9
|
+
attr_accessor :matched_count
|
10
|
+
attr_accessor :modified_count
|
11
|
+
attr_accessor :upserted_id
|
12
|
+
#before_save :filter_fields
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
##@param bson_doc[BSON Document] : a bson_document instance
|
22
|
+
##@param klass[klass] : klass of the target document.
|
23
|
+
##converts a bson_doc to the target klass instance.
|
24
|
+
##return [Object] : either the document in the target class or nil.
|
25
|
+
def bson_to_mongoid(bson_doc,klass)
|
26
|
+
if !bson_doc.nil?
|
27
|
+
|
28
|
+
t = Mongoid::Factory.from_db(klass,bson_doc)
|
29
|
+
return t
|
30
|
+
|
31
|
+
else
|
32
|
+
|
33
|
+
return nil
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
##@param query[Hash] -> query hash, defaults to empty hash.
|
40
|
+
|
41
|
+
##@param update[Hash] -> update hash.
|
42
|
+
|
43
|
+
##@param upsert[Boolean] -> defaults to true
|
44
|
+
|
45
|
+
##@param log[Boolean] -> defaults to false, if set to true, will print the entire query to the console, before executing it.
|
46
|
+
|
47
|
+
##@param bypass_versioning[Boolean] -> defaults to false, if true, then versioning will be bypassed.
|
48
|
+
|
49
|
+
##@param klass : the class of the document to be modified.
|
50
|
+
|
51
|
+
##@logic :
|
52
|
+
##will only AFFECT ONE DOCUMENT.
|
53
|
+
##if the query is empty, then versioning is bypassed, because otherwise, this will lead to an increment of all the documents in the collection.
|
54
|
+
##basically will find the document specified in the query and if it is not found, then will create a new document with the provided options.
|
55
|
+
##if it is found, then applies the update hash to found document.
|
56
|
+
##the version increment is applied to any document that is found, and updated.
|
57
|
+
|
58
|
+
##@return mongoid document instance or nil(if the update hash was empty). You need to check the document to see whether it has the changes you requested.
|
59
|
+
def versioned_upsert_one(query={},update={},klass=nil,upsert=true,log=false,bypass_versioning=false)
|
60
|
+
|
61
|
+
options = {}
|
62
|
+
|
63
|
+
if query.empty?
|
64
|
+
bypass_versioning = true
|
65
|
+
end
|
66
|
+
|
67
|
+
options,update = before_persist(options,update,bypass_versioning)
|
68
|
+
|
69
|
+
options[:upsert] = upsert
|
70
|
+
if !update.empty?
|
71
|
+
return bson_to_mongoid(collection.find_one_and_update(query,update,options),klass)
|
72
|
+
end
|
73
|
+
|
74
|
+
return nil
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
## @param options[Hash] : this is passed in from the calling method. Before_persist simply adds the return_document[:after] to it, so that the operation returns the changed document.
|
79
|
+
|
80
|
+
## @param update[Hash] : this is the update hash that is going to be sent to mongodb, it is also passed in from the calling method.
|
81
|
+
|
82
|
+
## If bypass versioning is false(default), then the update inc is set. If there is no "$inc" already in the update hash then we create a new entry for it, otherwise we simply set "version" to be incremented by one.
|
83
|
+
|
84
|
+
## @return [Array] : returns the options, and update.
|
85
|
+
def before_persist(options,update,bypass_versioning=false)
|
86
|
+
|
87
|
+
options[:return_document] = :after
|
88
|
+
|
89
|
+
if !bypass_versioning
|
90
|
+
if update["$inc"].nil?
|
91
|
+
update["$inc"] = {
|
92
|
+
"version" => 1 }
|
93
|
+
else
|
94
|
+
update["$inc"]["version"] = 1
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
return options,update
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
##@param doc[Mongodb document] : this is the 'new' document that is got after applying operations on an instance.
|
103
|
+
|
104
|
+
##@param instance[Mongodb document] : this is the old document, i.e the instance on which the operation was applied.
|
105
|
+
|
106
|
+
##checks each field on the new document
|
107
|
+
##if it is version, then sets that on the instance.
|
108
|
+
##if it is any other field, then sets it on the instance if the field is not the same.
|
109
|
+
|
110
|
+
##return [Boolean] : always returns true.
|
111
|
+
def after_persist(doc,instance)
|
112
|
+
doc.keys.each do |f|
|
113
|
+
if f == "version"
|
114
|
+
instance.send("#{f}=",doc[f])
|
115
|
+
else
|
116
|
+
if instance.send("#{f}") != doc[f]
|
117
|
+
instance.send("#{f}=",doc[f])
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
return true
|
122
|
+
end
|
123
|
+
|
124
|
+
##logs
|
125
|
+
def log_opts(query,update,options,create_or_update,log)
|
126
|
+
|
127
|
+
if log
|
128
|
+
|
129
|
+
puts "doing-------------------- #{create_or_update}"
|
130
|
+
|
131
|
+
|
132
|
+
puts "the query is :"
|
133
|
+
puts JSON.pretty_generate(query)
|
134
|
+
|
135
|
+
puts "the update is"
|
136
|
+
puts JSON.pretty_generate(update)
|
137
|
+
|
138
|
+
puts "the options are"
|
139
|
+
puts JSON.pretty_generate(options)
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
# removes "version" and "op_success" fields from the document before save or update, this ensures that these fields can only be persisted by the versioned_create and versioned_update methods.
|
148
|
+
# prevents inadvertent persistence of these fields.
|
149
|
+
#return attributes[Hash] : the document as a set of key-value fields.
|
150
|
+
def filter_fields
|
151
|
+
|
152
|
+
remove_attribute(:version)
|
153
|
+
remove_attribute(:op_success)
|
154
|
+
attributes
|
155
|
+
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
## @param query[Hash] : optional query hash.
|
161
|
+
## @param log[Boolean] : defaults to false, set true if you want to print out the final command sent to mongo.
|
162
|
+
|
163
|
+
## @logic:
|
164
|
+
|
165
|
+
## begin the method by setting op_success to false, so that it can be true only if everything works out perfectly.
|
166
|
+
|
167
|
+
## the query defaults to the id of the present instance, or the optional query hash if one is provided.
|
168
|
+
|
169
|
+
## checks that the current version is 0 , otherwise does nothing, this ensures that we only persist new documents.
|
170
|
+
|
171
|
+
## update only sets via "setonInsert". By default, the document is persisted only if
|
172
|
+
## a. its id does not exist in the collection
|
173
|
+
## OR
|
174
|
+
## b. the parameters supplied in the optional query hash do not find a document in the collection.
|
175
|
+
|
176
|
+
## 'version' and 'op_success' are not added to the setOnInsert. Version is set in the #before_persist method, and op_success is set after the call to mongo.
|
177
|
+
|
178
|
+
## expected_version is the version we expect to see in the new doucment after applying the operation, provided that the operation actually persists a document.
|
179
|
+
|
180
|
+
## op_success becomes true only if a document is returned after the operation is executed and the version is 1.
|
181
|
+
|
182
|
+
## after_persist sets the fields in the persisted document on the instance.
|
183
|
+
def versioned_create(query={},log=false)
|
184
|
+
|
185
|
+
|
186
|
+
self.send("op_success=",false)
|
187
|
+
update = {}
|
188
|
+
options = {}
|
189
|
+
id_query = {"_id" => as_document["_id"]}
|
190
|
+
|
191
|
+
query = query.empty? ? id_query : query
|
192
|
+
|
193
|
+
|
194
|
+
if version == 0
|
195
|
+
|
196
|
+
update["$setOnInsert"] = {}
|
197
|
+
options[:upsert] = true
|
198
|
+
|
199
|
+
expected_version = 1
|
200
|
+
|
201
|
+
prepare_insert(options) do
|
202
|
+
|
203
|
+
as_document.keys.each do |k|
|
204
|
+
if (k != "version" && k != "op_success")
|
205
|
+
update["$setOnInsert"][k] = self.send(k.to_sym)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
update["$setOnInsert"]["version"] = 1
|
210
|
+
|
211
|
+
options,update = self.class.before_persist(options,update,true)
|
212
|
+
|
213
|
+
self.class.log_opts(query,update,options,"create",log)
|
214
|
+
|
215
|
+
write_result = collection.update_one(query,update,options)
|
216
|
+
|
217
|
+
|
218
|
+
|
219
|
+
self.matched_count = write_result.matched_count
|
220
|
+
self.modified_count = write_result.modified_count
|
221
|
+
self.upserted_id = write_result.upserted_id
|
222
|
+
##as long as it matched a document, or it inserted a document
|
223
|
+
if write_result.matched_count > 0 || write_result.upserted_id
|
224
|
+
self.send("op_success=",true)
|
225
|
+
self.version = 1
|
226
|
+
else
|
227
|
+
self.send("op_success",false)
|
228
|
+
end
|
229
|
+
#if !write_result.upserted_id.nil?
|
230
|
+
|
231
|
+
# self.send("op_success=",true)
|
232
|
+
# self.version = 1
|
233
|
+
#else
|
234
|
+
# self.send("op_success=",false)
|
235
|
+
#end
|
236
|
+
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
return query,update,options
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
## @param dirty_fields[Hash] : an optional hash, whose keys should be the names of the fields that have changed i.e should be updated, defaults to empty, which results in all the fields of the document being updated.
|
249
|
+
|
250
|
+
## @param bypass_versioning[Boolean] : whether the version check should be bypassed. Defaults to false. Default condition is that the updated query should have both, the document id + the document version. And the update hash should increment the document version. If the bypass_versioning flag is set to true, then document_version is not considered in the query and it is not incremented in the update hash.
|
251
|
+
|
252
|
+
## @param optional_update_hash[Hash] : an optional hash, that defaults to being empty. If provided it is used as the update hash in the final command sent to mongo. If not provided, it remains empty, and all those fields which are considered dirty are assigned to the "$set" in the update hash. The "$inc" part for versioning is not affected whether this hash is provided or not, since it is set in the before_persist.
|
253
|
+
|
254
|
+
##@param log[Boolean] : defaults to false, if set to true will print out the final command sent to mongo.
|
255
|
+
|
256
|
+
##@logic:
|
257
|
+
|
258
|
+
##set op_success to false at the beginning.
|
259
|
+
|
260
|
+
##if dirty fields is empty, then it becomes the document as a hash of key_values.
|
261
|
+
|
262
|
+
##if it has something in it, then set the values of the dirty fields keys to whatever is the value of the respective field in the document.
|
263
|
+
|
264
|
+
##proceed only if the doc_version is greater than 0
|
265
|
+
|
266
|
+
##all fields to be persisted are put into the "$set" part of the update, and upsert is set to false.
|
267
|
+
|
268
|
+
##after the call to mongo, provided that the persisted_document is not nil, two possibilities.
|
269
|
+
|
270
|
+
##if bypass versioning is true, then op is successfull since we dont look at versions.
|
271
|
+
|
272
|
+
##if false, then op is successfull only if version == expected version.
|
273
|
+
|
274
|
+
##finally call after_persist to set all the changed fields on the document.
|
275
|
+
##this becomes relevant especially in case where you pass in an optional update hash with an "$inc" for some field. The incremented value is not there on the instance, since the instance has the older value and this must be set if the op is successfull on the instance.
|
276
|
+
def versioned_update(dirty_fields={},bypass_versioning=false,optional_update_hash={},log=false)
|
277
|
+
self.send("op_success=",false)
|
278
|
+
query = {}
|
279
|
+
options = {}
|
280
|
+
update = {}
|
281
|
+
curr_doc = as_document
|
282
|
+
|
283
|
+
|
284
|
+
|
285
|
+
##if the dirty fields are empty then it becomes equal to a hash whose keys are the document attributes, and whose values for each key are nil,
|
286
|
+
##otherwise dirty_fields stays as it is.
|
287
|
+
dirty_fields = dirty_fields.empty? ? Hash[curr_doc.keys.zip([])] : dirty_fields
|
288
|
+
|
289
|
+
if curr_doc["version"] > 0
|
290
|
+
|
291
|
+
if !bypass_versioning
|
292
|
+
query["version"] = curr_doc["version"]
|
293
|
+
end
|
294
|
+
query["_id"] = curr_doc["_id"]
|
295
|
+
update["$set"] = {}
|
296
|
+
options[:upsert] = false
|
297
|
+
expected_version = curr_doc["version"] + 1
|
298
|
+
|
299
|
+
##what happens is that we send the update["$set"][k] to whatever was stored in the dirty_fields.
|
300
|
+
|
301
|
+
prepare_update(options) do
|
302
|
+
|
303
|
+
dirty_fields.keys.each do |k|
|
304
|
+
if (k != "version" && k != "_id" && k != "op_success")
|
305
|
+
update["$set"][k] = self.send(k.to_sym)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
update = optional_update_hash.empty? ? update : optional_update_hash
|
311
|
+
|
312
|
+
options,update = self.class.before_persist(options,update,bypass_versioning)
|
313
|
+
|
314
|
+
self.class.log_opts(query,update,options,"update",log)
|
315
|
+
|
316
|
+
write_result = collection.update_one(query,update,options)
|
317
|
+
|
318
|
+
if write_result.modified_count == 1
|
319
|
+
self.send("op_success=",true)
|
320
|
+
persisted_doc = self.class.to_s.constantize.find(self.to_param)
|
321
|
+
persisted_doc = persisted_doc.attributes
|
322
|
+
self.class.after_persist(persisted_doc,self)
|
323
|
+
else
|
324
|
+
self.send("op_success=",false)
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
return query,update,options
|
332
|
+
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|