acts_as_soft_delete_by_field 1.0.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.
- 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
|
+
|