jobler 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b10be50515f5770dc9da54cedb9f700550c8a9bb
4
+ data.tar.gz: f274e293bd5c22da12da7ef1d2bcdc32de3c0ed7
5
+ SHA512:
6
+ metadata.gz: 43e45f56fdd80700e80e09a15e3c6b8ecf6857b3e5c2a4fde0037b0ef449fbd0ef2045680b86ebef9427204840e4041941d5c12b63c31d87d62b6d462d66974f
7
+ data.tar.gz: 56097d69f110d225413778b01c2d0b64c47c3b9505033de03acec159ea9099516c3ca152452cb591a8318dfa0f41f6ceb4836e467043a69cf53103459ce66c44
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 kaspernj
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,82 @@
1
+ # Jobler
2
+
3
+ Jobler helps you generate large reports or very large and slow pages through background jobs.
4
+
5
+ ## Install
6
+
7
+ Add it to your Gemfile and bundle it:
8
+
9
+ ```ruby
10
+ gem "jobler"
11
+ ```
12
+
13
+ Install the migrations and run them:
14
+ ```bash
15
+ rake railties:install:migrations
16
+ rake db:migrate
17
+ ```
18
+
19
+ Add it to your routes:
20
+
21
+ ```ruby
22
+ mount Jobler::Engine => "/jobler"
23
+ ```
24
+
25
+ Autoload the "Joblers" you are going to write through application.rb:
26
+
27
+ ```ruby
28
+ config.autoload_paths << Rails.root.join("app", "joblers")
29
+ ```
30
+
31
+ Add a ApplicationJobler to follow the ApplicationController and ApplicationRecord pattern:
32
+ ```ruby
33
+ class ApplicationJobler < Jobler::BaseJobler
34
+ end
35
+ ```
36
+
37
+ Jobler is going to queue its jobs through the ActiveJob queue called `:jobler`, so make sure a worker is listening to that queue.
38
+
39
+ ## Usage
40
+
41
+ Write a new Jobler located in "app/joblers":
42
+
43
+ ```ruby
44
+ class MyJobler < ApplicationJobler
45
+ # This method will be executed in the background
46
+ def execute!
47
+ my_temp_file = Tempfile.new
48
+
49
+ # Do some heavy lifting code wise...
50
+
51
+ create_result!(name: "my-file", temp_file: my_temp_file)
52
+ end
53
+
54
+ # This method will be called from the web when the execute is completed and successful
55
+ def result
56
+ Jobler::FileDownload.new(
57
+ file_name: "some-file.zip",
58
+ temp_file: temp_file_for_result(name: "my-file")
59
+ )
60
+ end
61
+ end
62
+ ```
63
+
64
+ You can do something like this in your controller:
65
+ ```ruby
66
+ class MyController < ApplicationController
67
+ def some_action
68
+ scheduler = Jobler::JobScheduler.create! jobler_type: "MyJobler", job_args: {
69
+ current_user_id: current_user.id,
70
+ query_parameters: request.query_parameters
71
+ }
72
+
73
+ redirect_to jobler.job_path(scheduler.job)
74
+ end
75
+ end
76
+ ```
77
+
78
+ This will show a wait page and them a complete page with a download link, once the job is completed.
79
+
80
+ ## License
81
+
82
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,27 @@
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
+ require "rdoc/task"
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = "rdoc"
11
+ rdoc.title = "Jobler"
12
+ rdoc.options << "--line-numbers"
13
+ rdoc.rdoc_files.include("README.rdoc")
14
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load "rails/tasks/engine.rake"
19
+
20
+ load "rails/tasks/statistics.rake"
21
+
22
+ if Rails.env.development? || Rails.env.test?
23
+ require "best_practice_project"
24
+ BestPracticeProject.load_tasks
25
+ end
26
+
27
+ Bundler::GemHelper.install_tasks
File without changes
@@ -0,0 +1,2 @@
1
+ //= require jquery
2
+ //= require jobler/jobs
@@ -0,0 +1,29 @@
1
+ function updateJobProgress() {
2
+ console.log("updateJobProgress()")
3
+ job = $(".job")
4
+
5
+ $.ajax({type: "GET", url: job.data("job-path"), complete: function(data) {
6
+ console.log(data.responseText)
7
+
8
+ result = $.parseJSON(data.responseText)
9
+
10
+ $(".job-progress").text((result.job.progress * 100).toFixed(2) + "%")
11
+
12
+ if (result.job.state == "completed") {
13
+ console.log("Job is completed - reload!")
14
+ location.reload()
15
+ }
16
+ }})
17
+
18
+ if (job.data("state") != "completed") {
19
+ setTimeout("updateJobProgress()", 5000)
20
+ }
21
+ }
22
+
23
+ $(document).ready(function() {
24
+ job = $(".job")
25
+
26
+ if (job.data("state") != "completed") {
27
+ updateJobProgress()
28
+ }
29
+ })
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any styles
10
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
11
+ * file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,3 @@
1
+ class Jobler::ApplicationController < ActionController::Base
2
+ protect_from_forgery with: :exception
3
+ end
@@ -0,0 +1,13 @@
1
+ class Jobler::DownloadsController < Jobler::ApplicationController
2
+ def show
3
+ @job = Jobler::Job.find(params[:id])
4
+ @result = @job.jobler.result
5
+
6
+ if @result.is_a?(Jobler::FileDownload)
7
+ send_file @result.temp_file.path, disposition: "attachment", filename: @result.file_name
8
+ else
9
+ flash[:error] = "The result wasn't a file download"
10
+ redirect_to :back
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ class Jobler::JobsController < Jobler::ApplicationController
2
+ def show
3
+ @job = Jobler::Job.find(params[:id])
4
+ @result = @job.jobler.result if @job.completed?
5
+
6
+ respond_to do |format|
7
+ format.json do
8
+ render json: {
9
+ job: {
10
+ progress: @job.progress,
11
+ state: @job.state
12
+ }
13
+ }
14
+ end
15
+ format.html
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,2 @@
1
+ module Jobler::ApplicationHelper
2
+ end
@@ -0,0 +1,16 @@
1
+ class Jobler::Job < ActiveRecord::Base
2
+ has_many :results, class_name: "Jobler::Result", dependent: :destroy
3
+
4
+ validates :jobler_type, :state, presence: true
5
+
6
+ def jobler
7
+ user_jobler = jobler_type.constantize.new
8
+ user_jobler.instance_variable_set(:@args, YAML.load(parameters)) # rubocop:disable Security/YAMLLoad
9
+ user_jobler.instance_variable_set(:@job, self)
10
+ user_jobler
11
+ end
12
+
13
+ def completed?
14
+ state == "completed"
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ class Jobler::Result < ActiveRecord::Base
2
+ belongs_to :job, class_name: "Jobler::Job"
3
+
4
+ validates :job, :name, presence: true
5
+ end
@@ -0,0 +1,17 @@
1
+ <div class="job" data-id="<%= @job.id %>" data-path="<%= job_path(@job) %>" data-state="<%= @job.state %>"></div>
2
+
3
+ <% if @job.completed? %>
4
+ <h1>Completed</h1>
5
+
6
+ <% if @result.is_a?(Jobler::FileDownload) %>
7
+ <%= link_to "Download", download_path(@job) %>
8
+ <% end %>
9
+ <% else %>
10
+ <h1>Waiting</h1>
11
+
12
+ <div class="job-progress">
13
+ <% if @job.progress? %>
14
+ <%= number_with_precision @job.progress, precision: 2 %>%
15
+ <% end %>
16
+ </div>
17
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Jobler</title>
5
+ <%= stylesheet_link_tag "jobler/application", media: "all" %>
6
+ <%= javascript_include_tag "jobler/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body class="controller-<%= controller_name %> action-<%= action_name %>">
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ Jobler::Engine.routes.draw do
2
+ resources :downloads, only: :show
3
+ resources :jobs, only: :show
4
+ end
@@ -0,0 +1,13 @@
1
+ class CreateJobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :jobler_jobs do |t|
4
+ t.string :jobler_type, null: false
5
+ t.string :state, default: "new", null: false
6
+ t.float :progress, default: 0.0, null: false
7
+ t.text :parameters
8
+ t.datetime :started_at
9
+ t.datetime :ended_at
10
+ t.timestamps null: false
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ class CreateJoblerResults < ActiveRecord::Migration
2
+ def change
3
+ create_table :jobler_results do |t|
4
+ t.belongs_to :job, index: true, null: false
5
+ t.string :name, index: true, null: false
6
+ t.binary :result, limit: 16.megabyte
7
+ t.timestamps null: false
8
+ end
9
+
10
+ add_foreign_key :jobler_results, :jobler_jobs, column: "job_id"
11
+ end
12
+ end
data/lib/jobler.rb ADDED
@@ -0,0 +1,11 @@
1
+ require "jobler/engine"
2
+
3
+ module Jobler
4
+ path = "#{File.dirname(__FILE__)}/jobler"
5
+
6
+ autoload :BaseJobler, "#{path}/base_jobler"
7
+ autoload :FileDownload, "#{path}/file_download"
8
+ autoload :JobScheduler, "#{path}/job_scheduler"
9
+ autoload :JobRunner, "#{path}/job_runner"
10
+ autoload :TemplateRenderer, "#{path}/template_renderer"
11
+ end
@@ -0,0 +1,56 @@
1
+ class Jobler::BaseJobler
2
+ attr_reader :args, :job
3
+
4
+ def create_result!(args)
5
+ temp_file = args.fetch(:temp_file)
6
+ temp_file.close unless temp_file.closed?
7
+
8
+ job.results.create!(
9
+ name: args.fetch(:name),
10
+ result: File.read(temp_file.path)
11
+ )
12
+ end
13
+
14
+ def execute!
15
+ raise NoMethodError, "You should define the 'execute!' method on #{self.class.name}"
16
+ end
17
+
18
+ def increment_progress!
19
+ @_progress_count ||= 0.0
20
+ @_progress_count += 1.0
21
+
22
+ new_progress = @_progress_count / @_progress_total
23
+
24
+ if @_current_progress.nil?
25
+ update = true
26
+ else
27
+ progress_difference = new_progress - @_current_progress
28
+ update = true if progress_difference > 0.01
29
+ end
30
+
31
+ if update
32
+ job.update_columns(progress: new_progress)
33
+ @_current_progress = new_progress
34
+ end
35
+ end
36
+
37
+ def progress_total(new_total)
38
+ @_progress_total = new_total.to_f
39
+ end
40
+
41
+ def result
42
+ raise NoMethodError, "You should define the 'result' method on #{self.class.name}"
43
+ end
44
+
45
+ def temp_file_for_result(args)
46
+ job_result = job.results.where(name: args.fetch(:name)).first
47
+
48
+ raise "No result by that name: #{args.fetch(:name)}" unless job_result
49
+
50
+ temp_file = Tempfile.new
51
+ temp_file.binmode
52
+ temp_file.write(job_result.result)
53
+ temp_file.close
54
+ temp_file
55
+ end
56
+ end
@@ -0,0 +1,3 @@
1
+ class Jobler::Engine < ::Rails::Engine
2
+ isolate_namespace Jobler
3
+ end
@@ -0,0 +1,8 @@
1
+ class Jobler::FileDownload
2
+ attr_reader :file_name, :temp_file
3
+
4
+ def initialize(args)
5
+ @file_name = args.fetch(:file_name)
6
+ @temp_file = args.fetch(:temp_file)
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ class Jobler::JobRunner < ActiveJob::Base
2
+ queue_as :jobler
3
+
4
+ def perform(job_id)
5
+ job = Jobler::Job.find(job_id)
6
+ job.update_attributes!(started_at: Time.zone.now, state: "started")
7
+
8
+ begin
9
+ job.jobler.execute!
10
+ job.update_attributes!(ended_at: Time.zone.now, progress: 1.0, state: "completed")
11
+ rescue Exception => e # rubocop:disable Lint/RescueException
12
+ job.update_attributes!(ended_at: Time.zone.now, state: "error")
13
+ raise e
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ class Jobler::JobScheduler
2
+ attr_reader :job
3
+
4
+ def self.create!(args)
5
+ scheduler = Jobler::JobScheduler.new(args)
6
+ scheduler.create_job
7
+ scheduler.perform_job_later
8
+ scheduler
9
+ end
10
+
11
+ def initialize(args)
12
+ @jobler_type = args.fetch(:jobler_type)
13
+ @job_args = args[:job_args]
14
+ end
15
+
16
+ def create_job
17
+ @job = Jobler::Job.create!(jobler_type: @jobler_type, parameters: YAML.dump(@job_args))
18
+ end
19
+
20
+ def perform_job_later
21
+ Jobler::JobRunner.perform_later(job.id)
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ class TemplateRenderer
2
+ include RenderAnywhere
3
+ end
@@ -0,0 +1,3 @@
1
+ module Jobler
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :jobler do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jobler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - kaspernj
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.2.7.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.2.7.1
27
+ description: Generate pages or files in the background
28
+ email:
29
+ - kaspernj@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - MIT-LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - app/assets/javascripts/jobler.js
38
+ - app/assets/javascripts/jobler/application.js
39
+ - app/assets/javascripts/jobler/jobs.js
40
+ - app/assets/stylesheets/jobler/application.css
41
+ - app/controllers/jobler/application_controller.rb
42
+ - app/controllers/jobler/downloads_controller.rb
43
+ - app/controllers/jobler/jobs_controller.rb
44
+ - app/helpers/jobler/application_helper.rb
45
+ - app/models/jobler/job.rb
46
+ - app/models/jobler/result.rb
47
+ - app/views/jobler/jobs/show.html.erb
48
+ - app/views/layouts/jobler/application.html.erb
49
+ - config/routes.rb
50
+ - db/migrate/20170130220604_create_jobs.rb
51
+ - db/migrate/20170130220734_create_jobler_results.rb
52
+ - lib/jobler.rb
53
+ - lib/jobler/base_jobler.rb
54
+ - lib/jobler/engine.rb
55
+ - lib/jobler/file_download.rb
56
+ - lib/jobler/job_runner.rb
57
+ - lib/jobler/job_scheduler.rb
58
+ - lib/jobler/template_renderer.rb
59
+ - lib/jobler/version.rb
60
+ - lib/tasks/jobler_tasks.rake
61
+ homepage: https://www.github.com/kaspernj/jobler
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.6.8
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Generate pages or files in the background
85
+ test_files: []