mongoid_paranoia 0.1.2

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