sidejobs 0.0.1

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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +80 -0
  4. data/Rakefile +18 -0
  5. data/lib/generators/sidejobs/install_generator.rb +24 -0
  6. data/lib/generators/sidejobs/templates/configuration.rb +7 -0
  7. data/lib/generators/sidejobs/templates/migration.rb +25 -0
  8. data/lib/sidejobs.rb +34 -0
  9. data/lib/sidejobs/configuration.rb +7 -0
  10. data/lib/sidejobs/daemon.rb +104 -0
  11. data/lib/sidejobs/extensions/active_job/queue_adapter.rb +47 -0
  12. data/lib/sidejobs/job.rb +21 -0
  13. data/lib/sidejobs/processor.rb +23 -0
  14. data/lib/sidejobs/queue.rb +24 -0
  15. data/lib/sidejobs/railtie.rb +15 -0
  16. data/lib/sidejobs/version.rb +3 -0
  17. data/lib/tasks/sidejobs.rake +11 -0
  18. data/test/daemon_test.rb +71 -0
  19. data/test/dummy/Rakefile +5 -0
  20. data/test/dummy/app/assets/javascripts/application.js +13 -0
  21. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  22. data/test/dummy/app/controllers/application_controller.rb +5 -0
  23. data/test/dummy/app/helpers/application_helper.rb +2 -0
  24. data/test/dummy/app/jobs/charge_subscriptions_job.rb +8 -0
  25. data/test/dummy/app/jobs/clear_guest_users_job.rb +8 -0
  26. data/test/dummy/app/jobs/send_newsletters_job.rb +8 -0
  27. data/test/dummy/app/jobs/share_products_job.rb +8 -0
  28. data/test/dummy/app/jobs/update_exchanges_job.rb +8 -0
  29. data/test/dummy/app/mailers/user_mailer.rb +7 -0
  30. data/test/dummy/app/views/layouts/application.html.erb +12 -0
  31. data/test/dummy/app/views/user_mailer/invite.text.erb +1 -0
  32. data/test/dummy/bin/bundle +3 -0
  33. data/test/dummy/bin/rails +4 -0
  34. data/test/dummy/bin/rake +4 -0
  35. data/test/dummy/bin/setup +29 -0
  36. data/test/dummy/config.ru +4 -0
  37. data/test/dummy/config/application.rb +25 -0
  38. data/test/dummy/config/boot.rb +5 -0
  39. data/test/dummy/config/database.yml +7 -0
  40. data/test/dummy/config/database.yml.travis +12 -0
  41. data/test/dummy/config/environment.rb +5 -0
  42. data/test/dummy/config/environments/development.rb +49 -0
  43. data/test/dummy/config/environments/production.rb +79 -0
  44. data/test/dummy/config/environments/test.rb +45 -0
  45. data/test/dummy/config/initializers/assets.rb +11 -0
  46. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  47. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  48. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  49. data/test/dummy/config/initializers/inflections.rb +16 -0
  50. data/test/dummy/config/initializers/mime_types.rb +4 -0
  51. data/test/dummy/config/initializers/session_store.rb +3 -0
  52. data/test/dummy/config/initializers/sidejobs.rb +7 -0
  53. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  54. data/test/dummy/config/locales/en.yml +23 -0
  55. data/test/dummy/config/routes.rb +56 -0
  56. data/test/dummy/config/secrets.yml +22 -0
  57. data/test/dummy/db/migrate/20161022211612_create_sidejobs.rb +25 -0
  58. data/test/dummy/db/schema.rb +37 -0
  59. data/test/dummy/log/development.log +228 -0
  60. data/test/dummy/log/sidejobs.log +1782 -0
  61. data/test/dummy/log/test.log +14856 -0
  62. data/test/dummy/public/404.html +67 -0
  63. data/test/dummy/public/422.html +67 -0
  64. data/test/dummy/public/500.html +66 -0
  65. data/test/dummy/public/favicon.ico +0 -0
  66. data/test/generator_test.rb +20 -0
  67. data/test/processor_test.rb +49 -0
  68. data/test/queue_test.rb +69 -0
  69. data/test/test_helper.rb +14 -0
  70. metadata +212 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 13c5ef1d9d233c888d758b942da10a980b46d75d
4
+ data.tar.gz: 7efac020aee70f251f7ce5d2682899d078e5a2bb
5
+ SHA512:
6
+ metadata.gz: d50fda67d9726aa4bf94aabba4defcb04d7ec19e45b23060ab16e3b345b0e615183b10430d8ec68af4c240cc6af34c7f5087cbf7a3bee0f9ecdc72d7609931e8
7
+ data.tar.gz: d7a52d18d78bcfe948ab762399bcab78592209042ca9b0749398db947ac82100e76fd8813fc856a196bc0ff84ae5407ec5333dc508d57f404baae17ae5b71a4b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 Mathías Montossi
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ [![Gem Version](https://badge.fury.io/rb/sidejobs.svg)](http://badge.fury.io/rb/sidejobs)
2
+ [![Code Climate](https://codeclimate.com/github/mmontossi/sidejobs/badges/gpa.svg)](https://codeclimate.com/github/mmontossi/sidejobs)
3
+ [![Build Status](https://travis-ci.org/mmontossi/sidejobs.svg)](https://travis-ci.org/mmontossi/sidejobs)
4
+ [![Dependency Status](https://gemnasium.com/mmontossi/sidejobs.svg)](https://gemnasium.com/mmontossi/sidejobs)
5
+
6
+ # Sidejobs
7
+
8
+ Versatile async database based jobs for rails.
9
+
10
+ ## Install
11
+
12
+ Put this line in your Gemfile:
13
+ ```ruby
14
+ gem 'sidejobs'
15
+ ```
16
+
17
+ Then bundle:
18
+ ```
19
+ $ bundle
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ Generate the sidejobs configuration and migration file:
25
+ ```
26
+ bundle exec rails g sidejobs:install
27
+ ```
28
+
29
+ The default configuration options are:
30
+ ```ruby
31
+ Sidejobs.configure do |config|
32
+ config.max_attempts = 3
33
+ config.sleep_delay = 15
34
+ config.batch_size = 20
35
+ end
36
+ ```
37
+
38
+ Run the migration to create the sidejobs table:
39
+ ```
40
+ bundle exec rake db:migrate
41
+ ```
42
+
43
+ Assign the sidejobs adapter to the environments:
44
+ ```ruby
45
+ Rails.application.configure do
46
+ config.active_job.queue_adapter = :sidejobs
47
+ end
48
+ ```
49
+
50
+ ## Usage
51
+
52
+ Start the daemon using the rake task:
53
+ ```
54
+ bundle exec rake sidejobs:start
55
+ ```
56
+
57
+ Now you can deliver mails using deliver_later:
58
+ ```ruby
59
+ UserMailer.invite('someone@mail.com').deliver_later
60
+ ```
61
+
62
+ Or perform jobs using perform_later:
63
+ ```ruby
64
+ SendNewsletterJob.perform_later
65
+ ```
66
+
67
+ Management is done programtically using Sidejobs::Job model:
68
+ ```ruby
69
+ Sidejobs::Job.failing.where('attempts > ?', 3).destroy_all
70
+ ```
71
+
72
+ NOTE: Is better to do it this way to have the freedom to integrate the code anyway you want.
73
+
74
+ ## Credits
75
+
76
+ This gem is maintained and funded by [mmontossi](https://github.com/mmontossi).
77
+
78
+ ## License
79
+
80
+ It is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ require 'rake/testtask'
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'lib'
13
+ t.libs << 'test'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = false
16
+ end
17
+
18
+ task default: :test
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+
3
+ module Sidejobs
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_configuration_file
11
+ copy_file 'configuration.rb', 'config/initializers/sidejobs.rb'
12
+ end
13
+
14
+ def create_migration_file
15
+ migration_template 'migration.rb', 'db/migrate/create_sidejobs.rb'
16
+ end
17
+
18
+ def self.next_migration_number(path)
19
+ Time.now.utc.strftime '%Y%m%d%H%M%S'
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ Sidejobs.configure do |config|
2
+
3
+ config.max_attempts = 3
4
+ config.sleep_delay = 15
5
+ config.batch_size = 20
6
+
7
+ end
@@ -0,0 +1,25 @@
1
+ class CreateSidejobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :sidejobs do |t|
4
+ if ActiveRecord::Base.connection_config[:adapter] == 'postgresql'
5
+ t.jsonb :data
6
+ else
7
+ t.string :data
8
+ end
9
+ t.string :queue
10
+ t.string :status, default: 'pending'
11
+ t.integer :priority, default: 0
12
+ t.integer :attempts, default: 0
13
+ t.text :error
14
+ t.datetime :failed_at
15
+ t.datetime :completed_at
16
+ t.datetime :processed_at
17
+ t.datetime :scheduled_at
18
+
19
+ t.timestamps null: false
20
+ end
21
+
22
+ add_index :sidejobs, %i(status scheduled_at attempts)
23
+ add_index :sidejobs, :priority
24
+ end
25
+ end
data/lib/sidejobs.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'sidejobs/extensions/active_job/queue_adapter'
2
+ require 'sidejobs/configuration'
3
+ require 'sidejobs/daemon'
4
+ require 'sidejobs/job'
5
+ require 'sidejobs/processor'
6
+ require 'sidejobs/queue'
7
+ require 'sidejobs/railtie'
8
+ require 'sidejobs/version'
9
+
10
+ module Sidejobs
11
+ class << self
12
+
13
+ def daemon
14
+ @daemon ||= Daemon.new
15
+ end
16
+
17
+ def queue
18
+ @queue ||= Queue.new
19
+ end
20
+
21
+ def logger
22
+ @logger ||= Logger.new(Rails.root.join('log/sidejobs.log'))
23
+ end
24
+
25
+ def configuration
26
+ @configuration ||= Configuration.new
27
+ end
28
+
29
+ def configure
30
+ yield configuration
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,7 @@
1
+ module Sidejobs
2
+ class Configuration
3
+
4
+ attr_accessor :max_attempts, :sleep_delay, :batch_size
5
+
6
+ end
7
+ end
@@ -0,0 +1,104 @@
1
+ module Sidejobs
2
+ class Daemon
3
+
4
+ def initialize
5
+ @stopping = false
6
+ end
7
+
8
+ def running?
9
+ if pid
10
+ begin
11
+ Process.kill 0, pid
12
+ true
13
+ rescue Errno::ESRCH
14
+ false
15
+ end
16
+ else
17
+ false
18
+ end
19
+ end
20
+
21
+ def start
22
+ unless running?
23
+ daemonize
24
+ write_pid
25
+ trap_signals
26
+ process
27
+ end
28
+ end
29
+
30
+ def stop
31
+ if running?
32
+ Process.kill :TERM, pid
33
+ delete_pid
34
+ end
35
+ end
36
+
37
+ def restart
38
+ if running?
39
+ stop
40
+ start
41
+ else
42
+ start
43
+ end
44
+ end
45
+
46
+ def pid
47
+ File.read(pid_path).to_i rescue nil
48
+ end
49
+
50
+ def stopping?
51
+ @stopping == true
52
+ end
53
+
54
+ private
55
+
56
+ def daemonize
57
+ exit if fork
58
+ Process.setsid
59
+ exit if fork
60
+ Dir.chdir '/'
61
+ File.umask 0000
62
+ $stdout.reopen log_path, 'a'
63
+ $stderr.reopen $stdout
64
+ $stdout.sync = true
65
+ end
66
+
67
+ def trap_signals
68
+ trap :TERM do
69
+ @stopping = true
70
+ end
71
+ end
72
+
73
+ def delete_pid
74
+ File.unlink pid_path
75
+ end
76
+
77
+ def write_pid
78
+ FileUtils.mkdir_p pid_path.dirname
79
+ File.write pid_path, Process.pid
80
+ end
81
+
82
+ def log_path
83
+ Rails.root.join 'log/sidejobs.log'
84
+ end
85
+
86
+ def pid_path
87
+ Rails.root.join 'tmp/pids/sidejobs.pid'
88
+ end
89
+
90
+ def processor
91
+ @processor ||= Processor.new
92
+ end
93
+
94
+ def process
95
+ Sidejobs.logger.info 'Starting'
96
+ until stopping? do
97
+ processor.process
98
+ sleep Sidejobs.configuration.sleep_delay
99
+ end
100
+ Sidejobs.logger.info 'Stopping'
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,47 @@
1
+ module Sidejobs
2
+ module Extensions
3
+ module ActiveJob
4
+ module QueueAdapters
5
+ class SidejobsAdapter
6
+ class << self
7
+
8
+ def enqueue(job)
9
+ Sidejobs.queue.add(
10
+ job.serialize,
11
+ queue: job.queue_name,
12
+ priority: calculate_priority(job.queue_name)
13
+ )
14
+ end
15
+
16
+ def enqueue_at(job, timestamp)
17
+ Sidejobs.queue.add(
18
+ job.serialize,
19
+ queue: job.queue_name,
20
+ priority: calculate_priority(job.queue_name),
21
+ schedule_at: Time.at(timestamp)
22
+ )
23
+ end
24
+
25
+ private
26
+
27
+ def calculate_priority(queue)
28
+ case queue
29
+ when 'mailers'
30
+ 80
31
+ when 'high_priority'
32
+ 60
33
+ when 'default'
34
+ 40
35
+ when 'low_priority'
36
+ 20
37
+ else
38
+ 0
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,21 @@
1
+ module Sidejobs
2
+ class Job < ActiveRecord::Base
3
+ self.table_name = 'sidejobs'
4
+ STATUS = %w(pending processing failing complete)
5
+
6
+ STATUS.each do |name|
7
+ scope name, -> { where(status: name) }
8
+ define_method "#{name}?" do
9
+ status == name
10
+ end
11
+ end
12
+
13
+ validates_presence_of :queue, :data, :status
14
+ validates_presence_of :error, :failed_at, if: :failing?
15
+ validates_presence_of :completed_at, if: :complete?
16
+ validates_presence_of :processed_at, if: :processing?
17
+ validates_inclusion_of :status, within: STATUS
18
+ validates_numericality_of :priority, :attempts, only_integer: true
19
+
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module Sidejobs
2
+ class Processor
3
+
4
+ def process
5
+ original_logger = ActiveJob::Base.logger
6
+ ActiveJob::Base.logger = Sidejobs.logger
7
+ Sidejobs.queue.fetch.each do |job|
8
+ Sidejobs.logger.info "Processing ##{job.id} attempt #{job.attempts} of #{job.data['job_class']}"
9
+ job.update status: 'processing', processed_at: Time.now, attempts: job.attempts+1
10
+ begin
11
+ ActiveJob::Base.execute job.data
12
+ job.update status: 'complete', completed_at: Time.now
13
+ Sidejobs.logger.info 'Done'
14
+ rescue => exception
15
+ job.update status: 'failing', failed_at: Time.now, error: exception.message
16
+ Sidejobs.logger.info "Error: #{exception.message}"
17
+ end
18
+ end
19
+ ActiveJob::Base.logger = original_logger
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Sidejobs
2
+ class Queue
3
+
4
+ def add(data, options={})
5
+ Job.create(
6
+ data: data.to_json,
7
+ queue: (options[:queue] || 'default'),
8
+ priority: (options[:priority] || 0),
9
+ scheduled_at: options[:schedule_at]
10
+ )
11
+ end
12
+
13
+ def fetch
14
+ Job.where(status: %w(pending failing)).where(
15
+ 'attempts < ?',
16
+ Sidejobs.configuration.max_attempts
17
+ ).where(
18
+ 'scheduled_at <= ? OR scheduled_at IS NULL',
19
+ Time.now
20
+ ).order(priority: :desc).limit(Sidejobs.configuration.batch_size)
21
+ end
22
+
23
+ end
24
+ end