destroy_soon 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.mkd +12 -0
- data/Gemfile +4 -0
- data/README.mkd +80 -0
- data/Rakefile +10 -0
- data/TODO.mkd +3 -0
- data/destroy_soon.gemspec +28 -0
- data/lib/destroy_soon.rb +9 -0
- data/lib/destroy_soon/job.rb +17 -0
- data/lib/destroy_soon/model_additions.rb +17 -0
- data/lib/destroy_soon/queue.rb +19 -0
- data/lib/destroy_soon/version.rb +3 -0
- data/spec/model_additions_spec.rb +41 -0
- data/spec/queue_spec.rb +44 -0
- data/spec/schema.rb +26 -0
- data/spec/spec_helper.rb +28 -0
- metadata +175 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.mkd
ADDED
data/Gemfile
ADDED
data/README.mkd
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
## About
|
2
|
+
|
3
|
+
DestroySoon encapsulates the common pattern of asynchronously removing ActiveRecord models. In complex systems where there is a [God](http://en.wikipedia.org/wiki/God_object) entity, the God descruction take a long time to run due to all dependent destruction. In this case it's a good ideia to place the heavy weight job to the background.
|
4
|
+
|
5
|
+
DestroySoon uses [DelayedJob](https://github.com/collectiveidea/delayed_job) as queue processing system.
|
6
|
+
|
7
|
+
[![Build Status](https://secure.travis-ci.org/redu/destroy-soon.png)](http://travis-ci.org/redu/destroy-soon)
|
8
|
+
|
9
|
+
## Quickstart
|
10
|
+
|
11
|
+
Add DestroySoon to your gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'destroy_soon', :git => 'git://github.com/redu/destroy-soon.git'
|
15
|
+
```
|
16
|
+
|
17
|
+
You just need to inject some behaivor to the object you want to destroy asynchronously:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class Course < ActiveRecord::Base
|
21
|
+
include DestroySoon::ModelAdditions
|
22
|
+
end
|
23
|
+
```
|
24
|
+
|
25
|
+
The ActiveRecord model should have a new column called ``destroy_soon``. This column will be setted to true when a destruction task is scheduled. You should generate a migration as follows:
|
26
|
+
|
27
|
+
```sh
|
28
|
+
$ rails g migration AddDestroySoonToCourse
|
29
|
+
```
|
30
|
+
|
31
|
+
Edit the migration to add the column:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class AddDestroySoonToCourse < ActiveRecord::Migration
|
35
|
+
def self.up
|
36
|
+
add_column :courses, :destroy_soon, :boolean, :default => false
|
37
|
+
add_index :courses, :destroy_soon
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.down
|
41
|
+
remove_column :courses, :destroy_soon
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
And migrate the database using ``rake db:migrate``. In order to destroy Course instance in the backgroud call the ``async_destroy`` method:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class CoursesController < BaseController
|
50
|
+
def destroy
|
51
|
+
@course = Course.find(params[:id])
|
52
|
+
@course.async_destroy
|
53
|
+
|
54
|
+
respond_with(@course)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
## Configuration
|
60
|
+
|
61
|
+
You can use DelayedJob's named queues setting the queue name as follows:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
# config/initializers/destroy_soon.rb
|
65
|
+
DestroySoon::Queue.default_queue = "destruction"
|
66
|
+
```
|
67
|
+
|
68
|
+
<img src="https://github.com/downloads/redu/redupy/redutech-marca.png" alt="Redu Educational Technologies" width="300">
|
69
|
+
|
70
|
+
This project is maintained and funded by [Redu Educational Techologies](http://tech.redu.com.br).
|
71
|
+
|
72
|
+
# Copyright
|
73
|
+
|
74
|
+
Copyright (c) 2012 Redu Educational Technologies
|
75
|
+
|
76
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
77
|
+
|
78
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
79
|
+
|
80
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/TODO.mkd
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "destroy_soon/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "destroy_soon"
|
7
|
+
s.version = DestroySoon::VERSION
|
8
|
+
s.authors = ["Guilherme Cavalcanti"]
|
9
|
+
s.email = ["guiocavalcanti@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/redu/destroy-soon"
|
11
|
+
s.summary = "Delayed destroy ActiveRecord model using DelayedJob as queue system"
|
12
|
+
s.description = "Delayed destroy ActiveRecord model"
|
13
|
+
s.rubyforge_project = "destroy_soon"
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
+
s.test_files = Dir.glob('spec/**/*')
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_development_dependency "sqlite3"
|
23
|
+
|
24
|
+
s.add_runtime_dependency "activerecord", "~> 3.0.10"
|
25
|
+
s.add_runtime_dependency "activesupport", "~> 3.0.10"
|
26
|
+
s.add_runtime_dependency "delayed_job_active_record", "~> 0.3.2"
|
27
|
+
s.add_runtime_dependency "rake"
|
28
|
+
end
|
data/lib/destroy_soon.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module DestroySoon
|
2
|
+
class Job
|
3
|
+
attr_accessor :entity_klass, :entity_id
|
4
|
+
def initialize(opts)
|
5
|
+
@entity_id = opts[:entity].id
|
6
|
+
@entity_klass = opts[:entity].class.to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
entity.try(:destroy)
|
11
|
+
end
|
12
|
+
|
13
|
+
def entity
|
14
|
+
entity_klass.constantize.find_by_id(entity_id)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "delayed_job_active_record"
|
3
|
+
|
4
|
+
module DestroySoon::ModelAdditions
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
included do
|
7
|
+
scope :will_not_be_destroyed, where(:destroy_soon => false)
|
8
|
+
end
|
9
|
+
|
10
|
+
def async_destroy
|
11
|
+
unless self.destroy_soon
|
12
|
+
self.update_attribute(:destroy_soon, true)
|
13
|
+
|
14
|
+
DestroySoon::Queue.new.enqueue DestroySoon::Job.new(:entity => self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
module DestroySoon
|
4
|
+
class Queue
|
5
|
+
DEFAULT_QUEUE = nil
|
6
|
+
cattr_accessor :default_queue
|
7
|
+
|
8
|
+
def initializer
|
9
|
+
@default_queue = default_queue || DEFAULT_QUEUE
|
10
|
+
end
|
11
|
+
|
12
|
+
def enqueue(job)
|
13
|
+
args = [job]
|
14
|
+
args << { :queue => default_queue } if default_queue
|
15
|
+
|
16
|
+
Delayed::Job.enqueue(*args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module DestroySoon
|
4
|
+
describe ModelAdditions do
|
5
|
+
before(:all) do
|
6
|
+
class User < ActiveRecord::Base
|
7
|
+
include DestroySoon::ModelAdditions
|
8
|
+
end
|
9
|
+
end
|
10
|
+
after do
|
11
|
+
Delayed::Job.destroy_all
|
12
|
+
end
|
13
|
+
let(:subject) do
|
14
|
+
User.create(:name => "Call me susy")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have a marked_for_destruction attr" do
|
18
|
+
User.new.should respond_to :destroy_soon?
|
19
|
+
end
|
20
|
+
it "should respond to delayed destroy" do
|
21
|
+
subject.should respond_to :async_destroy
|
22
|
+
end
|
23
|
+
it "should set destroy_soon to true when trying to destroy" do
|
24
|
+
expect {
|
25
|
+
subject.async_destroy
|
26
|
+
}.to change(subject, :destroy_soon?)
|
27
|
+
end
|
28
|
+
it "should delay the destruction" do
|
29
|
+
Queue.any_instance.should_receive(:enqueue)
|
30
|
+
subject.async_destroy
|
31
|
+
end
|
32
|
+
it "should not be enqueued again if it's going to be destroyed" do
|
33
|
+
Queue.any_instance.should_receive(:enqueue).once
|
34
|
+
2.times { subject.async_destroy }
|
35
|
+
end
|
36
|
+
it "should scope by visibility" do
|
37
|
+
subject.async_destroy
|
38
|
+
User.will_not_be_destroyed.should_not include subject
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/queue_spec.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module DestroySoon
|
4
|
+
describe Queue do
|
5
|
+
after do
|
6
|
+
Delayed::Job.destroy_all
|
7
|
+
end
|
8
|
+
before(:all) do
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
include DestroySoon::ModelAdditions
|
11
|
+
end
|
12
|
+
end
|
13
|
+
let(:user) do
|
14
|
+
User.create(:name => "call me susy")
|
15
|
+
end
|
16
|
+
let(:job) do
|
17
|
+
Job.new(:entity => user)
|
18
|
+
end
|
19
|
+
let(:subject) do
|
20
|
+
Queue.new
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should enqueue the job" do
|
24
|
+
Delayed::Job.should_receive(:enqueue).with(job)
|
25
|
+
subject.enqueue(job)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should enqueue the job on the configured queue" do
|
29
|
+
Queue.default_queue = "general"
|
30
|
+
Delayed::Job.should_receive(:enqueue).with(job, :queue => "general")
|
31
|
+
subject.enqueue(job)
|
32
|
+
Queue.default_queue = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should call destroy after all" do
|
36
|
+
User.any_instance.should_receive(:destroy).once
|
37
|
+
subject.enqueue(job)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return the job" do
|
41
|
+
subject.enqueue(job).should be_a Delayed::Job
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
begin
|
3
|
+
drop_table :users
|
4
|
+
drop_table :delayed_jobs
|
5
|
+
rescue
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table "delayed_jobs" do |t|
|
9
|
+
t.integer "priority", :default => 0
|
10
|
+
t.integer "attempts", :default => 0
|
11
|
+
t.text "handler"
|
12
|
+
t.text "last_error"
|
13
|
+
t.datetime "run_at"
|
14
|
+
t.datetime "locked_at"
|
15
|
+
t.datetime "failed_at"
|
16
|
+
t.string "locked_by"
|
17
|
+
t.string "queue"
|
18
|
+
t.datetime "created_at"
|
19
|
+
t.datetime "updated_at"
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table "users" do |t|
|
23
|
+
t.string :name
|
24
|
+
t.boolean :destroy_soon, :default => false
|
25
|
+
end
|
26
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
|
8
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
9
|
+
|
10
|
+
require "rubygems"
|
11
|
+
require "bundler/setup"
|
12
|
+
require "destroy_soon"
|
13
|
+
|
14
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
15
|
+
load("schema.rb")
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
19
|
+
config.run_all_when_everything_filtered = true
|
20
|
+
config.filter_run :focus
|
21
|
+
Delayed::Worker.delay_jobs = false
|
22
|
+
|
23
|
+
# Run specs in random order to surface order dependencies. If you find an
|
24
|
+
# order dependency and want to debug it, you can fix the order by providing
|
25
|
+
# the seed, which is printed after each run.
|
26
|
+
# --seed 1234
|
27
|
+
config.order = 'random'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: destroy_soon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Guilherme Cavalcanti
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-10-22 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
none: false
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
hash: 3
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
prerelease: false
|
31
|
+
type: :development
|
32
|
+
name: rspec
|
33
|
+
requirement: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
hash: 3
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
prerelease: false
|
45
|
+
type: :development
|
46
|
+
name: sqlite3
|
47
|
+
requirement: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 19
|
55
|
+
segments:
|
56
|
+
- 3
|
57
|
+
- 0
|
58
|
+
- 10
|
59
|
+
version: 3.0.10
|
60
|
+
prerelease: false
|
61
|
+
type: :runtime
|
62
|
+
name: activerecord
|
63
|
+
requirement: *id003
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ~>
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 19
|
71
|
+
segments:
|
72
|
+
- 3
|
73
|
+
- 0
|
74
|
+
- 10
|
75
|
+
version: 3.0.10
|
76
|
+
prerelease: false
|
77
|
+
type: :runtime
|
78
|
+
name: activesupport
|
79
|
+
requirement: *id004
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
hash: 23
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
- 3
|
90
|
+
- 2
|
91
|
+
version: 0.3.2
|
92
|
+
prerelease: false
|
93
|
+
type: :runtime
|
94
|
+
name: delayed_job_active_record
|
95
|
+
requirement: *id005
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
hash: 3
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
106
|
+
prerelease: false
|
107
|
+
type: :runtime
|
108
|
+
name: rake
|
109
|
+
requirement: *id006
|
110
|
+
description: Delayed destroy ActiveRecord model
|
111
|
+
email:
|
112
|
+
- guiocavalcanti@gmail.com
|
113
|
+
executables: []
|
114
|
+
|
115
|
+
extensions: []
|
116
|
+
|
117
|
+
extra_rdoc_files: []
|
118
|
+
|
119
|
+
files:
|
120
|
+
- .gitignore
|
121
|
+
- .rspec
|
122
|
+
- .travis.yml
|
123
|
+
- CHANGELOG.mkd
|
124
|
+
- Gemfile
|
125
|
+
- README.mkd
|
126
|
+
- Rakefile
|
127
|
+
- TODO.mkd
|
128
|
+
- destroy_soon.gemspec
|
129
|
+
- lib/destroy_soon.rb
|
130
|
+
- lib/destroy_soon/job.rb
|
131
|
+
- lib/destroy_soon/model_additions.rb
|
132
|
+
- lib/destroy_soon/queue.rb
|
133
|
+
- lib/destroy_soon/version.rb
|
134
|
+
- spec/model_additions_spec.rb
|
135
|
+
- spec/queue_spec.rb
|
136
|
+
- spec/schema.rb
|
137
|
+
- spec/spec_helper.rb
|
138
|
+
homepage: http://github.com/redu/destroy-soon
|
139
|
+
licenses: []
|
140
|
+
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
none: false
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
hash: 3
|
152
|
+
segments:
|
153
|
+
- 0
|
154
|
+
version: "0"
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
none: false
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
hash: 3
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
version: "0"
|
164
|
+
requirements: []
|
165
|
+
|
166
|
+
rubyforge_project: destroy_soon
|
167
|
+
rubygems_version: 1.8.24
|
168
|
+
signing_key:
|
169
|
+
specification_version: 3
|
170
|
+
summary: Delayed destroy ActiveRecord model using DelayedJob as queue system
|
171
|
+
test_files:
|
172
|
+
- spec/model_additions_spec.rb
|
173
|
+
- spec/queue_spec.rb
|
174
|
+
- spec/schema.rb
|
175
|
+
- spec/spec_helper.rb
|