acts_as_soft_delete_by_field 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README +114 -0
- data/Rakefile +23 -0
- data/lib/acts_as_soft_delete_by_field.rb +5 -0
- data/lib/acts_as_soft_delete_by_field_assertions.rb +240 -0
- data/lib/soft_delete_by_field.rb +112 -0
- metadata +86 -0
data/README
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
Soft Delete By Field
|
2
|
+
===================
|
3
|
+
|
4
|
+
Provides mechanism for soft delete functionality. Soft delete means that on
|
5
|
+
delete, an object is flagged as deleted rather than being removed from the
|
6
|
+
database. This makes it far easier to recover objects that have
|
7
|
+
been deleted by mistake.
|
8
|
+
|
9
|
+
This soft delete mechanism uses a field in a models table, to flag and
|
10
|
+
record the date of deletion.
|
11
|
+
|
12
|
+
Requirements
|
13
|
+
------------
|
14
|
+
This plugin is designed for Rails 3. The syntax of the scope options
|
15
|
+
will not work with Rails 2.
|
16
|
+
|
17
|
+
The model object needs to have a datetime field, that on
|
18
|
+
delete will be set to the current time. By default, this field should
|
19
|
+
be :deleted_at, but you can also specify your own field.
|
20
|
+
|
21
|
+
Usage
|
22
|
+
-----
|
23
|
+
|
24
|
+
class Thing < ActiveRecord::Base
|
25
|
+
|
26
|
+
acts_as_soft_delete_by_field
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
If you wish to use a field other than :deleted_at, for example :soft_deleted_at,
|
31
|
+
you can do this via the acts_as_soft_delete_by_field declaration:
|
32
|
+
|
33
|
+
class Thing < ActiveRecord::Base
|
34
|
+
|
35
|
+
acts_as_soft_delete_by_field(:soft_deleted_at)
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
Functionality
|
40
|
+
-------------
|
41
|
+
Adds 'extant' and 'deleted' scopes to the model. For example, if added
|
42
|
+
to a Thing model, would allow you to do the following:
|
43
|
+
|
44
|
+
thing.soft_delete ---- Soft deletes an instance of thing
|
45
|
+
|
46
|
+
Thing.extant ---- Finds all things that are not deleted
|
47
|
+
Thing.deleted.count ---- Counts how many things have been deleted
|
48
|
+
|
49
|
+
Thing.extant.find(
|
50
|
+
:all,
|
51
|
+
:conditions => ["colour = ?", 'red']
|
52
|
+
) ---- Finds all extant things that have colour set as red
|
53
|
+
|
54
|
+
Thing.extant.where(:colour => 'red') ---- As above using where
|
55
|
+
|
56
|
+
|
57
|
+
Also if Box has_many things
|
58
|
+
|
59
|
+
box.things.deleted ---- Finds all the deleted things in the box
|
60
|
+
|
61
|
+
Note that Thing's delete instance method is not effected by this functionality
|
62
|
+
so:
|
63
|
+
|
64
|
+
thing.soft_delete --- alters the thing instance to be flagged as deleted
|
65
|
+
thing.delete --- deletes the thing from the database.
|
66
|
+
|
67
|
+
If you want delete actions to soft_delete, overwrite delete:
|
68
|
+
|
69
|
+
class Thing
|
70
|
+
|
71
|
+
acts_as_soft_delete_by_field
|
72
|
+
|
73
|
+
def delete
|
74
|
+
soft_delete
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
Callbacks
|
80
|
+
---------
|
81
|
+
|
82
|
+
There are also two callback methods: before_soft_delete and after_soft_delete
|
83
|
+
Overwrite these methods in the model to use them. For example:
|
84
|
+
|
85
|
+
class Thing
|
86
|
+
acts_as_soft_delete_by_field
|
87
|
+
|
88
|
+
def after_soft_delete
|
89
|
+
puts "Thing soft deleted"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
Now, when a thing is soft deleted, "Thing soft deleted" will printed be to the
|
94
|
+
console.
|
95
|
+
|
96
|
+
Testing
|
97
|
+
-------
|
98
|
+
|
99
|
+
A number of custom assertions are made available when this plugin is installed.
|
100
|
+
They are held in the module ActsAsSoftDeleteByFieldAssertions. In particular,
|
101
|
+
assert_soft_delete_working_for works through each of the assertions and can
|
102
|
+
be used as a test that soft deletion is working correctly on any model where
|
103
|
+
it is used.
|
104
|
+
|
105
|
+
class ThingTest < ActiveSupport::TestCase
|
106
|
+
|
107
|
+
def test_soft_delete
|
108
|
+
assert_soft_delete_working_for(Thing.first)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
Copyright (c) 2011 Rob Nichols, released under the MIT license
|
114
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the acts_as_soft_delete plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the acts_as_soft_delete plugin.'
|
17
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'ActsAsSoftDelete'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# Provides assertion methods that make it easier to test soft deletion functionality
|
2
|
+
#
|
3
|
+
# The soft delete functionality can be tested via:
|
4
|
+
#
|
5
|
+
# class ThingTest < ActiveSupport::TestCase
|
6
|
+
#
|
7
|
+
# def test_soft_delete
|
8
|
+
# assert_soft_delete_working_for(Thing.first)
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
module ActsAsSoftDeleteByFieldAssertions
|
14
|
+
|
15
|
+
def assert_soft_delete_by_field_working_for(item)
|
16
|
+
# Using clones, as otherwise other assertions affected
|
17
|
+
@starting_number = item.class.count
|
18
|
+
assert_before_soft_delete_all_correct_for(item)
|
19
|
+
@start_time = Time.now
|
20
|
+
item.soft_delete
|
21
|
+
assert_extant_working_for(item)
|
22
|
+
assert_deleted_working_for(item)
|
23
|
+
assert_still_in_database_but_with_deleted_at_set(item)
|
24
|
+
assert_soft_deleted(item)
|
25
|
+
assert_recover_soft_deleted(item)
|
26
|
+
assert_deletion_prevented_if_reasons_not_to_delete_overwritten_with_method_returning_true(item.clone)
|
27
|
+
assert_deletion_prevented_if_reasons_not_to_delete_detected(item)
|
28
|
+
assert_before_soft_delete(item.clone)
|
29
|
+
assert_after_soft_delete(item.clone)
|
30
|
+
end
|
31
|
+
|
32
|
+
def assert_soft_deleted(item)
|
33
|
+
assert_equal(true, item.reload.is_deleted?, error_messages_for(item, "Not deleted: #{item.inspect}"))
|
34
|
+
end
|
35
|
+
|
36
|
+
def assert_extant(item)
|
37
|
+
deletion_object_details = ". Deletion_object: #{@deletion_objects.inspect}" if @deletion_objects
|
38
|
+
assert_equal(true, item.reload.is_extant?, error_messages_for(item, "Not extant: #{item.inspect}"))
|
39
|
+
end
|
40
|
+
|
41
|
+
def assert_get_request_to_delete_raises_error
|
42
|
+
login_as User.first
|
43
|
+
assert_raise RuntimeError do
|
44
|
+
get :delete
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def assert_recover_soft_deleted(item)
|
49
|
+
item.recover_soft_deleted
|
50
|
+
assert_before_soft_delete_all_correct_for(item)
|
51
|
+
end
|
52
|
+
|
53
|
+
def assert_before_soft_delete_all_correct_for(item)
|
54
|
+
assert_all_extant(item)
|
55
|
+
assert_extant_includes(item)
|
56
|
+
assert_none_soft_deleted(item)
|
57
|
+
assert_extant(item)
|
58
|
+
end
|
59
|
+
|
60
|
+
def assert_extant_includes(item)
|
61
|
+
assert item.class.extant.include?(item)
|
62
|
+
end
|
63
|
+
|
64
|
+
def assert_all_extant(item)
|
65
|
+
assert_equal item.class.count, item.class.extant.count
|
66
|
+
end
|
67
|
+
|
68
|
+
def assert_none_soft_deleted(item)
|
69
|
+
assert_equal 0, item.class.deleted.count
|
70
|
+
end
|
71
|
+
|
72
|
+
def assert_extant_working_for(item)
|
73
|
+
assert_equal @starting_number - 1, item.class.extant.count
|
74
|
+
assert !item.class.extant.include?(item)
|
75
|
+
end
|
76
|
+
|
77
|
+
def assert_deleted_working_for(item)
|
78
|
+
assert_equal 1, item.class.deleted.count
|
79
|
+
assert_equal item, item.class.deleted.first
|
80
|
+
end
|
81
|
+
|
82
|
+
def assert_still_in_database_but_with_deleted_at_set(item)
|
83
|
+
assert_object_still_in_database(item)
|
84
|
+
assert_deleted_at_set_as_time_after_test_started_for(item)
|
85
|
+
end
|
86
|
+
|
87
|
+
def assert_deleted_at_set_as_time_after_test_started_for(object)
|
88
|
+
assert object.deleted_via_soft_delete_by_field_name_at >= @start_time, "Deletion occured before test was run (#{object.deleted_via_soft_delete_by_field_name_at})"
|
89
|
+
end
|
90
|
+
|
91
|
+
def assert_object_still_in_database(object)
|
92
|
+
assert_equal object, object.class.find(object.id)
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_soft_deletion_objects(object, source_path)
|
96
|
+
@source_path = source_path
|
97
|
+
@object_class = object.class.to_s
|
98
|
+
@object_id = object.id.to_s
|
99
|
+
@deletion_objects = {
|
100
|
+
:source_path => @source_path,
|
101
|
+
:object_class => @object_class,
|
102
|
+
:object_id => @object_id
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def assert_soft_deletion_via_delete_action(object, path)
|
107
|
+
create_deletion_objects_and_pass_to_delete(object, path)
|
108
|
+
assert_redirection_to_path(@source_path)
|
109
|
+
assert_soft_deleted(object)
|
110
|
+
end
|
111
|
+
|
112
|
+
def assert_failure_to_soft_delete_via_delete_action(object, path)
|
113
|
+
create_deletion_objects_and_pass_to_delete(object, path)
|
114
|
+
assert_redirection_to_path(@source_path)
|
115
|
+
assert_extant(object)
|
116
|
+
end
|
117
|
+
|
118
|
+
def create_deletion_objects_and_pass_to_delete(object, path)
|
119
|
+
create_soft_deletion_objects(object, path)
|
120
|
+
@deletion_objects
|
121
|
+
post :delete, @deletion_objects
|
122
|
+
end
|
123
|
+
|
124
|
+
def assert_undeletion_via_recover_soft_deleted_action(object, path)
|
125
|
+
object.soft_delete
|
126
|
+
create_deletion_objects_and_pass_to_recover_soft_deleted(object, path)
|
127
|
+
assert_redirection_to_path(@source_path)
|
128
|
+
assert_extant(object.reload)
|
129
|
+
end
|
130
|
+
|
131
|
+
def create_deletion_objects_and_pass_to_recover_soft_deleted(object, path)
|
132
|
+
create_soft_deletion_objects(object, path)
|
133
|
+
@deletion_objects
|
134
|
+
post :recover_soft_deleted, @deletion_objects
|
135
|
+
end
|
136
|
+
|
137
|
+
def assert_deletion_prevented_if_reasons_not_to_delete_overwritten_with_method_returning_true(item)
|
138
|
+
if model_has_custom_reason_not_to_delete_defined(item)
|
139
|
+
# This test not valid in this case
|
140
|
+
else
|
141
|
+
assert_soft_deletion_working(item)
|
142
|
+
method_to_add_to_item = <<EOF
|
143
|
+
|
144
|
+
def reasons_not_to_delete?
|
145
|
+
true
|
146
|
+
end
|
147
|
+
|
148
|
+
EOF
|
149
|
+
|
150
|
+
item.class_eval(method_to_add_to_item)
|
151
|
+
assert_reasons_not_to_delete_prevents_soft_deletion(item)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def assert_deletion_prevented_if_reasons_not_to_delete_detected(item)
|
156
|
+
if model_has_custom_reason_not_to_delete_defined(item)
|
157
|
+
# This test not valid in this case
|
158
|
+
else
|
159
|
+
assert_soft_deletion_working(item)
|
160
|
+
initial_reasons_not_to_delete = item.reasons_not_to_delete
|
161
|
+
item.reasons_not_to_delete = "Don't delete"
|
162
|
+
assert_reasons_not_to_delete_prevents_soft_deletion(item)
|
163
|
+
item.reasons_not_to_delete = initial_reasons_not_to_delete
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def assert_before_soft_delete(item)
|
168
|
+
message = "Hello world"
|
169
|
+
assert_soft_deletion_working(item)
|
170
|
+
assert(!item.respond_to?(:goodbye))
|
171
|
+
method_to_add_to_item = <<EOF
|
172
|
+
|
173
|
+
def goodbye
|
174
|
+
'#{message}'
|
175
|
+
end
|
176
|
+
|
177
|
+
EOF
|
178
|
+
item.class_eval(method_to_add_to_item)
|
179
|
+
item.soft_delete
|
180
|
+
assert item.is_deleted?, 'Item not deleted'
|
181
|
+
assert_equal message, item.goodbye
|
182
|
+
end
|
183
|
+
|
184
|
+
def assert_after_soft_delete(item)
|
185
|
+
message = 'Hello there'
|
186
|
+
assert_soft_deletion_working(item)
|
187
|
+
assert(!item.respond_to?(:hello))
|
188
|
+
method_to_add_to_item = <<EOF
|
189
|
+
|
190
|
+
def hello
|
191
|
+
'#{message}'
|
192
|
+
end
|
193
|
+
|
194
|
+
EOF
|
195
|
+
item.class_eval(method_to_add_to_item)
|
196
|
+
item.soft_delete
|
197
|
+
assert item.is_deleted?, 'Item not deleted'
|
198
|
+
assert_equal message, item.hello
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
private
|
203
|
+
def error_messages_for(item, first_message)
|
204
|
+
error_messages = [first_message]
|
205
|
+
error_messages << item.errors.full_messages unless item.errors.empty?
|
206
|
+
error_messages << "Flash: #{@request.session['flash'].inspect}" if @request
|
207
|
+
error_messages << "Deletion_object: #{@deletion_objects.inspect}" if @deletion_objects
|
208
|
+
error_messages.join(". ")
|
209
|
+
end
|
210
|
+
|
211
|
+
def assert_extant_at_start_of_test(item)
|
212
|
+
assert(item.is_extant?, "Item must be extant at start of test #{item.inspect}")
|
213
|
+
end
|
214
|
+
|
215
|
+
def assert_reasons_not_to_delete_prevents_soft_deletion(item)
|
216
|
+
item.soft_delete
|
217
|
+
assert(item.is_extant?, "Item was deleted in spite of reasons_not_to_delete? being true")
|
218
|
+
assert_errors_on_base(item)
|
219
|
+
end
|
220
|
+
|
221
|
+
def assert_soft_deletion_working(item)
|
222
|
+
item.soft_delete
|
223
|
+
assert(item.is_deleted?, "Item should be deleted. item: #{item.inspect}")
|
224
|
+
item.recover_soft_deleted
|
225
|
+
assert(item.is_extant?, "Item should be extant")
|
226
|
+
end
|
227
|
+
|
228
|
+
def model_has_custom_reason_not_to_delete_defined(item)
|
229
|
+
item.class.private_method_defined?('reasons_not_to_delete?') or (item.reasons_not_to_delete != item.reasons_not_to_delete?)
|
230
|
+
end
|
231
|
+
|
232
|
+
def assert_errors_on_base(object)
|
233
|
+
assert(object.errors[:base],
|
234
|
+
"Should have Errors on base detected for object: #{object.inspect}"
|
235
|
+
)
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
ActiveSupport::TestCase.send(:include, ActsAsSoftDeleteByFieldAssertions)
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
module Acts #:nodoc:
|
3
|
+
module SoftDeleteByField
|
4
|
+
DEFAULT_FIELD_NAME = :deleted_at
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.send :extend, ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
def soft_delete_by_field_name
|
13
|
+
@soft_delete_by_field_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def acts_as_soft_delete_by_field(field_name = nil)
|
17
|
+
attr_accessor :reasons_not_to_delete
|
18
|
+
field_name ||= DEFAULT_FIELD_NAME
|
19
|
+
@soft_delete_by_field_name = field_name.to_sym
|
20
|
+
send :include, SoftDeleteByField::InstanceMethods
|
21
|
+
scope :extant, where(["#{(self.class.name.tableize + ".") if self.class.kind_of?(SoftDeleteByField)}#{field_name} IS NULL"])
|
22
|
+
scope :deleted, where(["#{(self.class.name.tableize + ".") if self.class.kind_of?(SoftDeleteByField)}#{field_name} IS NOT NULL"])
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
module InstanceMethods
|
28
|
+
|
29
|
+
|
30
|
+
# Overwrite this method to add actions that are triggered before soft_delete operates.
|
31
|
+
def before_soft_delete
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
# Rename this 'delete' if you wish this functionality to replace the
|
36
|
+
# default delete behaviour.
|
37
|
+
def soft_delete
|
38
|
+
before_soft_delete
|
39
|
+
unless reasons_not_to_delete?
|
40
|
+
update_attribute(soft_delete_by_field_name, Time.now)
|
41
|
+
after_soft_delete
|
42
|
+
return 'deleted'
|
43
|
+
else
|
44
|
+
errors.add(:base, "Unable to delete. reasons_not_to_delete returns #{@reasons_not_to_delete || 'true'} for #{inspect}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Overwrite this method to add actions that are triggered after soft_delete operates.
|
49
|
+
def after_soft_delete
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
# Overwrite this method to add actions that are triggered before recover_soft_deleted operates.
|
54
|
+
def before_recover_soft_deleted
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def recover_soft_deleted
|
59
|
+
before_recover_soft_deleted
|
60
|
+
update_attribute(soft_delete_by_field_name, nil)
|
61
|
+
after_recover_soft_deleted
|
62
|
+
end
|
63
|
+
|
64
|
+
# Overwrite this method to add actions that are triggered after recover_soft_deleted operates.
|
65
|
+
def after_recover_soft_deleted
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
def is_deleted?
|
70
|
+
send(soft_delete_by_field_name) != nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def deleted?
|
74
|
+
is_deleted?
|
75
|
+
end
|
76
|
+
|
77
|
+
def is_extant?
|
78
|
+
!send(soft_delete_by_field_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def extant?
|
82
|
+
is_extant?
|
83
|
+
end
|
84
|
+
|
85
|
+
# You can either overwrite this method to add methods that prevent soft
|
86
|
+
# deletion, or set @reasons_not_to_delete to true. For example, set
|
87
|
+
# @reasons_not_to_delete = 'Component needs to be deleted first'
|
88
|
+
def reasons_not_to_delete?
|
89
|
+
@reasons_not_to_delete
|
90
|
+
end
|
91
|
+
|
92
|
+
def deleted_via_soft_delete_by_field_name_at
|
93
|
+
send(soft_delete_by_field_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def table_name_modifier
|
98
|
+
klass = self.class
|
99
|
+
if klass.kind_of?(SoftDeleteByField) and !klass.instance_of(SoftDeleteByField)
|
100
|
+
"#{klass.name.tableize}."
|
101
|
+
else
|
102
|
+
""
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def soft_delete_by_field_name
|
107
|
+
self.class.soft_delete_by_field_name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_soft_delete_by_field
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 1.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Rob Nichols
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-05 00:00:00 +01:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activerecord
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 3.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description: "Acts as Soft Delete by Field: Provides soft deletion for ActiveRecord models using a field to flag the datetime of deletion"
|
38
|
+
email: rob@nicholshayes.co.uk
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- README
|
47
|
+
- Rakefile
|
48
|
+
- lib/acts_as_soft_delete_by_field_assertions.rb
|
49
|
+
- lib/acts_as_soft_delete_by_field.rb
|
50
|
+
- lib/soft_delete_by_field.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: https://github.com/reggieb/acts_as_soft_delete_by_field
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.7
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Provides soft deletion for ActiveRecord models
|
85
|
+
test_files: []
|
86
|
+
|