mongoid_versioned_atomic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +34 -0
  4. data/lib/mongoid_versioned_atomic.rb +4 -0
  5. data/lib/mongoid_versioned_atomic/v_atomic.rb +336 -0
  6. data/lib/mongoid_versioned_atomic/version.rb +3 -0
  7. data/lib/tasks/mongoid_versioned_atomic_tasks.rake +4 -0
  8. data/test/dummy/README.rdoc +28 -0
  9. data/test/dummy/Rakefile +6 -0
  10. data/test/dummy/app/assets/images/facebook.png +0 -0
  11. data/test/dummy/app/assets/images/keratoscope.jpg +0 -0
  12. data/test/dummy/app/assets/javascripts/application.js +13 -0
  13. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  14. data/test/dummy/app/controllers/application_controller.rb +5 -0
  15. data/test/dummy/app/helpers/application_helper.rb +2 -0
  16. data/test/dummy/app/models/entry.rb +11 -0
  17. data/test/dummy/app/models/thing.rb +5 -0
  18. data/test/dummy/app/models/user.rb +51 -0
  19. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  20. data/test/dummy/bin/bundle +3 -0
  21. data/test/dummy/bin/rails +4 -0
  22. data/test/dummy/bin/rake +4 -0
  23. data/test/dummy/bin/setup +29 -0
  24. data/test/dummy/config.ru +4 -0
  25. data/test/dummy/config/application.rb +29 -0
  26. data/test/dummy/config/boot.rb +5 -0
  27. data/test/dummy/config/environment.rb +5 -0
  28. data/test/dummy/config/environments/development.rb +38 -0
  29. data/test/dummy/config/environments/production.rb +76 -0
  30. data/test/dummy/config/environments/test.rb +42 -0
  31. data/test/dummy/config/initializers/assets.rb +11 -0
  32. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  34. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  35. data/test/dummy/config/initializers/inflections.rb +16 -0
  36. data/test/dummy/config/initializers/mime_types.rb +4 -0
  37. data/test/dummy/config/initializers/session_store.rb +3 -0
  38. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  39. data/test/dummy/config/locales/en.yml +23 -0
  40. data/test/dummy/config/mongoid.yml +137 -0
  41. data/test/dummy/config/routes.rb +56 -0
  42. data/test/dummy/config/secrets.yml +22 -0
  43. data/test/dummy/log/development.log +21 -0
  44. data/test/dummy/log/test.log +21976 -0
  45. data/test/dummy/public/404.html +67 -0
  46. data/test/dummy/public/422.html +67 -0
  47. data/test/dummy/public/500.html +66 -0
  48. data/test/dummy/public/favicon.ico +0 -0
  49. data/test/dummy/test/fixtures/users.yml +9 -0
  50. data/test/dummy/test/models/user_test.rb +7 -0
  51. data/test/mongoid_versioned_atomic_test.rb +7 -0
  52. data/test/test_helper.rb +19 -0
  53. data/test/v_atomic_test.rb +569 -0
  54. 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,4 @@
1
+ require "mongoid"
2
+ require "mongoid_versioned_atomic/v_atomic"
3
+ module MongoidVersionedAtomic
4
+ end
@@ -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,3 @@
1
+ module MongoidVersionedAtomic
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :mongoid_versioned_atomic do
3
+ # # Task goes here
4
+ # 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>.