mongoid_paranoia 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NjFjZDkxZDIzNmM0MzRjMDNiODYyNmRkOGZjNWNiMzljZmI1NGU2Yg==
5
+ data.tar.gz: !binary |-
6
+ NjAxNjc2MjRiYjAxNWE5ZTc5M2UyYTRiNTQ4ZmZmODRmMzJhYjY5YQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ YjBiYmQ2Zjc3MjEyZjAzZTVkNTM2ZDQ4NWI4NzYwM2YzMWIzODRjY2NmYmNl
10
+ ZDIxZmVjNDE1NTI0ZTYzOTBmNWI2NGE0YzQ4ZDVhOTA5NjhhZGMwNDgwMmFh
11
+ MGFhZDNhYTkwZTQ0ZTc3ZTNlNGNiOWE3MThmMWY1NTNkOGNiMzA=
12
+ data.tar.gz: !binary |-
13
+ NjkzMTE1N2UxMDY1ZWEyYWJmNjA0Njg1Zjk0MWYxMmRkZjExZDEyNWQzZjE2
14
+ YzA0ZmUyNzRlZjc3YjRiM2M0NTI3ZTE2MzQ2NTNjOWJiMGExZTJiMWI4ZmZm
15
+ Zjc1MGM1MTU2YTgxZjNiOGY2NGExOGI4OTdlMjY5YjBjNWVkMWY=
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Josef Šimánek
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # Paranoid Documents for Mongoid 4 [![Build Status](https://travis-ci.org/simi/mongoid-paranoia.png?branch=master)](https://travis-ci.org/simi/mongoid-paranoia)
2
+
3
+ `Mongoid::Paranoia` enables a "soft delete" of Mongoid documents. Instead of being removed from the database, paranoid docs are flagged with a `deleted_at` timestamp and are ignored from queries by default.
4
+
5
+ The `Mongoid::Paranoia` functionality was originally supported in Mongoid itself, but was dropped from version 4.0.0 onwards. This gem was extracted from the [Mongoid 3.0.0-stable branch](https://github.com/simi/mongoid-paranoia/tree/3.0.0-stable). This gem should not be used with Mongoid versions 3.x and prior.
6
+
7
+ **Caution:** This repo/gem `mongoid_paranoia` (underscored) is different than `mongoid-paranoia` (hyphenated).
8
+ The owner of `mongoid-paranoia` (hyphenated) is not accepting enhancements and has declined our requests
9
+ to share maintenance of the gem.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'mongoid_paranoia'
17
+ ```
18
+
19
+ ## Changes in 4.0
20
+
21
+ ### Uniqueness validator is not overriden
22
+
23
+ #### Old syntax:
24
+ ```ruby
25
+ validates_uniqueness_of :title
26
+ validates :title, :uniqueness => true
27
+ ```
28
+
29
+ #### New syntax:
30
+ ```ruby
31
+ validates :title, uniqueness: { conditions: -> { where(deleted_at: nil) } }
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```ruby
37
+ class Person
38
+ include Mongoid::Document
39
+ include Mongoid::Paranoia
40
+ end
41
+
42
+ person.delete # Sets the deleted_at field to the current time, ignoring callbacks.
43
+ person.delete! # Permanently deletes the document, ignoring callbacks.
44
+ person.destroy # Sets the deleted_at field to the current time, firing callbacks.
45
+ person.destroy! # Permanently deletes the document, firing callbacks.
46
+ person.restore # Brings the "deleted" document back to life.
47
+ ```
48
+
49
+ The documents that have been "flagged" as deleted (soft deleted) can be accessed at any time by calling the deleted class method on the class.
50
+
51
+ ```ruby
52
+ Person.deleted # Returns documents that have been "flagged" as deleted.
53
+ ```
54
+
55
+ You can also access all documents (both deleted and non-deleted) at any time by using the `unscoped` class method:
56
+
57
+ ```ruby
58
+ Person.unscoped.all # Returns all documents, both deleted and non-deleted
59
+ ```
60
+
61
+ ### Callbacks ([@zhouguangming](https://github.com/zhouguangming))
62
+
63
+ `before_restore`, `after_restore` and `around_restore` callbacks are added to your model. They work similarly to the `before_destroy`, `after_destroy` and `around_destroy` callbacks.
64
+
65
+ ```ruby
66
+ class User
67
+ include Mongoid::Document
68
+ include Mongoid::Paranoia
69
+
70
+ before_restore :before_restore_action
71
+ after_restore :after_restore_action
72
+ around_restore :around_restore_action
73
+
74
+ private
75
+
76
+ def before_restore_action
77
+ puts "BEFORE"
78
+ end
79
+
80
+ def after_restore_action
81
+ puts "AFTER"
82
+ end
83
+
84
+ def around_restore_action
85
+ puts "AROUND - BEFORE"
86
+ yield # restoring
87
+ puts "AROUND - AFTER"
88
+ end
89
+ end
90
+ ```
91
+
92
+ ## TODO
93
+ - get rid of [monkey_patches.rb](https://github.com/simi/mongoid-paranoia/blob/master/lib/mongoid/paranoia/monkey_patches.rb)
94
+ - [review persisted? behaviour](https://github.com/simi/mongoid-paranoia/issues/2)
95
+
96
+ ## Authors
97
+
98
+ * original [Mongoid](https://github.com/mongoid/mongoid) implementation by [@durran](https://github.com/durran)
99
+ * extracted from [Mongoid](https://github.com/mongoid/mongoid) by [@simi](https://github.com/simi)
100
+ * [documentation improvements](https://github.com/simi/mongoid-paranoia/pull/3) by awesome [@loopj](https://github.com/loopj)
101
+ * [latest mongoid support, restore_callback support](https://github.com/simi/mongoid-paranoia/pull/8) by fabulous [@zhouguangming](https://github.com/zhouguangming)
102
+
103
+
104
+ ## Contributing
105
+
106
+ 1. Fork it
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "mongoid/paranoia"
@@ -0,0 +1,154 @@
1
+ # encoding: utf-8
2
+ require 'mongoid/paranoia/monkey_patches'
3
+ require 'active_support'
4
+ require 'active_support/deprecation'
5
+
6
+ module Mongoid
7
+
8
+ # Include this module to get soft deletion of root level documents.
9
+ # This will add a deleted_at field to the +Document+, managed automatically.
10
+ # Potentially incompatible with unique indices. (if collisions with deleted items)
11
+ #
12
+ # @example Make a document paranoid.
13
+ # class Person
14
+ # include Mongoid::Document
15
+ # include Mongoid::Paranoia
16
+ # end
17
+ module Paranoia
18
+ include Mongoid::Persistable::Deletable
19
+ extend ActiveSupport::Concern
20
+
21
+ included do
22
+ field :deleted_at, type: Time
23
+ self.paranoid = true
24
+
25
+ default_scope -> { where(deleted_at: nil) }
26
+ scope :deleted, -> { ne(deleted_at: nil) }
27
+ define_model_callbacks :restore
28
+ end
29
+
30
+ # Delete the paranoid +Document+ from the database completely. This will
31
+ # run the destroy callbacks.
32
+ #
33
+ # @example Hard destroy the document.
34
+ # document.destroy!
35
+ #
36
+ # @return [ true, false ] If the operation succeeded.
37
+ #
38
+ # @since 1.0.0
39
+ def destroy!
40
+ run_callbacks(:destroy) { delete! }
41
+ end
42
+
43
+ # Override the persisted method to allow for the paranoia gem.
44
+ # If a paranoid record is selected, then we only want to check
45
+ # if it's a new record, not if it is "destroyed"
46
+ #
47
+ # @example
48
+ # document.persisted?
49
+ #
50
+ # @return [ true, false ] If the operation succeeded.
51
+ #
52
+ # @since 4.0.0
53
+ def persisted?
54
+ !new_record?
55
+ end
56
+
57
+ # Delete the +Document+, will set the deleted_at timestamp and not actually
58
+ # delete it.
59
+ #
60
+ # @example Soft remove the document.
61
+ # document.remove
62
+ #
63
+ # @param [ Hash ] options The database options.
64
+ #
65
+ # @return [ true ] True.
66
+ #
67
+ # @since 1.0.0
68
+ def remove_with_paranoia(options = {})
69
+ cascade!
70
+ time = self.deleted_at = Time.now
71
+ paranoid_collection.find(atomic_selector).
72
+ update({ "$set" => { paranoid_field => time }})
73
+ @destroyed = true
74
+ true
75
+ end
76
+ alias_method_chain :remove, :paranoia
77
+ alias :delete :remove
78
+
79
+ # Delete the paranoid +Document+ from the database completely.
80
+ #
81
+ # @example Hard delete the document.
82
+ # document.delete!
83
+ #
84
+ # @return [ true, false ] If the operation succeeded.
85
+ #
86
+ # @since 1.0.0
87
+ def delete!
88
+ remove_without_paranoia
89
+ end
90
+
91
+ # Determines if this document is destroyed.
92
+ #
93
+ # @example Is the document destroyed?
94
+ # person.destroyed?
95
+ #
96
+ # @return [ true, false ] If the document is destroyed.
97
+ #
98
+ # @since 1.0.0
99
+ def destroyed?
100
+ (@destroyed ||= false) || !!deleted_at
101
+ end
102
+ alias :deleted? :destroyed?
103
+
104
+ # Restores a previously soft-deleted document. Handles this by removing the
105
+ # deleted_at flag.
106
+ #
107
+ # @example Restore the document from deleted state.
108
+ # document.restore
109
+ #
110
+ # TODO: @return [ Time ] The time the document had been deleted.
111
+ #
112
+ # @since 1.0.0
113
+ def restore
114
+ run_callbacks(:restore) do
115
+ paranoid_collection.find(atomic_selector).
116
+ update({ "$unset" => { paranoid_field => true }})
117
+ attributes.delete("deleted_at")
118
+ @destroyed = false
119
+ true
120
+ end
121
+ end
122
+
123
+ # Returns a string representing the documents's key suitable for use in URLs.
124
+ def to_param
125
+ new_record? ? nil : to_key.join('-')
126
+ end
127
+
128
+ private
129
+
130
+ # Get the collection to be used for paranoid operations.
131
+ #
132
+ # @example Get the paranoid collection.
133
+ # document.paranoid_collection
134
+ #
135
+ # @return [ Collection ] The root collection.
136
+ #
137
+ # @since 2.3.1
138
+ def paranoid_collection
139
+ embedded? ? _root.collection : self.collection
140
+ end
141
+
142
+ # Get the field to be used for paranoid operations.
143
+ #
144
+ # @example Get the paranoid field.
145
+ # document.paranoid_field
146
+ #
147
+ # @return [ String ] The deleted at field.
148
+ #
149
+ # @since 2.3.1
150
+ def paranoid_field
151
+ embedded? ? "#{atomic_position}.deleted_at" : "deleted_at"
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,113 @@
1
+ # encoding: utf-8
2
+ module Mongoid
3
+ module Paranoia
4
+ module Document
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # Indicates whether or not the document includes Mongoid::Paranoia.
9
+ # In Mongoid 3, this method was defined on all Mongoid::Documents.
10
+ # In Mongoid 4, it is no longer defined, hence we are shimming it here.
11
+ class_attribute :paranoid
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ Mongoid::Document.send(:include, Mongoid::Paranoia::Document)
18
+
19
+ module Mongoid
20
+ module Relations
21
+ module Builders
22
+ module NestedAttributes
23
+ class Many < NestedBuilder
24
+ # Destroy the child document, needs to do some checking for embedded
25
+ # relations and delay the destroy in case parent validation fails.
26
+ #
27
+ # @api private
28
+ #
29
+ # @example Destroy the child.
30
+ # builder.destroy(parent, relation, doc)
31
+ #
32
+ # @param [ Document ] parent The parent document.
33
+ # @param [ Proxy ] relation The relation proxy.
34
+ # @param [ Document ] doc The doc to destroy.
35
+ #
36
+ # @since 3.0.10
37
+ def destroy(parent, relation, doc)
38
+ doc.flagged_for_destroy = true
39
+ if !doc.embedded? || parent.new_record? || doc.paranoid?
40
+ destroy_document(relation, doc)
41
+ else
42
+ parent.flagged_destroys.push(->{ destroy_document(relation, doc) })
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ module Mongoid
52
+ module Relations
53
+ module Embedded
54
+ # This class handles the behaviour for a document that embeds many other
55
+ # documents within in it as an array.
56
+ class Many < Relations::Many
57
+ # Delete the supplied document from the target. This method is proxied
58
+ # in order to reindex the array after the operation occurs.
59
+ #
60
+ # @example Delete the document from the relation.
61
+ # person.addresses.delete(address)
62
+ #
63
+ # @param [ Document ] document The document to be deleted.
64
+ #
65
+ # @return [ Document, nil ] The deleted document or nil if nothing deleted.
66
+ #
67
+ # @since 2.0.0.rc.1
68
+ def delete(document)
69
+ execute_callback :before_remove, document
70
+ doc = target.delete_one(document)
71
+ if doc && !_binding?
72
+ _unscoped.delete_one(doc) unless doc.paranoid?
73
+ if _assigning?
74
+ if doc.paranoid?
75
+ doc.destroy(suppress: true)
76
+ else
77
+ base.add_atomic_pull(doc)
78
+ end
79
+ else
80
+ doc.delete(suppress: true)
81
+ unbind_one(doc)
82
+ end
83
+ end
84
+ reindex
85
+ execute_callback :after_remove, document
86
+ doc
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ module Mongoid
94
+ module Relations
95
+ module Embedded
96
+ class Many < Relations::Many
97
+ # For use only with Mongoid::Paranoia - will be removed in 4.0.
98
+ #
99
+ # @example Get the deleted documents from the relation.
100
+ # person.paranoid_phones.deleted
101
+ #
102
+ # @return [ Criteria ] The deleted documents.
103
+ #
104
+ # @since 3.0.10
105
+ def deleted
106
+ unscoped.deleted
107
+ end
108
+ # This class handles the behaviour for a document that embeds many other
109
+ # documents within in it as an array.
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,5 @@
1
+ module Mongoid
2
+ module Paranoia
3
+ VERSION = '0.1.2'
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require "mongoid/paranoia"
data/perf/scope.rb ADDED
@@ -0,0 +1,64 @@
1
+ require 'bundler/setup'
2
+ require 'mongoid'
3
+ require 'mongoid/paranoia'
4
+ require 'benchmark'
5
+
6
+
7
+ Mongoid.configure do |config|
8
+ config.connect_to('my_little_test')
9
+ end
10
+
11
+ class Model
12
+ include Mongoid::Document
13
+ field :text, type: String
14
+
15
+ index({ text: "text" })
16
+ end
17
+
18
+ class ParanoidModel
19
+ include Mongoid::Document
20
+ include Mongoid::Paranoia
21
+ field :text, type: String
22
+
23
+ index({ text: "text" })
24
+ end
25
+
26
+ class MetaParanoidModel
27
+ include Mongoid::Document
28
+ field :text, type: String
29
+ field :deleted_at, type: Time
30
+ default_scope -> { where(deleted_at: nil) }
31
+
32
+ index({ text: "text" })
33
+ end
34
+
35
+ if ENV['FORCE']
36
+ Mongoid.purge!
37
+ ::Mongoid::Tasks::Database.create_indexes
38
+
39
+ n = 50_000
40
+ n.times {|i| Model.create(text: "text #{i}")}
41
+ n.times {|i| ParanoidModel.create(text: "text #{i}")}
42
+ n.times {|i| MetaParanoidModel.create(text: "text #{i}")}
43
+ end
44
+
45
+ n = 100
46
+
47
+ puts "text_search benchmark ***"
48
+ Benchmark.bm(20) do |x|
49
+ x.report("without") { n.times { Model.text_search("text").execute } }
50
+ x.report("with") { n.times { ParanoidModel.text_search("text").execute } }
51
+ x.report("meta") { n.times { MetaParanoidModel.text_search("text").execute } }
52
+ x.report("unscoped meta") { n.times { MetaParanoidModel.unscoped.text_search("text").execute } }
53
+ x.report("unscoped paranoid") { n.times { ParanoidModel.unscoped.text_search("text").execute } }
54
+ end
55
+
56
+ puts ""
57
+ puts "Pluck all ids benchmark ***"
58
+ Benchmark.bm(20) do |x|
59
+ x.report("without") { n.times { Model.all.pluck(:id) } }
60
+ x.report("with") { n.times { ParanoidModel.all.pluck(:id) } }
61
+ x.report("meta") { n.times { MetaParanoidModel.all.pluck(:id) } }
62
+ x.report("unscoped meta") { n.times { MetaParanoidModel.unscoped.all.pluck(:id) } }
63
+ x.report("unscoped paranoid") { n.times { ParanoidModel.unscoped.all.pluck(:id) } }
64
+ end