├── .rspec
├── spec
├── dummy
│ ├── log
│ │ └── .gitkeep
│ ├── app
│ │ ├── mailers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ ├── .gitkeep
│ │ │ ├── delayed_jobs
│ │ │ │ ├── dummy_job.rb
│ │ │ │ └── other_dummy_job.rb
│ │ │ └── scheduled_jobs
│ │ │ │ ├── logging_job.rb
│ │ │ │ ├── dummy_job.rb
│ │ │ │ ├── dummy_scheduled_job.rb
│ │ │ │ └── dummy_heroku_job.rb
│ │ ├── views
│ │ │ ├── home
│ │ │ │ └── index.html.haml
│ │ │ └── layouts
│ │ │ │ └── application.html.erb
│ │ ├── helpers
│ │ │ └── application_helper.rb
│ │ ├── controllers
│ │ │ ├── home_controller.rb
│ │ │ └── application_controller.rb
│ │ └── assets
│ │ │ ├── stylesheets
│ │ │ └── application.css
│ │ │ └── javascripts
│ │ │ └── application.js
│ ├── lib
│ │ └── assets
│ │ │ └── .gitkeep
│ ├── public
│ │ ├── favicon.ico
│ │ ├── 500.html
│ │ ├── 422.html
│ │ └── 404.html
│ ├── db
│ │ ├── test.sqlite3
│ │ └── development.sqlite3
│ ├── config
│ │ ├── initializers
│ │ │ ├── jobbr.rb
│ │ │ ├── mime_types.rb
│ │ │ ├── backtrace_silencers.rb
│ │ │ ├── session_store.rb
│ │ │ ├── secret_token.rb
│ │ │ ├── wrap_parameters.rb
│ │ │ └── inflections.rb
│ │ ├── routes.rb
│ │ ├── environment.rb
│ │ ├── locales
│ │ │ └── en.yml
│ │ ├── boot.rb
│ │ ├── schedule.rb
│ │ ├── database.yml
│ │ ├── environments
│ │ │ ├── development.rb
│ │ │ ├── test.rb
│ │ │ └── production.rb
│ │ ├── application.rb
│ │ └── mongoid.yml
│ ├── config.ru
│ ├── Rakefile
│ ├── bin
│ │ └── rails
│ └── README.rdoc
├── support
│ └── generator_destination_root.rb
├── generators
│ ├── scheduled_job_config_generator_spec.rb
│ ├── delayed_job_generator_spec.rb
│ ├── scheduled_job_generator_spec.rb
│ └── initializer_generator_spec.rb
├── tasks
│ ├── jobbr_heroku_tasks_spec.rb
│ └── jobbr_tasks_spec.rb
├── controllers
│ └── delayed_jobs_controller_spec.rb
├── models
│ ├── delayed_spec.rb
│ ├── job_spec.rb
│ └── scheduled_spec.rb
├── spec_helper.rb
└── features
│ └── job_list_feature_spec.rb
├── app
├── assets
│ ├── images
│ │ └── jobbr
│ │ │ └── .gitkeep
│ ├── javascripts
│ │ └── jobbr
│ │ │ └── application.js.coffee
│ └── stylesheets
│ │ └── jobbr
│ │ └── application.css.scss
├── controllers
│ └── jobbr
│ │ ├── runs_controller.rb
│ │ ├── application_controller.rb
│ │ ├── jobs_controller.rb
│ │ └── delayed_jobs_controller.rb
├── views
│ ├── jobbr
│ │ ├── runs
│ │ │ ├── _logs.html.haml
│ │ │ └── show.html.haml
│ │ └── jobs
│ │ │ ├── index.html.haml
│ │ │ ├── _job_list.html.haml
│ │ │ └── show.html.haml
│ └── layouts
│ │ └── jobbr
│ │ └── application.html.haml
├── models
│ └── jobbr
│ │ ├── log_message.rb
│ │ ├── delayed.rb
│ │ ├── scheduled.rb
│ │ ├── run.rb
│ │ └── job.rb
└── helpers
│ └── jobbr
│ └── application_helper.rb
├── lib
├── jobbr.rb
├── jobbr
│ ├── version.rb
│ ├── engine.rb
│ ├── whenever.rb
│ ├── ohm_pagination.rb
│ ├── logger.rb
│ └── ohm.rb
├── generators
│ └── jobbr
│ │ ├── initializer
│ │ ├── templates
│ │ │ └── jobbr.rb
│ │ ├── initializer_generator.rb
│ │ └── USAGE
│ │ ├── delayed_job
│ │ ├── templates
│ │ │ └── delayed_job.erb
│ │ ├── USAGE
│ │ └── delayed_job_generator.rb
│ │ ├── scheduled_job_config
│ │ ├── USAGE
│ │ ├── scheduled_job_config_generator.rb
│ │ └── templates
│ │ │ └── schedule.rb
│ │ └── scheduled_job
│ │ ├── templates
│ │ └── scheduled_job.erb
│ │ ├── USAGE
│ │ └── scheduled_job_generator.rb
└── tasks
│ ├── jobbr_tasks.rake
│ └── jobbr_heroku_tasks.rake
├── .travis.yml
├── config
├── routes.rb
└── locales
│ └── jobbr.en.yml
├── .gitignore
├── script
└── rails
├── bin
└── jobbr
├── Gemfile
├── Rakefile
├── MIT-LICENSE
├── jobbr.gemspec
├── README.rdoc
└── Gemfile.lock
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/spec/dummy/log/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/jobbr/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/mailers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/lib/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/home/index.html.haml:
--------------------------------------------------------------------------------
1 | = delayed_job_polling_path
--------------------------------------------------------------------------------
/lib/jobbr.rb:
--------------------------------------------------------------------------------
1 | require "jobbr/engine"
2 |
3 | module Jobbr
4 | end
5 |
--------------------------------------------------------------------------------
/lib/jobbr/version.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 | VERSION = '2.0.0'
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 2.1.1
4 | services:
5 | - redis-server
6 |
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/home_controller.rb:
--------------------------------------------------------------------------------
1 | class HomeController < ApplicationController
2 |
3 | end
--------------------------------------------------------------------------------
/spec/dummy/db/test.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/jobbr/master/spec/dummy/db/test.sqlite3
--------------------------------------------------------------------------------
/spec/dummy/db/development.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/did/jobbr/master/spec/dummy/db/development.sqlite3
--------------------------------------------------------------------------------
/spec/dummy/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery
3 | end
4 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/jobbr.rb:
--------------------------------------------------------------------------------
1 | require 'ohm'
2 | require 'ohm/contrib'
3 | require 'jobbr/logger'
4 | require 'jobbr/ohm'
5 | require 'jobbr/ohm_pagination'
6 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/initializer/templates/jobbr.rb:
--------------------------------------------------------------------------------
1 | require 'ohm'
2 | require 'ohm/contrib'
3 | require 'jobbr/logger'
4 | require 'jobbr/ohm'
5 | require 'jobbr/ohm_pagination'
6 |
7 |
--------------------------------------------------------------------------------
/spec/dummy/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run Dummy::Application
5 |
--------------------------------------------------------------------------------
/spec/dummy/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 |
3 | resource :home
4 |
5 | root to: 'home#index'
6 |
7 | mount Jobbr::Engine => "/jobbr"
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | Dummy::Application.initialize!
6 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Jobbr::Engine.routes.draw do
2 |
3 | root to: 'jobs#index'
4 |
5 | resources :jobs do
6 | resources :runs
7 | end
8 |
9 | resources :delayed_jobs
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .rbenv-version
3 | .bundle/
4 | log/*.log
5 | pkg/
6 | spec/dummy/db/*.sqlite3
7 | spec/dummy/log/*.log
8 | spec/dummy/tmp/
9 | spec/dummy/.sass-cache
10 | *.gem
11 | coverage
12 | .idea
13 |
--------------------------------------------------------------------------------
/spec/dummy/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Sample localization file for English. Add more files in this directory for other locales.
2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3 |
4 | en:
5 | hello: "Hello world"
6 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/support/generator_destination_root.rb:
--------------------------------------------------------------------------------
1 | # mixin setting destination root needed by generator_spec
2 | module GeneratorDestinationRoot
3 |
4 | def initialize(*args)
5 | super(*args)
6 | self.destination_root = SPEC_TMP_ROOT
7 | end
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/app/controllers/jobbr/runs_controller.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class RunsController < Jobbr::ApplicationController
4 |
5 | def show
6 | @job = Job.by_name(params[:job_id])
7 | @run = @job.runs[params[:id]]
8 | end
9 |
10 | end
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/delayed_jobs/dummy_job.rb:
--------------------------------------------------------------------------------
1 | module DelayedJobs
2 |
3 | class DummyJob < Jobbr::Job
4 |
5 | include Jobbr::Delayed
6 |
7 | def perform(run, params)
8 | run.logger.debug 'job is running'
9 | end
10 |
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/spec/generators/scheduled_job_config_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jobbr::ScheduledJobConfigGenerator, type: :generator do
4 |
5 | it 'creates job model' do
6 | run_generator
7 | assert_file 'config/schedule.rb'
8 | end
9 |
10 | end
11 |
--------------------------------------------------------------------------------
/app/controllers/jobbr/application_controller.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 | class ApplicationController < ActionController::Base
3 |
4 | before_filter :set_locale
5 |
6 | protected
7 |
8 | def set_locale
9 | I18n.locale = :en
10 | end
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/delayed_job/templates/delayed_job.erb:
--------------------------------------------------------------------------------
1 | module DelayedJobs
2 |
3 | class <%=name.camelize%>Job < Jobbr::Job
4 |
5 | include Jobbr::Delayed
6 |
7 | def perform(run, params)
8 | # put your job code here
9 | end
10 |
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/initializer/initializer_generator.rb:
--------------------------------------------------------------------------------
1 | class Jobbr::InitializerGenerator < Rails::Generators::Base
2 | source_root File.expand_path('../templates', __FILE__)
3 |
4 | def copy_config_file
5 | copy_file "jobbr.rb", "config/initializers/jobbr.rb"
6 | end
7 |
8 | end
9 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/scheduled_job_config/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generate Whenever schedule.rb config file
3 |
4 | Example:
5 | rails generate scheduled_job_config
6 |
7 | This will create:
8 | Config: config/schedule.rb
9 | Config: config/initializers/jobbr.rb
10 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/scheduled_jobs/logging_job.rb:
--------------------------------------------------------------------------------
1 | module ScheduledJobs
2 |
3 | class LoggingJob < Jobbr::Job
4 |
5 | include Jobbr::Scheduled
6 |
7 | def perform(run)
8 | run.logger.debug 'foo'
9 | run.logger.error 'bar'
10 | end
11 |
12 | end
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__)
3 |
4 | if File.exist?(gemfile)
5 | ENV['BUNDLE_GEMFILE'] = gemfile
6 | require 'bundler'
7 | Bundler.setup
8 | end
9 |
10 | $:.unshift File.expand_path('../../../../lib', __FILE__)
--------------------------------------------------------------------------------
/app/views/jobbr/runs/_logs.html.haml:
--------------------------------------------------------------------------------
1 | %h5= title
2 | .well.logs{class: size}
3 | - run.ordered_messages.each do |msg|
4 | %span.date{class: msg.kind}= "[#{msg.created_at.localtime.strftime('%H:%M:%S')}]"
5 | %span.kind{class: msg.kind}= "[#{msg.kind}]"
6 | %span.message= raw(msg.message)
7 | %br
8 |
--------------------------------------------------------------------------------
/spec/dummy/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | # Add your own tasks in files placed in lib/tasks ending in .rake,
3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
4 |
5 | require File.expand_path('../config/application', __FILE__)
6 |
7 | Dummy::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/spec/generators/delayed_job_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jobbr::DelayedJobGenerator, type: :generator do
4 |
5 | arguments %w(foo)
6 |
7 | it 'creates job model' do
8 | run_generator
9 | assert_file 'app/models/delayed_jobs/foo_job.rb'
10 | end
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/app/models/jobbr/log_message.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class LogMessage < ::Ohm::Model
4 |
5 | include ::Ohm::Timestamps
6 | include ::Ohm::DataTypes
7 |
8 | attribute :kind, Type::Symbol
9 | attribute :message
10 |
11 | reference :run, 'Jobbr::Run'
12 |
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/delayed_jobs/other_dummy_job.rb:
--------------------------------------------------------------------------------
1 | module DelayedJobs
2 |
3 | class OtherDummyJob < Jobbr::Job
4 |
5 | include Jobbr::Delayed
6 |
7 | queue :critical
8 |
9 | def perform(run, params)
10 | run.logger.debug 'job is running'
11 | end
12 |
13 | end
14 |
15 | end
16 |
--------------------------------------------------------------------------------
/spec/generators/scheduled_job_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jobbr::ScheduledJobGenerator, type: :generator do
4 |
5 | arguments %w(foo)
6 |
7 | it 'creates job model' do
8 | run_generator
9 | assert_file 'app/models/scheduled_jobs/foo_job.rb'
10 | end
11 |
12 | end
13 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/delayed_job/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates a new delayed job in DelayedJobs namespace.
3 |
4 | Example:
5 | rails generate delayed_job MySample
6 |
7 | This will create:
8 | Model: app/models/delayed_jobs/my_sample_job.rb
9 | Config: config/initializers/jobbr.rb
10 |
--------------------------------------------------------------------------------
/spec/generators/initializer_generator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Jobbr::InitializerGenerator, type: :generator do
4 |
5 | before do
6 | run_generator
7 | end
8 |
9 | it 'creates jobbr initializer file' do
10 | assert_file 'config/initializers/jobbr.rb'
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/initializer/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates a jobbr initializer to require various dependencies.
3 | Will also create a Whenever configuration file.
4 |
5 | Example:
6 | rails generate scheduled_job MySample
7 |
8 | This will create:
9 | Config: config/initializers/jobbr.rb
10 |
--------------------------------------------------------------------------------
/lib/tasks/jobbr_tasks.rake:
--------------------------------------------------------------------------------
1 | namespace :jobbr do
2 |
3 | desc 'Mark all running job as failed.'
4 | task :sweep_running_jobs => :environment do
5 |
6 | Jobbr::Run.find(status: :running).union(status: :waiting).each do |run|
7 | run.status = :failed
8 | run.save
9 | end
10 |
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/spec/dummy/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/spec/dummy/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Dummy
5 | <%= stylesheet_link_tag "application", :media => "all" %>
6 | <%= javascript_include_tag "application" %>
7 | <%= csrf_meta_tags %>
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | ENGINE_ROOT = File.expand_path('../..', __FILE__)
5 | ENGINE_PATH = File.expand_path('../../lib/jobbr/engine', __FILE__)
6 |
7 | require 'rails/all'
8 | require 'rails/engine/commands'
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/scheduled_jobs/dummy_job.rb:
--------------------------------------------------------------------------------
1 | module ScheduledJobs
2 |
3 | class DummyJob < Jobbr::Job
4 |
5 | include Jobbr::Scheduled
6 |
7 | # description 'Describe your job here'
8 |
9 | # every 1.day, at: '5am'
10 |
11 | def perform
12 | # put your job code here
13 | end
14 |
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/lib/jobbr/engine.rb:
--------------------------------------------------------------------------------
1 | require 'jobbr/logger'
2 |
3 | module Jobbr
4 | class Engine < ::Rails::Engine
5 |
6 | isolate_namespace Jobbr
7 |
8 | initializer 'jobbr.action_controller' do |app|
9 | ActiveSupport.on_load :action_controller do
10 | helper Jobbr::ApplicationHelper
11 | end
12 | end
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/views/jobbr/jobs/index.html.haml:
--------------------------------------------------------------------------------
1 | %ol.breadcrumb
2 | %li.active= t('.title')
3 |
4 | - unless @scheduled_jobs.empty?
5 | = render 'job_list', title: t('.scheduled_jobs'), jobs: @scheduled_jobs, css_class: 'scheduled-jobs'
6 |
7 | - unless @delayed_jobs.empty?
8 | = render 'job_list', title: t('.delayed_jobs'), jobs: @delayed_jobs, css_class: 'delayed-jobs'
9 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/scheduled_jobs/dummy_scheduled_job.rb:
--------------------------------------------------------------------------------
1 | module ScheduledJobs
2 |
3 | class DummyScheduledJob < Jobbr::Job
4 |
5 | include Jobbr::Scheduled
6 |
7 | description 'A dummy Job'
8 |
9 | every 1.day, at: '5.30 am'
10 |
11 | def perform(run)
12 | run.logger.debug 'Dummy!!!!'
13 | end
14 |
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/scheduled_job/templates/scheduled_job.erb:
--------------------------------------------------------------------------------
1 | module ScheduledJobs
2 |
3 | class <%=name.camelize%>Job < Jobbr::Job
4 |
5 | include Jobbr::Scheduled
6 |
7 | # description 'Describe your job here'
8 |
9 | # every 1.day, at: '5am'
10 |
11 | def perform
12 | # put your job code here
13 | end
14 |
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/scheduled_job_config/scheduled_job_config_generator.rb:
--------------------------------------------------------------------------------
1 | class Jobbr::ScheduledJobConfigGenerator < Rails::Generators::Base
2 | source_root File.expand_path('../templates', __FILE__)
3 |
4 | def copy_config_file
5 | copy_file "schedule.rb", "config/schedule.rb"
6 | generate 'jobbr:initializer' unless Rails.env.test?
7 | end
8 |
9 | end
10 |
--------------------------------------------------------------------------------
/spec/dummy/app/models/scheduled_jobs/dummy_heroku_job.rb:
--------------------------------------------------------------------------------
1 | module ScheduledJobs
2 |
3 | class DummyHerokuJob < Jobbr::Job
4 |
5 | include Jobbr::Scheduled
6 |
7 | description 'Describe your job here'
8 |
9 | heroku_run :daily, priority: 0
10 |
11 | def perform(run)
12 | run.logger.debug 'heroku :)'
13 | end
14 |
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/schedule.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] = ARGV.first || ENV['RAILS_ENV'] || 'development'
2 | require File.expand_path(File.dirname(__FILE__) + '/../config/environment')
3 | require 'jobbr/whenever'
4 |
5 | set :output, "#{path}/log/cron.log"
6 | job_type :jobbr, 'cd :path && RAILS_ENV=:environment bundle exec jobbr :task :output'
7 |
8 | Jobbr::Whenever.schedule_jobs(self)
9 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/scheduled_job_config/templates/schedule.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] = ARGV.first || ENV['RAILS_ENV'] || 'development'
2 | require File.expand_path(File.dirname(__FILE__) + '/../config/environment')
3 | require 'jobbr/whenever'
4 |
5 | set :output, "#{path}/log/cron.log"
6 | job_type :jobbr, 'cd :path && RAILS_ENV=:environment bundle exec jobbr :task :output'
7 |
8 | Jobbr::Whenever.schedule_jobs(self)
9 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/scheduled_job/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Generates a new scheduled job in ScheduledJobs namespace.
3 | Will also create a Whenever configuration file.
4 |
5 | Example:
6 | rails generate scheduled_job MySample
7 |
8 | This will create:
9 | Model: app/models/scheduled_jobs/my_sample_job.rb
10 | Config: config/schedule.rb
11 | Config: config/initializers/jobbr.rb
12 |
--------------------------------------------------------------------------------
/app/models/jobbr/delayed.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/concern'
2 |
3 | module Jobbr
4 |
5 | module Delayed
6 |
7 | extend ActiveSupport::Concern
8 |
9 | included do
10 |
11 | include Sidekiq::Extensions::ActiveRecord
12 |
13 | end
14 |
15 | module ClassMethods
16 |
17 | def queue(queue = nil)
18 | @queue = queue.to_sym if queue
19 | @queue
20 | end
21 |
22 | end
23 |
24 | end
25 |
26 | end
27 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/delayed_job/delayed_job_generator.rb:
--------------------------------------------------------------------------------
1 | class Jobbr::DelayedJobGenerator < Rails::Generators::NamedBase
2 |
3 | source_root File.expand_path('../templates', __FILE__)
4 |
5 | def create_delayed_job
6 | empty_directory "app/models/delayed_jobs"
7 | template "delayed_job.erb", "app/models/delayed_jobs/#{file_name}_job.rb", name: file_name
8 | generate 'jobbr:initializer' unless Rails.env.test?
9 | end
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/spec/tasks/jobbr_heroku_tasks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'rake'
3 |
4 | describe 'jobbr_heroku_tasks' do
5 |
6 | before do
7 | Rake.application.rake_require 'tasks/jobbr_heroku_tasks'
8 | Rake::Task.define_task(:environment)
9 | end
10 |
11 | it 'runs daily jobs' do
12 | expect {
13 | Rake.application.invoke_task 'jobbr:heroku:daily'
14 | }.to change { Jobbr::Run.all.count }.from(0).to(1)
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # Dummy::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/lib/generators/jobbr/scheduled_job/scheduled_job_generator.rb:
--------------------------------------------------------------------------------
1 | class Jobbr::ScheduledJobGenerator < Rails::Generators::NamedBase
2 |
3 | source_root File.expand_path('../templates', __FILE__)
4 |
5 | def create_scheduled_job
6 | empty_directory "app/models/scheduled_jobs"
7 | template "scheduled_job.erb", "app/models/scheduled_jobs/#{file_name}_job.rb", name: file_name
8 | generate 'jobbr:scheduled_job_config' unless Rails.env.test?
9 | end
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/app/views/jobbr/runs/show.html.haml:
--------------------------------------------------------------------------------
1 | %ol.breadcrumb
2 | %li
3 | = link_to t('jobbr.jobs.index.title'), jobs_path
4 | %li
5 | = link_to @job.name.humanize, job_path(@job)
6 | %li.active
7 | - if @run.started_at
8 | = l @run.started_at
9 |
10 | = status_icon(@run.status)
11 |
12 | - if @run.run_time
13 | .well
14 | = t('.run_time', run_time: ChronicDuration.output(@run.run_time.round(2), format: :long))
15 |
16 | = render 'logs', run: @run, title: t('.logs'), size: 'large'
17 |
--------------------------------------------------------------------------------
/lib/jobbr/whenever.rb:
--------------------------------------------------------------------------------
1 | require 'jobbr/ohm'
2 |
3 | module Jobbr
4 |
5 | module Whenever
6 |
7 | extend self
8 |
9 | # Generates crontab for each scheduled Job using Whenever DSL.
10 | def schedule_jobs(job_list)
11 | Jobbr::Ohm.models(Jobbr::Scheduled).each do |job|
12 | if job.every
13 | job_list.every job.every[0], job.every[1] do
14 | job_list.jobbr job.task_name
15 | end
16 | end
17 | end
18 | end
19 |
20 | end
21 |
22 | end
23 |
--------------------------------------------------------------------------------
/app/controllers/jobbr/jobs_controller.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class JobsController < Jobbr::ApplicationController
4 |
5 | def index
6 | @scheduled_jobs = Jobbr::Job.scheduled
7 | @delayed_jobs = Jobbr::Job.delayed
8 | end
9 |
10 | def show
11 | if @job = Job.by_name(params[:id])
12 | @runs = Jobbr::OhmPagination.new(@job.runs).sort_by(:started_at).order('ALPHA DESC').per(10).page(params[:page])
13 | @last_run = @job.last_run
14 | end
15 | end
16 |
17 | end
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/app/controllers/jobbr/delayed_jobs_controller.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class DelayedJobsController < Jobbr::ApplicationController
4 |
5 | def create
6 | params.merge!(current_user_id: current_user.id.to_s) rescue nil
7 | @run = Job.run_by_name(params[:job_name], params)
8 | render json: { id: @run.id.to_s }
9 | end
10 |
11 | def show
12 | job_run = Run[params[:id]]
13 | render json: { status: job_run.status, result: job_run.result, progress: job_run.progress }
14 | end
15 |
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | Dummy::Application.config.secret_token = '282ec86f2f30ae3fce5193f0eafbd7bdd78f83d05b42fe6f2305df1d7b6565407a439d9bb415edbde10e213aa39b8c715ee0c278165302ddbd89aa8b6b7fbc79'
8 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/wrap_parameters.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 | #
3 | # This file contains settings for ActionController::ParamsWrapper which
4 | # is enabled by default.
5 |
6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
7 | ActiveSupport.on_load(:action_controller) do
8 | wrap_parameters format: [:json]
9 | end
10 |
11 | # Disable root element in JSON by default.
12 | ActiveSupport.on_load(:active_record) do
13 | self.include_root_in_json = false
14 | end
15 |
--------------------------------------------------------------------------------
/spec/dummy/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 | #
12 | # These inflection rules are supported but not enabled by default:
13 | # ActiveSupport::Inflector.inflections do |inflect|
14 | # inflect.acronym 'RESTful'
15 | # end
16 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
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 vendor/assets/stylesheets of plugins, if any, 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 top of the
9 | * compiled file, but it's generally better to create a new file per style scope.
10 | *
11 | *= require_self
12 | *= require_tree .
13 | */
14 |
--------------------------------------------------------------------------------
/bin/jobbr:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require 'optparse'
4 |
5 | opt_parser = OptionParser.new do |opts|
6 | opts.banner = 'Usage: jobbr [job_name]'
7 |
8 | opts.on('-v', '--version') do
9 | puts "Jobbr v#{Jobbr::VERSION}"
10 | exit
11 | end
12 |
13 | opts.on('-h', '--help', 'Show this message') do
14 | puts opts
15 | exit
16 | end
17 |
18 | end
19 |
20 | opt_parser.parse!
21 |
22 | if ARGV.length == 1
23 | require File.expand_path('config/environment')
24 | unless ARGV[0].start_with?('scheduled_jobs')
25 | ARGV[0] = "scheduled_jobs/#{ARGV[0]}"
26 | end
27 | Jobbr::Job.run_by_name(ARGV[0])
28 | else
29 | puts opt_parser
30 | end
31 |
32 |
--------------------------------------------------------------------------------
/spec/dummy/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | development:
7 | adapter: sqlite3
8 | database: db/development.sqlite3
9 | pool: 5
10 | timeout: 5000
11 |
12 | # Warning: The database defined as "test" will be erased and
13 | # re-generated from your development database when you run "rake".
14 | # Do not set this db to the same as development or production.
15 | test:
16 | adapter: sqlite3
17 | database: db/test.sqlite3
18 | pool: 5
19 | timeout: 5000
20 |
21 | production:
22 | adapter: sqlite3
23 | database: db/production.sqlite3
24 | pool: 5
25 | timeout: 5000
26 |
--------------------------------------------------------------------------------
/spec/dummy/app/assets/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // This is a manifest file that'll be compiled into application.js, which will include all the files
2 | // listed below.
3 | //
4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6 | //
7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8 | // the compiled file.
9 | //
10 | // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11 | // GO AFTER THE REQUIRES BELOW.
12 | //
13 | //= require jquery
14 | //= require jquery_ujs
15 | //= require_tree .
16 |
--------------------------------------------------------------------------------
/spec/dummy/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/lib/tasks/jobbr_heroku_tasks.rake:
--------------------------------------------------------------------------------
1 | require 'jobbr/ohm.rb'
2 |
3 | namespace :jobbr do
4 |
5 | namespace :heroku do
6 |
7 | desc 'Run all minutely Heroku jobs'
8 | task :minutely => :environment do
9 | run_heroku_scheduled_classes(:minutely)
10 | end
11 |
12 | desc 'Run all hourly Heroku jobs'
13 | task :hourly => :environment do
14 | run_heroku_scheduled_classes(:hourly)
15 | end
16 |
17 | desc 'Run all daily Heroku jobs'
18 | task :daily => :environment do
19 | run_heroku_scheduled_classes(:daily)
20 | end
21 |
22 | def run_heroku_scheduled_classes(frequency)
23 | Jobbr::Ohm.models(Jobbr::Scheduled).select{|c| c.heroku_frequency == frequency }.sort{|a,b| b.heroku_priority <=> a.heroku_priority}.each(&:run)
24 | end
25 |
26 | end
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem 'rails', '>= 4.0.0'
6 |
7 | # UI
8 | gem 'jquery-rails'
9 | gem 'haml'
10 | gem 'chronic_duration'
11 | gem 'sass-rails', '>= 4.0.2'
12 | gem 'coffee-rails'
13 | gem 'therubyracer'
14 | gem 'less-rails'
15 | gem 'bootstrap-sass'
16 | gem 'font-awesome-rails'
17 | gem 'turbolinks'
18 | gem 'kaminari'
19 |
20 | # Backend
21 | gem 'redis'
22 | gem 'ohm', '>= 2.0.1'
23 | gem 'ohm-contrib'
24 | gem 'sidekiq'
25 | gem 'whenever'
26 | gem 'require_all'
27 |
28 | group :development do
29 | gem 'unicorn'
30 | end
31 |
32 | group :test do
33 | gem 'rspec-rails', '~> 2.99'
34 | gem 'mocha'
35 | gem 'generator_spec'
36 | gem 'codeclimate-test-reporter', require: 'nil'
37 | gem 'capybara'
38 | gem 'poltergeist'
39 | gem 'launchy'
40 | gem 'timecop'
41 | end
42 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env rake
2 | begin
3 | require 'bundler/setup'
4 | rescue LoadError
5 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6 | end
7 |
8 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
9 | load APP_RAKEFILE
10 |
11 | # === Gems install tasks ===
12 | Bundler::GemHelper.install_tasks
13 |
14 | desc 'Run Rspec tests'
15 | task :default do
16 | ["rspec spec"].each do |cmd|
17 | puts "Starting to run #{cmd}..."
18 | system("export DISPLAY=:99.0 && bundle exec #{cmd}")
19 | raise "#{cmd} failed!" unless $?.exitstatus == 0
20 | end
21 | end
22 |
23 | task :push_gem do
24 | puts "Building gem (version: #{Jobbr::VERSION})"
25 | system "gem build jobbr.gemspec"
26 | puts 'Pushing to rubygems.org'
27 | system "gem push jobbr-#{Jobbr::VERSION}.gem"
28 | end
--------------------------------------------------------------------------------
/spec/dummy/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/models/jobbr/scheduled.rb:
--------------------------------------------------------------------------------
1 | require 'active_support/concern'
2 |
3 | module Jobbr
4 |
5 | module Scheduled
6 |
7 | extend ActiveSupport::Concern
8 |
9 | module ClassMethods
10 |
11 | def every(every = nil, options = {})
12 | @every = [every, options] if every
13 | @every
14 | end
15 |
16 | # heroku frequency can be :minutely, :hourly or :daily
17 | def heroku_run(frequency, options = {})
18 | @heroku_frequency = frequency
19 | @heroku_priority = options[:priority] || 0
20 | end
21 |
22 | def heroku_frequency
23 | @heroku_frequency
24 | end
25 |
26 | def heroku_priority
27 | @heroku_priority
28 | end
29 |
30 | def task_name
31 | name.demodulize.underscore
32 | end
33 |
34 | end
35 |
36 | end
37 |
38 | end
39 |
--------------------------------------------------------------------------------
/spec/dummy/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/spec/controllers/delayed_jobs_controller_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Jobbr
4 |
5 | describe DelayedJobsController do
6 |
7 | routes { Jobbr::Engine.routes }
8 |
9 | it 'creates a new job by its name' do
10 | expect {
11 | post :create, {job_name: 'delayed_jobs/dummy_job'}
12 | }.to change{ Job.count }.by(1)
13 |
14 | job = DelayedJobs::DummyJob.instance
15 | job.runs.count.should == 1
16 |
17 | JSON.parse(response.body)['id'].should == job.runs.first.id
18 | end
19 |
20 | it 'returns a job run status' do
21 | DelayedJobs::DummyJob.run({})
22 |
23 | run = Run.all.first
24 | get :show, {id: run.id}
25 |
26 | json = JSON.parse(response.body)
27 | json['status'].should == 'success'
28 | json['progress'].should == 100
29 | json['result'].should be_nil
30 | end
31 |
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/app/views/layouts/jobbr/application.html.haml:
--------------------------------------------------------------------------------
1 | !!! XML
2 | !!!
3 | %html{ :xmlns => 'http://www.w3.org/1999/xhtml' }
4 | %head
5 | %title JobbR
6 |
7 | = csrf_meta_tags
8 |
9 | :javascript
10 | function getURLParameter(name) {
11 | return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20'))||null
12 | }
13 |
14 | = stylesheet_link_tag 'jobbr/application'
15 | = javascript_include_tag 'jobbr/application'
16 |
17 | %body
18 | .navbar.navbar-default
19 | .navbar-header
20 | %a.navbar-brand= t('.title')
21 | .collapse.navbar-collapse
22 | .nav.navbar-nav.navbar-right
23 | %a.btn.btn-default#auto-refresh{type: 'button', 'data-toggle' => 'button', href: '#'}
24 | = fa_icon 'refresh'
25 | = t('.auto_refresh')
26 | #main.container
27 | = yield
28 |
--------------------------------------------------------------------------------
/app/views/jobbr/jobs/_job_list.html.haml:
--------------------------------------------------------------------------------
1 | %h5= title
2 |
3 | %table.table.table-striped.table-hover{class: css_class}
4 | %thead
5 | %tr
6 | %th= t('.status')
7 | %th= t('.job_name')
8 | %th= t('.last_run')
9 | %th= t('.average_run_time')
10 | %th
11 |
12 | %tbody
13 | - jobs.each do |job|
14 | %tr
15 | %td
16 | = status_icon(job.last_run.status)
17 | %td= job.name
18 | - if job.last_run && job.last_run.started_at
19 | %td= l job.last_run.started_at.localtime
20 | %td= ChronicDuration.output(job.average_run_time)
21 | %td
22 | .btn-toolbar
23 | .btn-group
24 | = link_to job_path(job), class: 'btn all-runs', title: t('.see_all_runs') do
25 | = fa_icon 'list'
26 | = link_to job_run_path(job, job.last_run), class: 'btn last-run', title: t('.see_last_run') do
27 | = fa_icon 'download'
28 |
--------------------------------------------------------------------------------
/spec/models/delayed_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Jobbr
4 |
5 | describe Delayed do
6 |
7 | it 'creates a new job by its name' do
8 | expect {
9 | Job.run_by_name('delayed_jobs/dummy_job', {}, false)
10 | }.to change{ Job.count }.by(1)
11 |
12 | job = DelayedJobs::DummyJob.instance
13 | job.runs.count.should == 1
14 | job.runs.first.messages.count.should == 2
15 | end
16 |
17 | it 'does not create duplicated name jobs' do
18 | expect {
19 | Job.run_by_name('delayed_jobs/dummy_job', {}, false)
20 | Job.run_by_name('delayed_jobs/dummy_job', {}, false)
21 | Job.run_by_name('delayed_jobs/dummy_job', {}, false)
22 | }.to change{ Job.all.count }.by(1)
23 |
24 | expect {
25 | Job.run_by_name('delayed_jobs/other_dummy_job', {}, false)
26 | Job.run_by_name('delayed_jobs/other_dummy_job', {}, false)
27 | }.to change{ Job.all.count }.by(1)
28 | end
29 |
30 | end
31 |
32 | end
33 |
--------------------------------------------------------------------------------
/lib/jobbr/ohm_pagination.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class OhmPagination
4 |
5 | attr_accessor :array, :current_page, :limit_value, :total_items, :sort_by, :order
6 |
7 | def initialize(array)
8 | @array = array
9 | @total_items = array.count
10 | end
11 |
12 | def page(page)
13 | @current_page = [page.to_i, 1].max
14 | self
15 | end
16 |
17 | def sort_by(sort_by)
18 | @sort_by = sort_by
19 | self
20 | end
21 |
22 | def order(order)
23 | @order = order
24 | self
25 | end
26 |
27 | def per(limit_value)
28 | @limit_value = limit_value.to_i
29 | self
30 | end
31 |
32 | def total_pages
33 | @total_pages ||= (total_items.to_f / limit_value).ceil
34 | end
35 |
36 | def each
37 | return unless block_given?
38 | limit_start = (current_page - 1) * limit_value
39 | array.sort_by(@sort_by, order: @order, limit: [limit_start, limit_value]).each do |item|
40 | yield(item)
41 | end
42 | end
43 |
44 | end
45 |
46 | end
--------------------------------------------------------------------------------
/app/models/jobbr/run.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class Run < ::Ohm::Model
4 |
5 | include ::Ohm::Timestamps
6 | include ::Ohm::DataTypes
7 | include ::Ohm::Callbacks
8 |
9 | attribute :status, Type::Symbol
10 | attribute :started_at, Type::Time
11 | attribute :finished_at, Type::Time
12 | attribute :progress, Type::Integer
13 | attribute :result
14 |
15 | reference :job, 'Jobbr::Job'
16 | collection :messages, 'Jobbr::LogMessage'
17 |
18 | index :status
19 |
20 | def run_time
21 | @run_time ||= if finished_at && started_at
22 | finished_at - started_at
23 | else
24 | nil
25 | end
26 | end
27 |
28 | def to_param
29 | id
30 | end
31 |
32 | def ordered_messages
33 | self.messages.sort_by(:created_at, order: 'ALPHA ASC')
34 | end
35 |
36 | def before_delete
37 | self.messages.each(&:delete)
38 | end
39 |
40 | def logger
41 | @logger ||= Jobbr::Logger.new(Rails.logger, self)
42 | end
43 |
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2013 YOURNAME
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.
21 |
--------------------------------------------------------------------------------
/app/assets/javascripts/jobbr/application.js.coffee:
--------------------------------------------------------------------------------
1 | #= require jquery
2 | #= require jquery_ujs
3 | #= require turbolinks
4 | #= require bootstrap
5 |
6 | #= require_self
7 |
8 | autoRefreshInterval = 3000
9 | timeout = undefined
10 |
11 | scrollToBottom = ($container) ->
12 | if $container.length > 0
13 | $container.scrollTop($container[0].scrollHeight)
14 |
15 | toggleRefreshButton = ->
16 | $('#auto-refresh').toggleClass('active')
17 | $('#auto-refresh i').toggleClass('fa-spin')
18 | $('#auto-refresh').hasClass('active')
19 |
20 | enableAutoRefresh = (force = false) ->
21 | if force || (getURLParameter('refresh') == '1')
22 | if toggleRefreshButton()
23 | timeout = setTimeout(->
24 | Turbolinks.visit("#{document.location.pathname}?refresh=1")
25 | , autoRefreshInterval)
26 | else
27 | clearTimeout(timeout)
28 | Turbolinks.visit(document.location.pathname)
29 |
30 | init = ->
31 | scrollToBottom($('.logs'))
32 | enableAutoRefresh()
33 | $('#auto-refresh').on 'click', -> enableAutoRefresh(true)
34 |
35 | $(document).ready ->
36 |
37 | init()
38 | $(document).on 'page:load', init
39 |
--------------------------------------------------------------------------------
/config/locales/jobbr.en.yml:
--------------------------------------------------------------------------------
1 | en:
2 |
3 | time:
4 | formats:
5 | default: "%d %b %Y %H:%M:%S"
6 |
7 | layouts:
8 | jobbr:
9 | application:
10 | title: JobbR
11 | auto_refresh: Auto-refresh
12 |
13 | jobbr:
14 |
15 | jobs:
16 |
17 | index:
18 | title: Job list
19 | scheduled_jobs: Scheduled Jobs
20 | delayed_jobs: Delayed Jobs
21 |
22 | job_list:
23 | status: Status
24 | job_name: Name
25 | last_run: Last ran at
26 | average_run_time: Average run time
27 | see_all_runs: See all runs
28 | see_last_run: See last run
29 |
30 | show:
31 | status: Status
32 | last_run: Last run
33 | last_run_logs: Last run logs
34 | duration: Duration
35 | scheduling: "Scheduled every %{scheduling}"
36 | average_run_time: "Average duration: %{run_time}"
37 | see_run: See details for this run
38 |
39 | runs:
40 | show:
41 | run_time: "Run time: %{run_time}"
42 | previous_run: "Previous run"
43 | next_run: "Next run"
44 |
--------------------------------------------------------------------------------
/lib/jobbr/logger.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class Logger
4 |
5 | attr_accessor :run, :wrapped_logger, :level
6 |
7 | def initialize(logger, run)
8 | self.wrapped_logger = logger
9 | self.run = run
10 | self.level = 0
11 | end
12 |
13 | def debug(message)
14 | wrapped_logger.debug(message)
15 | write_message(:debug, message)
16 | end
17 |
18 | def info(message)
19 | wrapped_logger.info(message)
20 | write_message(:info, message)
21 | end
22 |
23 | def warn(message)
24 | wrapped_logger.warn(message)
25 | write_message(:warn, message)
26 | end
27 |
28 | def error(message)
29 | wrapped_logger.error(message)
30 | write_message(:error, message)
31 | end
32 |
33 | def fatal(message)
34 | wrapped_logger.error(message)
35 | write_message(:fatal, message)
36 | end
37 |
38 | protected
39 |
40 | def write_message(kind, message)
41 | if message.is_a? Array
42 | message = message.join('
')
43 | end
44 | Jobbr::LogMessage.create(kind: kind, message: message, run: run)
45 | end
46 |
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/jobbr/application.css.scss:
--------------------------------------------------------------------------------
1 | @import 'bootstrap';
2 | @import 'font-awesome';
3 |
4 | .navbar-right {
5 | margin-top: 5px;
6 | .btn {
7 | margin-right: 5px;
8 | }
9 | }
10 |
11 | .job-status {
12 |
13 | margin-left: 15px;
14 | font-size: 18px;
15 |
16 | &.waiting {
17 | color: orange;
18 | }
19 |
20 | &.running, &.success {
21 | color: limegreen;
22 | }
23 | &.failed {
24 | color: red;
25 | }
26 |
27 | }
28 |
29 | .breadcrumb {
30 |
31 | .job-status {
32 | margin-left: 5px;
33 | font-size: 15px;
34 | }
35 |
36 | }
37 |
38 | .logs {
39 | background-color: #424242;
40 | color: white;
41 | font-family: Monaco, 'Courier New';
42 | height: 200px;
43 | overflow: auto;
44 |
45 | .kind, .date {
46 | color: #AAAAAA;
47 | }
48 | .kind, .date {
49 | &.error, &.fatal {
50 | color: #FF8E8E;
51 | }
52 | &.warn {
53 | color: orange;
54 | }
55 | }
56 |
57 | &.large {
58 | height: 600px
59 | }
60 |
61 | }
62 |
63 | .table td {
64 | padding-top: 14px;
65 | }
66 |
67 | .btn-toolbar {
68 | margin: -5px 0 0;
69 | i {
70 | font-size: 14px;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/views/jobbr/jobs/show.html.haml:
--------------------------------------------------------------------------------
1 | %ol.breadcrumb
2 | %li
3 | = link_to t('jobbr.jobs.index.title'), jobs_path
4 | %li.active= @job.name.humanize
5 |
6 | .well
7 | - if @job.scheduled? && @job.every
8 | %p= raw t('.scheduling', scheduling: display_scheduling(@job))
9 | = raw t('.average_run_time', run_time: ChronicDuration.output(@job.average_run_time, format: :long))
10 |
11 | = render 'jobbr/runs/logs', run: @last_run, title: t('.last_run_logs'), size: 'small'
12 |
13 | %table.table.table-striped.table-hover
14 | %thead
15 | %tr
16 | %th= t('.status')
17 | %th= t('.last_run')
18 | %th= t('.duration')
19 | %th
20 |
21 | %tbody
22 | - @runs.each do |run|
23 | %tr
24 | %td
25 | = status_icon(run.status)
26 | - if run.started_at
27 | %td= l run.started_at.localtime
28 | %td= ChronicDuration.output((run.finished_at - run.started_at).round(2)) rescue 'N/A'
29 | %td
30 | .btn-toolbar
31 | .btn-group
32 | = link_to job_run_path(@job, run), class: 'btn see-run', title: t('.see_run') do
33 | = fa_icon 'download'
34 |
35 | = paginate @runs
36 |
--------------------------------------------------------------------------------
/spec/models/job_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Jobbr
4 |
5 | describe Job do
6 |
7 | it "behaves consistently with polymorphism" do
8 | dummy_job = DelayedJobs::DummyJob.instance
9 | other_dummy_job = DelayedJobs::OtherDummyJob.instance
10 |
11 | Run::create(job: dummy_job)
12 | dummy_job.runs.size.should == 1
13 | other_dummy_job.runs.size.should == 0
14 |
15 | Run::create(job: other_dummy_job)
16 | dummy_job.runs.size.should == 1
17 | other_dummy_job.runs.size.should == 1
18 | end
19 |
20 | it "destroys related runs and logs when deleted" do
21 | expect {
22 | DelayedJobs::DummyJob.run({})
23 | }.to change { Job.all.count + Run.all.count + LogMessage.all.count }.from(0)
24 |
25 | expect {
26 | DelayedJobs::DummyJob.instance.delete
27 | }.to change { Job.all.count + Run.all.count + LogMessage.all.count }.to(0)
28 | end
29 |
30 | it "consistently pass params to jobs" do
31 | params = {foo: 1, bar: 2}
32 | DelayedJobs::DummyJob.any_instance.expects(:perform).with(instance_of(Jobbr::Run), params)
33 | DelayedJobs::DummyJob.run(params, false)
34 | end
35 |
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/jobbr/ohm.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | module Ohm
4 |
5 | extend self
6 |
7 | require 'ohm'
8 |
9 | # Return all Ohm models.
10 | # You can also pass a module class to get all models including that module
11 | def models(parent = nil)
12 | model_paths = Dir["#{Rails.root}/app/models/*_jobs/*.rb"]
13 | model_paths.each{ |path| require path }
14 | sanitized_model_paths = model_paths.map { |path| path.gsub(/.*\/app\/models\//, '').gsub('.rb', '') }
15 | model_constants = sanitized_model_paths.map do |path|
16 | path.split('/').map { |token| token.camelize }.join('::').constantize
17 | end
18 | model_constants.select { |model| superclasses(model).include?(::Ohm::Model) }
19 |
20 | if parent
21 | model_constants.select { |model| model.included_modules.include?(parent) }
22 | else
23 | model_constants
24 | end
25 | end
26 |
27 | protected
28 |
29 | # Return all superclasses for a given class.
30 | def superclasses(klass)
31 | super_classes = []
32 | while klass != Object
33 | klass = klass.superclass
34 | super_classes << klass
35 | end
36 | super_classes
37 | end
38 |
39 | end
40 |
41 | end
42 |
--------------------------------------------------------------------------------
/app/helpers/jobbr/application_helper.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 | module ApplicationHelper
3 |
4 | include FontAwesome::Rails::IconHelper
5 |
6 | def delayed_job_creation_path(delayed_job_class, params = {})
7 | jobbr.delayed_jobs_path(params.merge(job_name: delayed_job_class.name.underscore))
8 | end
9 |
10 | def delayed_job_polling_path(id = ':job_id')
11 | jobbr.delayed_job_path(id)
12 | end
13 |
14 | def status_icon(job_status)
15 | css_class = "job-status #{job_status}"
16 | if job_status == :waiting
17 | fa_icon 'circle-o', class: css_class
18 | elsif job_status == :running
19 | fa_icon 'refresh', class: "#{css_class} fa-spin"
20 | elsif job_status == :success
21 | fa_icon 'certificate', class: css_class
22 | else
23 | fa_icon 'exclamation-circle', class: css_class
24 | end
25 | end
26 |
27 | def display_scheduling(job)
28 | every = job.every
29 | if every
30 | scheduling = every[0].is_a?(Fixnum) ? ChronicDuration.output(every[0]) : every[0].to_s
31 | if every[1] && !every[1].empty?
32 | scheduling = "#{scheduling} at #{every[1][:at]}"
33 | end
34 | scheduling
35 | end
36 | end
37 |
38 | end
39 | end
40 |
--------------------------------------------------------------------------------
/spec/tasks/jobbr_tasks_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'rake'
3 |
4 | describe 'jobbr_tasks' do
5 |
6 | before do
7 | Rake.application.rake_require 'tasks/jobbr_tasks'
8 | Rake::Task.define_task(:environment)
9 | end
10 |
11 | describe 'dynamic jobbr tasks declaration' do
12 |
13 | xit 'should define jobbr tasks' do
14 | task_names = Rake.application.tasks.map(&:name)
15 | task_names.should include('jobbr:dummy_heroku_job')
16 | task_names.should include('jobbr:dummy_scheduled_job')
17 | task_names.should include('jobbr:logging_job')
18 | task_names.should include('jobbr:dummy_heroku_job')
19 | end
20 |
21 | xit 'actually run jobs' do
22 | expect {
23 | Rake.application.invoke_task 'jobbr:logging_job'
24 | }.to change { Jobbr::Run.all.count }.from(0).to(1)
25 |
26 | Jobbr::Run.all.first.status.should be :success
27 | end
28 |
29 | end
30 |
31 | describe 'job sweeping' do
32 |
33 | before do
34 | 3.times { Jobbr::Run.create(status: :running, started_at: Time.now) }
35 | 2.times { Jobbr::Run.create(status: :success, started_at: Time.now) }
36 | end
37 |
38 | it 'marks running jobs as failed' do
39 | expect {
40 | Rake.application.invoke_task 'jobbr:sweep_running_jobs'
41 | }.to change { Jobbr::Run.find(status: :failed).count }.from(0).to(3)
42 | end
43 |
44 | end
45 |
46 | end
47 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Show full error reports and disable caching
10 | config.consider_all_requests_local = true
11 | config.action_controller.perform_caching = false
12 |
13 | # Don't care if the mailer can't send
14 | config.action_mailer.raise_delivery_errors = false
15 |
16 | # Print deprecation notices to the Rails logger
17 | config.active_support.deprecation = :log
18 |
19 | # Only use best-standards-support built into browsers
20 | config.action_dispatch.best_standards_support = :builtin
21 |
22 | # Raise exception on mass assignment protection for Active Record models
23 | # config.active_record.mass_assignment_sanitizer = :strict
24 |
25 | # Log the query plan for queries taking more than this (works
26 | # with SQLite, MySQL, and PostgreSQL)
27 | # config.active_record.auto_explain_threshold_in_seconds = 0.5
28 |
29 | # Do not compress assets
30 | config.assets.compress = false
31 |
32 | # Expands the lines which load the assets
33 | config.assets.debug = true
34 |
35 | config.eager_load = false
36 | end
37 |
--------------------------------------------------------------------------------
/jobbr.gemspec:
--------------------------------------------------------------------------------
1 | $:.push File.expand_path('../lib', __FILE__)
2 |
3 | require 'jobbr/version'
4 |
5 | Gem::Specification.new do |s|
6 |
7 | s.name = 'jobbr'
8 | s.version = Jobbr::VERSION
9 | s.authors = ['Christian Blavier']
10 | s.email = ['cblavier@gmail.com']
11 | s.homepage = 'https://github.com/cblavier/jobbr'
12 | s.summary = 'Rails engine to manage jobs.'
13 | s.description = 'Rails engine to manage and supervise your batch jobs. Based on sidekiq.'
14 |
15 | s.files = `git ls-files`.split("\n")
16 | s.test_files = `git ls-files -- spec/*`.split("\n")
17 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18 | s.require_paths = ['lib']
19 |
20 | s.add_runtime_dependency 'rails', '>= 4.0.0'
21 |
22 | # UI
23 | s.add_runtime_dependency 'jquery-rails'
24 | s.add_runtime_dependency 'haml'
25 | s.add_runtime_dependency 'chronic_duration'
26 | s.add_runtime_dependency 'sass-rails', '>= 4.0.2'
27 | s.add_runtime_dependency 'coffee-rails'
28 | s.add_runtime_dependency 'therubyracer'
29 | s.add_runtime_dependency 'less-rails'
30 | s.add_runtime_dependency 'bootstrap-sass'
31 | s.add_runtime_dependency 'font-awesome-rails'
32 | s.add_runtime_dependency 'turbolinks'
33 | s.add_runtime_dependency 'kaminari'
34 |
35 | # Backend
36 | s.add_runtime_dependency 'redis'
37 | s.add_runtime_dependency 'ohm', '>= 2.0.1'
38 | s.add_runtime_dependency 'ohm-contrib'
39 | s.add_runtime_dependency 'sidekiq', '>= 3.0.0'
40 | s.add_runtime_dependency 'whenever'
41 | s.add_runtime_dependency 'require_all'
42 |
43 | end
44 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Configure static asset server for tests with Cache-Control for performance
11 | config.serve_static_assets = true
12 | config.static_cache_control = "public, max-age=3600"
13 |
14 | # Show full error reports and disable caching
15 | config.consider_all_requests_local = true
16 | config.action_controller.perform_caching = false
17 |
18 | # Raise exceptions instead of rendering exception templates
19 | config.action_dispatch.show_exceptions = false
20 |
21 | # Disable request forgery protection in test environment
22 | config.action_controller.allow_forgery_protection = false
23 |
24 | # Tell Action Mailer not to deliver emails to the real world.
25 | # The :test delivery method accumulates sent emails in the
26 | # ActionMailer::Base.deliveries array.
27 | config.action_mailer.delivery_method = :test
28 |
29 | # Raise exception on mass assignment protection for Active Record models
30 | # config.active_record.mass_assignment_sanitizer = :strict
31 |
32 | # Print deprecation notices to the stderr
33 | config.active_support.deprecation = :stderr
34 |
35 | config.eager_load = false
36 | end
37 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 |
3 | if ENV['CI'] == 'true'
4 | ENV['CODECLIMATE_REPO_TOKEN'] ||= 'edafaf863fa93aff340625ec4ac8d70244456849256d0b4b16383ca0a76a11ec'
5 | require "codeclimate-test-reporter"
6 | CodeClimate::TestReporter.start
7 | end
8 |
9 | require File.join(File.dirname(__FILE__), 'dummy', 'config', 'environment.rb')
10 | require 'require_all'
11 | require 'rspec/rails'
12 | require 'generator_spec'
13 | require 'capybara/rspec'
14 | require 'capybara/poltergeist'
15 | require 'timecop'
16 |
17 | require_all Rails.root.join('..','..','lib','generators','jobbr', '**/*_generator.rb')
18 | require_all File.join(File.dirname(__FILE__), 'support', '**', '*.rb')
19 |
20 | SPEC_TMP_ROOT = Pathname.new(Dir.tmpdir)
21 |
22 | RSpec.configure do |config|
23 |
24 | Capybara.javascript_driver = :poltergeist
25 | config.mock_with :mocha
26 |
27 | config.include GeneratorSpec::TestCase, type: :generator
28 | config.include GeneratorDestinationRoot, type: :generator
29 | config.include RSpec::Rails::RequestExampleGroup, type: :feature
30 |
31 | config.before(:each, type: :generator) do
32 | FileUtils.rm_rf(SPEC_TMP_ROOT)
33 | prepare_destination
34 | end
35 |
36 | config.after(:each, type: :generator) do
37 | FileUtils.rm_rf(SPEC_TMP_ROOT)
38 | end
39 |
40 | config.before(:each) do
41 | clean_redis
42 | end
43 |
44 | config.after(:all) do
45 | Timecop.return
46 | clean_redis
47 | end
48 |
49 | def clean_redis
50 | Ohm.redis.call('KEYS', 'Jobbr::*').each{ |key| Ohm.redis.call('DEL', key) }
51 | end
52 |
53 | config.infer_spec_type_from_file_location!
54 |
55 | end
56 |
--------------------------------------------------------------------------------
/spec/features/job_list_feature_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 | require 'spec_helper'
3 |
4 | feature 'Job list' do
5 |
6 | before do
7 | Timecop.travel(2.hours.ago)
8 | ScheduledJobs::DummyJob.run
9 | ScheduledJobs::LoggingJob.run
10 | DelayedJobs::DummyJob.run
11 | DelayedJobs::OtherDummyJob.run
12 | Timecop.return
13 | end
14 |
15 | it 'shows all scheduled jobs' do
16 | visit jobbr_path
17 | assert_title 'Job list'
18 | find('.table.scheduled-jobs tbody').should have_selector('tr', count: 2)
19 | end
20 |
21 |
22 | it 'shows all delayed jobs' do
23 | visit jobbr_path
24 | assert_title 'Job list'
25 | find('.table.delayed-jobs tbody').should have_selector('tr', count: 2)
26 | end
27 |
28 | it 'shows correct status for each job' do
29 | Jobbr::Run.create(status: :failed, started_at: Time.now, job: ScheduledJobs::DummyJob.instance)
30 | visit jobbr_path
31 | assert_title 'Job list'
32 | first('.table.scheduled-jobs tbody tr').should have_selector('i.failed')
33 | end
34 |
35 | it 'show all runs for a specific job' do
36 | Timecop.travel(5.minutes.ago)
37 | Jobbr::Run.create(status: :failed, started_at: Time.now, job: ScheduledJobs::DummyJob.instance)
38 | Timecop.return
39 | Jobbr::Run.create(status: :running, started_at: Time.now, job: ScheduledJobs::DummyJob.instance)
40 |
41 | visit jobbr_path
42 | assert_title 'Job list'
43 |
44 | first('.scheduled-jobs a.all-runs').click
45 | assert_title 'Dummy job'
46 | find('.table tbody').should have_selector('tr', count: 3)
47 | end
48 |
49 | it 'shows a specific run' do
50 | visit jobbr_path
51 | assert_title 'Job list'
52 |
53 | first('.scheduled-jobs a.last-run').click
54 | assert_title I18n.localize(ScheduledJobs::DummyJob.instance.ordered_runs.first.started_at)
55 | end
56 |
57 | def assert_title(title)
58 | find('ol.breadcrumb li.active').should have_content(title)
59 | end
60 |
61 | end
62 |
--------------------------------------------------------------------------------
/spec/dummy/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | Dummy::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # Code is not reloaded between requests
5 | config.cache_classes = true
6 |
7 | # Full error reports are disabled and caching is turned on
8 | config.consider_all_requests_local = false
9 | config.action_controller.perform_caching = true
10 |
11 | # Disable Rails's static asset server (Apache or nginx will already do this)
12 | config.serve_static_assets = false
13 |
14 | # Compress JavaScripts and CSS
15 | config.assets.compress = true
16 |
17 | # Don't fallback to assets pipeline if a precompiled asset is missed
18 | config.assets.compile = false
19 |
20 | # Generate digests for assets URLs
21 | config.assets.digest = true
22 |
23 | # Defaults to nil and saved in location specified by config.assets.prefix
24 | # config.assets.manifest = YOUR_PATH
25 |
26 | # Specifies the header that your server uses for sending files
27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
29 |
30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
31 | # config.force_ssl = true
32 |
33 | # See everything in the log (default is :info)
34 | # config.log_level = :debug
35 |
36 | # Prepend all log lines with the following tags
37 | # config.log_tags = [ :subdomain, :uuid ]
38 |
39 | # Use a different logger for distributed setups
40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
41 |
42 | # Use a different cache store in production
43 | # config.cache_store = :mem_cache_store
44 |
45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server
46 | # config.action_controller.asset_host = "http://assets.example.com"
47 |
48 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
49 | # config.assets.precompile += %w( search.js )
50 |
51 | # Disable delivery errors, bad email addresses will be ignored
52 | # config.action_mailer.raise_delivery_errors = false
53 |
54 | # Enable threaded mode
55 | # config.threadsafe!
56 |
57 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
58 | # the I18n.default_locale when a translation can not be found)
59 | config.i18n.fallbacks = true
60 |
61 | # Send deprecation notices to registered listeners
62 | config.active_support.deprecation = :notify
63 |
64 | # Log the query plan for queries taking more than this (works
65 | # with SQLite, MySQL, and PostgreSQL)
66 | # config.active_record.auto_explain_threshold_in_seconds = 0.5
67 |
68 | config.eager_load = true
69 | end
70 |
--------------------------------------------------------------------------------
/spec/dummy/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require "action_controller/railtie"
4 | require "action_mailer/railtie"
5 | require "sprockets/railtie"
6 |
7 | Bundler.require
8 | require "jobbr"
9 |
10 | module Dummy
11 | class Application < Rails::Application
12 | # Settings in config/environments/* take precedence over those specified here.
13 | # Application configuration should go into files in config/initializers
14 | # -- all .rb files in that directory are automatically loaded.
15 |
16 | # Custom directories with classes and modules you want to be autoloadable.
17 | # config.autoload_paths += %W(#{config.root}/extras)
18 |
19 | # Only load the plugins named here, in the order given (default is alphabetical).
20 | # :all can be used as a placeholder for all plugins not explicitly named.
21 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
22 |
23 | # Activate observers that should always be running.
24 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
25 |
26 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
27 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
28 | # config.time_zone = 'Central Time (US & Canada)'
29 |
30 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
31 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
32 | # config.i18n.default_locale = :de
33 |
34 | # Configure the default encoding used in templates for Ruby 1.9.
35 | config.encoding = "utf-8"
36 |
37 | # Configure sensitive parameters which will be filtered from the log file.
38 | config.filter_parameters += [:password]
39 |
40 | # Enable escaping HTML in JSON.
41 | config.active_support.escape_html_entities_in_json = true
42 |
43 | # Use SQL instead of Active Record's schema dumper when creating the database.
44 | # This is necessary if your schema can't be completely dumped by the schema dumper,
45 | # like if you have constraints or database-specific column types
46 | # config.active_record.schema_format = :sql
47 |
48 | # Enforce whitelist mode for mass assignment.
49 | # This will create an empty whitelist of attributes available for mass-assignment for all models
50 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible
51 | # parameters by using an attr_accessible or attr_protected declaration.
52 | #config.active_record.whitelist_attributes = true
53 |
54 | # Enable the asset pipeline
55 | config.assets.enabled = true
56 |
57 | # Version of your assets, change this if you want to expire all your assets
58 | config.assets.version = '1.0'
59 |
60 | I18n.enforce_available_locales = false
61 |
62 | config.secret_key_base = '12b5558fef409d5a8e0966a8b38671b7a25708d1c8777bd75760064c8c3cbe82bdd834e7fc3b7b94cd736d03c413265b114b2414691a01ec01340a086717440b'
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/spec/dummy/config/mongoid.yml:
--------------------------------------------------------------------------------
1 | development:
2 | # Configure available database sessions. (required)
3 | sessions:
4 | # Defines the default session. (required)
5 | default:
6 | # Defines the name of the default database that Mongoid can connect to.
7 | # (required).
8 | database: dummy_development
9 | # Provides the hosts the default session can connect to. Must be an array
10 | # of host:port pairs. (required)
11 | hosts:
12 | - localhost:27017
13 | options:
14 | # Change whether the session persists in safe mode by default.
15 | # (default: false)
16 | # safe: false
17 |
18 | # Change the default consistency model to :eventual or :strong.
19 | # :eventual will send reads to secondaries, :strong sends everything
20 | # to master. (default: :eventual)
21 | # consistency: :eventual
22 |
23 | # How many times Moped should attempt to retry an operation after
24 | # failure. (default: 30)
25 | # max_retries: 30
26 |
27 | # The time in seconds that Moped should wait before retrying an
28 | # operation on failure. (default: 1)
29 | # retry_interval: 1
30 | # Configure Mongoid specific options. (optional)
31 | options:
32 | # Configuration for whether or not to allow access to fields that do
33 | # not have a field definition on the model. (default: true)
34 | # allow_dynamic_fields: true
35 |
36 | # Enable the identity map, needed for eager loading. (default: false)
37 | # identity_map_enabled: false
38 |
39 | # Includes the root model name in json serialization. (default: false)
40 | # include_root_in_json: false
41 |
42 | # Include the _type field in serializaion. (default: false)
43 | # include_type_for_serialization: false
44 |
45 | # Preload all models in development, needed when models use
46 | # inheritance. (default: false)
47 | # preload_models: false
48 |
49 | # Protect id and type from mass assignment. (default: true)
50 | # protect_sensitive_fields: true
51 |
52 | # Raise an error when performing a #find and the document is not found.
53 | # (default: true)
54 | # raise_not_found_error: true
55 |
56 | # Raise an error when defining a scope with the same name as an
57 | # existing method. (default: false)
58 | # scope_overwrite_exception: false
59 |
60 | # Skip the database version check, used when connecting to a db without
61 | # admin access. (default: false)
62 | # skip_version_check: false
63 |
64 | # User Active Support's time zone in conversions. (default: true)
65 | # use_activesupport_time_zone: true
66 |
67 | # Ensure all times are UTC in the app side. (default: false)
68 | # use_utc: false
69 | test:
70 | sessions:
71 | default:
72 | database: dummy_test
73 | hosts:
74 | - localhost:27017
75 | options:
76 | consistency: :strong
77 | # In the test environment we lower the retries and retry interval to
78 | # low amounts for fast failures.
79 | max_retries: 1
80 | retry_interval: 0
81 |
--------------------------------------------------------------------------------
/spec/models/scheduled_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Jobbr
4 |
5 | describe Scheduled do
6 |
7 | it 'creates a new job by its name' do
8 | expect {
9 | ScheduledJobs::DummyScheduledJob.run
10 | }.to change{ Job.all.count }.by(1)
11 | end
12 |
13 | it 'does not create duplicated name jobs' do
14 | expect {
15 | ScheduledJobs::DummyScheduledJob.run
16 | ScheduledJobs::DummyScheduledJob.run
17 | ScheduledJobs::DummyScheduledJob.run
18 | }.to change{ Job.all.count }.by(1)
19 | expect {
20 | ScheduledJobs::DummyJob.run
21 | ScheduledJobs::DummyJob.run
22 | }.to change{ Job.all.count }.by(1)
23 | end
24 |
25 | it 'creates a job run for each run' do
26 | ScheduledJobs::DummyScheduledJob.run
27 | job = ScheduledJobs::DummyScheduledJob.instance
28 |
29 | expect {
30 | ScheduledJobs::DummyScheduledJob.run
31 | ScheduledJobs::DummyScheduledJob.run
32 | }.to change{ job.runs.count }.from(1).to(3)
33 | end
34 |
35 | it 'does not create more run than max_run_per_job' do
36 | ScheduledJobs::DummyScheduledJob.run
37 | job = ScheduledJobs::DummyScheduledJob.instance
38 | first_run = job.runs.first
39 | max_run_per_job = 5
40 | Job.any_instance.stubs(:max_run_per_job).returns(max_run_per_job)
41 |
42 | expect {
43 | (max_run_per_job + 3).times do
44 | ScheduledJobs::DummyScheduledJob.run
45 | end
46 | }.to change{ job.runs.count }.from(1).to(max_run_per_job)
47 |
48 | # ensure that it removes first executions and not latest
49 | job.runs.should_not include(first_run)
50 | end
51 |
52 | it 'changes run status from running to success' do
53 | ScheduledJobs::DummyScheduledJob.run do
54 | ScheduledJobs::DummyScheduledJob.instance.runs.first.status.should be :running
55 | end
56 | ScheduledJobs::DummyScheduledJob.instance.runs.first.status.should be :success
57 | end
58 |
59 | it 'changes run status from running to failed in case of exception' do
60 | ScheduledJobs::DummyScheduledJob.any_instance.stubs(:perform).raises('an error')
61 | begin
62 | ScheduledJobs::DummyScheduledJob.run
63 | rescue Exception
64 | end
65 | ScheduledJobs::DummyScheduledJob.instance.runs.first.status.should be :failed
66 | end
67 |
68 | it 'sets running dates' do
69 | ScheduledJobs::DummyScheduledJob.run
70 | job_run = ScheduledJobs::DummyScheduledJob.instance.runs.first
71 | job_run.started_at.should_not be_nil
72 | job_run.finished_at.should_not be_nil
73 | end
74 |
75 | it 'sets the progress to 100% at the end' do
76 | ScheduledJobs::DummyScheduledJob.run
77 | ScheduledJobs::DummyScheduledJob.instance.runs.first.progress.should be 100
78 | end
79 |
80 | it 'creates log messages when logging' do
81 | ScheduledJobs::LoggingJob.run
82 | last_job_run = ScheduledJobs::LoggingJob.instance.runs.first
83 | last_job_run.messages.size.should == 3
84 | last_job_run.messages[2].kind.should be :debug
85 | last_job_run.messages[2].message.should == 'foo'
86 | last_job_run.messages[3].kind.should be :error
87 | last_job_run.messages[3].message.should == 'bar'
88 | end
89 |
90 | end
91 |
92 | end
93 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Jobbr
2 |
3 | Jobbr is a Rails engine for supervising your delayed jobs and scheduled jobs (think Cron).
4 | Delayed jobs will run using sidekiq.
5 |
6 | It provides a framework to abstract creation and execution of such jobs and a user interface to supervise jobs and read their logs.
7 |
8 | {
}[http://travis-ci.org/cblavier/jobbr]
9 |
10 | {
}[https://codeclimate.com/github/cblavier/jobbr]
11 |
12 | {
}[https://codeclimate.com/github/cblavier/jobbr]
13 |
14 | == Screenshots
15 |
16 | {
}[http://cl.ly/image/3r320L101c3h]
17 | {
}[http://cl.ly/image/21433N411G01]
18 |
19 | == Dependencies
20 |
21 | Jobbr has strong dependencies on following components:
22 |
23 | * *Sidekiq*: the background processing framework used to run delayed jobs.
24 | * *Redis*: all jobs & logs are stored in Redis for supervision.
25 | * *Whenever*: Jobbr uses {Whenever}[https://github.com/javan/whenever] gem to automatically updates Crontab during deployment.
26 |
27 | == Setup
28 |
29 | Start by adding Jobbr to your Gemfile:
30 |
31 | gem 'jobbr'
32 |
33 | === User interface
34 |
35 | Then mount Jobbr engine to your `routes.rb` file.
36 |
37 | mount Jobbr::Engine => "/jobbr"
38 |
39 | === Scheduled Jobs
40 |
41 | Use provided generators to create a first scheduled job
42 |
43 | $> rails g jobbr:scheduled_job dummy
44 |
45 | It will create a namespaced model as a well as a Whenever configuration file.
46 |
47 | Provided you fill in description and scheduling attributes in the model, you will be able to see it in jobbr tasks:
48 |
49 | $> bundle exec jobbr --list
50 | bundle exec jobbr dummy_job # A dummy Job
51 |
52 | And to see it in your crontab preview:
53 |
54 | $> whenever
55 | 30 5 * * * /bin/bash -l -c 'cd /Users/cblavier/code/my_app && RAILS_ENV=production bundle exec jobbr dummy_job >> /Users/cblavier/code/my_app/log/cron.log 2>&1'
56 |
57 | === Heroku Scheduled Jobs
58 |
59 | You can also use Heroku Scheduler to run jobs. Unfortunately Heroku does not provide Cron-scheduling, but let you run jobs every 10 minutes, every hour or every day.
60 |
61 | Jobbr provides you with 3 tasks 'jobbr:heroku:minutely', 'jobbr:heroku:hourly' and 'jobbr:heroku:daily', that will run any Job with `heroku_run` directive.
62 |
63 | Then you will need to manually add jobs to the Heroku scheduler console
64 |
65 | {
}[http://cl.ly/image/2N1T1l1w2c28]
66 |
67 |
68 | === Delayed Jobs
69 |
70 | Use generators to get a new job model:
71 |
72 | $> rails g jobbr:delayed_job dummy
73 |
74 | You will get a new model with a perform method. Perform parameters are:
75 |
76 | * params: is a hash of parameters for your job.
77 | * run: is the object that will be persisted (and polled) for this job execution. Your delayed job can use it to provide progress information (to display a progress bar) and a final result.
78 |
79 | run.progress = 100
80 | run.result = 'my job result'
81 |
82 | You can now run your delayed job as following:
83 |
84 | run_id = DelayedJobs::DummyJob.run_delayed(some_param: 37)
85 |
86 | And then get job status like this:
87 |
88 | Jobbr::Run.find(run_id).status # returns :waiting / :running / :failed / :success
89 |
90 | Jobbr also provides a controller to run and poll delayed_jobs :
91 |
92 | * Post on following url to run your job: delayed_job_creation_path(DelayedJobs::DummyJob, { some_param: 37 })
93 |
94 | * And then poll this url (using the id returned in previous post) to get your job status: delayed_job_polling_path(run_id)
95 |
96 | This project rocks and uses MIT-LICENSE.
97 |
--------------------------------------------------------------------------------
/app/models/jobbr/job.rb:
--------------------------------------------------------------------------------
1 | module Jobbr
2 |
3 | class Job < ::Ohm::Model
4 |
5 | MAX_RUN_PER_JOB = 500
6 |
7 | include ::Ohm::Timestamps
8 | include ::Ohm::DataTypes
9 | include ::Ohm::Callbacks
10 |
11 | attribute :type
12 | attribute :delayed, Type::Boolean
13 |
14 | collection :runs, 'Jobbr::Run'
15 |
16 | index :type
17 | index :delayed
18 |
19 | def self.instance(instance_class_name = nil)
20 | if instance_class_name
21 | job_class = instance_class_name.camelize.constantize
22 | else
23 | job_class = self
24 | end
25 |
26 | job = Job.find(type: job_class.to_s).first
27 | if job.nil?
28 | delayed = job_class.included_modules.include?(Jobbr::Delayed)
29 | job = Job.create(type: job_class.to_s, delayed: delayed)
30 | end
31 | job
32 | end
33 |
34 | def self.run_by_name(name, *args)
35 | instance(name).run(*args)
36 | end
37 |
38 | def self.run(*args)
39 | instance.run(*args)
40 | end
41 |
42 | def self.description(desc = nil)
43 | @description = desc if desc
44 | @description
45 | end
46 |
47 | def self.delayed
48 | find(delayed: true)
49 | end
50 |
51 | def self.scheduled
52 | find(delayed: false)
53 | end
54 |
55 | def self.count
56 | all.count
57 | end
58 |
59 | def self.by_name(name)
60 | class_name = name.underscore.camelize
61 | Job.find(type: class_name).first
62 | end
63 |
64 | # overriding Ohm find to get Sidekiq to find job instances
65 | def self.find(id)
66 | if id.instance_of?(Hash)
67 | super
68 | elsif job = Jobbr::Job[id]
69 | job.send(:typed_self)
70 | end
71 | end
72 |
73 | def run(params = {}, delayed = true)
74 | job_run = Run.create(status: :waiting, started_at: Time.now, job: self)
75 | if delayed && self.delayed && !Rails.env.test?
76 | delayed_options = { retry: 0, backtrace: true }
77 | delayed_options[:queue] = typed_self.class.queue if typed_self.class.queue
78 | typed_self.delay(delayed_options).inner_run(job_run.id, params)
79 | else
80 | self.inner_run(job_run.id, params)
81 | end
82 | job_run
83 | end
84 |
85 | def handle_process_interruption(job_run, signals)
86 | signals.each do |signal|
87 | Signal.trap(signal) do
88 | job_run.status = :failed
89 | job_run.logger.error("Job interrupted by a #{signal} signal")
90 | job_run.finished_at = Time.now
91 | job_run.save
92 | end
93 | end
94 | end
95 |
96 | def every
97 | if scheduled?
98 | require self.type.underscore
99 | Object::const_get(self.type).every
100 | else
101 | nil
102 | end
103 | end
104 |
105 | def last_run
106 | @last_run ||= self.ordered_runs.first
107 | end
108 |
109 | def average_run_time
110 | return 0 if runs.empty?
111 | (runs.map { |run| run.run_time }.compact.inject { |sum, el| sum + el }.to_f / runs.count).round(2)
112 | end
113 |
114 | def to_param
115 | self.type.underscore.dasherize.gsub('/', '::')
116 | end
117 |
118 | def name
119 | self.type.demodulize.underscore.humanize
120 | end
121 |
122 | def scheduled?
123 | !self.delayed
124 | end
125 |
126 | def delayed?
127 | self.delayed
128 | end
129 |
130 | def ordered_runs
131 | self.runs.sort_by(:started_at, order: 'ALPHA DESC')
132 | end
133 |
134 | def after_delete
135 | self.runs.each(&:delete)
136 | end
137 |
138 | def perform
139 | raise NotImplementedError.new :message => 'Must be implemented'
140 | end
141 |
142 | protected
143 |
144 | # mocking purpose
145 | def max_run_per_job
146 | MAX_RUN_PER_JOB
147 | end
148 |
149 | # prevents Run collection to grow beyond max_run_per_job
150 | def cap_runs!
151 | runs_count = self.runs.count
152 | if runs_count > max_run_per_job
153 | runs.sort_by(:started_at, order: 'ALPHA ASC', limit: [0, runs_count - max_run_per_job]).each do |run|
154 | if run.status == :failed || run.status == :success
155 | run.delete
156 | end
157 | end
158 | end
159 | end
160 |
161 | def inner_run(job_run_id, params = {})
162 | job_run = Run[job_run_id]
163 | job_run.status = :running
164 | job_run.started_at = Time.now
165 | job_run.save
166 |
167 | cap_runs!
168 |
169 | handle_process_interruption(job_run, ['TERM', 'INT'])
170 |
171 | begin
172 | job_run.logger.debug("Starting with params #{params.inspect}")
173 | perform(job_run, params)
174 | job_run.status = :success
175 | job_run.progress = 100
176 | rescue Exception => e
177 | job_run.status = :failed
178 | job_run.logger.error(e.message)
179 | job_run.logger.error(e.backtrace)
180 | raise e
181 | ensure
182 | job_run.finished_at = Time.now
183 | job_run.save
184 | end
185 | end
186 |
187 | def perform(job_run, params)
188 | case typed_self.method(:perform).parameters.length
189 | when 0 then typed_self.perform
190 | when 1 then typed_self.perform(job_run)
191 | when 2 then typed_self.perform(job_run, params)
192 | end
193 | end
194 |
195 | # working around lack of polymorphism in Ohm
196 | # using type attributed to get a typed instance
197 | def typed_self
198 | @typed_self ||= Object::const_get(self.type).new(id: self.id)
199 | end
200 |
201 | end
202 |
203 | end
204 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | jobbr (2.0.0)
5 | bootstrap-sass
6 | chronic_duration
7 | coffee-rails
8 | font-awesome-rails
9 | haml
10 | jquery-rails
11 | kaminari
12 | less-rails
13 | ohm (>= 2.0.1)
14 | ohm-contrib
15 | rails (>= 4.0.0)
16 | redis
17 | require_all
18 | sass-rails (>= 4.0.2)
19 | sidekiq (>= 3.0.0)
20 | therubyracer
21 | turbolinks
22 | whenever
23 |
24 | GEM
25 | remote: http://rubygems.org/
26 | specs:
27 | actionmailer (4.1.1)
28 | actionpack (= 4.1.1)
29 | actionview (= 4.1.1)
30 | mail (~> 2.5.4)
31 | actionpack (4.1.1)
32 | actionview (= 4.1.1)
33 | activesupport (= 4.1.1)
34 | rack (~> 1.5.2)
35 | rack-test (~> 0.6.2)
36 | actionview (4.1.1)
37 | activesupport (= 4.1.1)
38 | builder (~> 3.1)
39 | erubis (~> 2.7.0)
40 | activemodel (4.1.1)
41 | activesupport (= 4.1.1)
42 | builder (~> 3.1)
43 | activerecord (4.1.1)
44 | activemodel (= 4.1.1)
45 | activesupport (= 4.1.1)
46 | arel (~> 5.0.0)
47 | activesupport (4.1.1)
48 | i18n (~> 0.6, >= 0.6.9)
49 | json (~> 1.7, >= 1.7.7)
50 | minitest (~> 5.1)
51 | thread_safe (~> 0.1)
52 | tzinfo (~> 1.1)
53 | addressable (2.3.6)
54 | arel (5.0.1.20140414130214)
55 | bootstrap-sass (3.1.1.1)
56 | sass (~> 3.2)
57 | builder (3.2.2)
58 | capybara (2.3.0)
59 | mime-types (>= 1.16)
60 | nokogiri (>= 1.3.3)
61 | rack (>= 1.0.0)
62 | rack-test (>= 0.5.4)
63 | xpath (~> 2.0)
64 | celluloid (0.15.2)
65 | timers (~> 1.1.0)
66 | chronic (0.10.2)
67 | chronic_duration (0.10.4)
68 | numerizer (~> 0.1.1)
69 | cliver (0.3.2)
70 | codeclimate-test-reporter (0.3.0)
71 | simplecov (>= 0.7.1, < 1.0.0)
72 | coffee-rails (4.0.1)
73 | coffee-script (>= 2.2.0)
74 | railties (>= 4.0.0, < 5.0)
75 | coffee-script (2.2.0)
76 | coffee-script-source
77 | execjs
78 | coffee-script-source (1.7.0)
79 | commonjs (0.2.7)
80 | connection_pool (2.0.0)
81 | diff-lcs (1.2.5)
82 | docile (1.1.5)
83 | erubis (2.7.0)
84 | execjs (2.2.0)
85 | font-awesome-rails (4.1.0.0)
86 | railties (>= 3.2, < 5.0)
87 | generator_spec (0.9.2)
88 | activesupport (>= 3.0.0)
89 | railties (>= 3.0.0)
90 | haml (4.0.5)
91 | tilt
92 | hike (1.2.3)
93 | hiredis (0.5.2)
94 | i18n (0.6.9)
95 | jquery-rails (3.1.0)
96 | railties (>= 3.0, < 5.0)
97 | thor (>= 0.14, < 2.0)
98 | json (1.8.1)
99 | kaminari (0.16.1)
100 | actionpack (>= 3.0.0)
101 | activesupport (>= 3.0.0)
102 | kgio (2.9.2)
103 | launchy (2.4.2)
104 | addressable (~> 2.3)
105 | less (2.5.1)
106 | commonjs (~> 0.2.7)
107 | less-rails (2.5.0)
108 | actionpack (>= 3.1)
109 | less (~> 2.5.0)
110 | libv8 (3.16.14.3)
111 | mail (2.5.4)
112 | mime-types (~> 1.16)
113 | treetop (~> 1.4.8)
114 | metaclass (0.0.4)
115 | mime-types (1.25.1)
116 | mini_portile (0.6.0)
117 | minitest (5.3.4)
118 | mocha (1.1.0)
119 | metaclass (~> 0.0.1)
120 | msgpack (0.5.8)
121 | multi_json (1.10.1)
122 | nido (0.0.1)
123 | nokogiri (1.6.2.1)
124 | mini_portile (= 0.6.0)
125 | numerizer (0.1.1)
126 | ohm (2.0.1)
127 | msgpack
128 | nido
129 | redic
130 | ohm-contrib (2.0.0)
131 | ohm (~> 2.0.0)
132 | poltergeist (1.5.1)
133 | capybara (~> 2.1)
134 | cliver (~> 0.3.1)
135 | multi_json (~> 1.0)
136 | websocket-driver (>= 0.2.0)
137 | polyglot (0.3.5)
138 | rack (1.5.2)
139 | rack-test (0.6.2)
140 | rack (>= 1.0)
141 | rails (4.1.1)
142 | actionmailer (= 4.1.1)
143 | actionpack (= 4.1.1)
144 | actionview (= 4.1.1)
145 | activemodel (= 4.1.1)
146 | activerecord (= 4.1.1)
147 | activesupport (= 4.1.1)
148 | bundler (>= 1.3.0, < 2.0)
149 | railties (= 4.1.1)
150 | sprockets-rails (~> 2.0)
151 | railties (4.1.1)
152 | actionpack (= 4.1.1)
153 | activesupport (= 4.1.1)
154 | rake (>= 0.8.7)
155 | thor (>= 0.18.1, < 2.0)
156 | raindrops (0.13.0)
157 | rake (10.3.2)
158 | redic (1.1.1)
159 | hiredis
160 | redis (3.0.7)
161 | redis-namespace (1.4.1)
162 | redis (~> 3.0.4)
163 | ref (1.0.5)
164 | require_all (1.3.2)
165 | rspec-collection_matchers (1.0.0)
166 | rspec-expectations (>= 2.99.0.beta1)
167 | rspec-core (2.99.0)
168 | rspec-expectations (2.99.0)
169 | diff-lcs (>= 1.1.3, < 2.0)
170 | rspec-mocks (2.99.1)
171 | rspec-rails (2.99.0)
172 | actionpack (>= 3.0)
173 | activemodel (>= 3.0)
174 | activesupport (>= 3.0)
175 | railties (>= 3.0)
176 | rspec-collection_matchers
177 | rspec-core (~> 2.99.0)
178 | rspec-expectations (~> 2.99.0)
179 | rspec-mocks (~> 2.99.0)
180 | sass (3.2.19)
181 | sass-rails (4.0.3)
182 | railties (>= 4.0.0, < 5.0)
183 | sass (~> 3.2.0)
184 | sprockets (~> 2.8, <= 2.11.0)
185 | sprockets-rails (~> 2.0)
186 | sidekiq (3.1.4)
187 | celluloid (>= 0.15.2)
188 | connection_pool (>= 2.0.0)
189 | json
190 | redis (>= 3.0.6)
191 | redis-namespace (>= 1.3.1)
192 | simplecov (0.8.2)
193 | docile (~> 1.1.0)
194 | multi_json
195 | simplecov-html (~> 0.8.0)
196 | simplecov-html (0.8.0)
197 | sprockets (2.11.0)
198 | hike (~> 1.2)
199 | multi_json (~> 1.0)
200 | rack (~> 1.0)
201 | tilt (~> 1.1, != 1.3.0)
202 | sprockets-rails (2.1.3)
203 | actionpack (>= 3.0)
204 | activesupport (>= 3.0)
205 | sprockets (~> 2.8)
206 | therubyracer (0.12.1)
207 | libv8 (~> 3.16.14.0)
208 | ref
209 | thor (0.19.1)
210 | thread_safe (0.3.4)
211 | tilt (1.4.1)
212 | timecop (0.7.1)
213 | timers (1.1.0)
214 | treetop (1.4.15)
215 | polyglot
216 | polyglot (>= 0.3.1)
217 | turbolinks (2.2.2)
218 | coffee-rails
219 | tzinfo (1.2.1)
220 | thread_safe (~> 0.1)
221 | unicorn (4.8.3)
222 | kgio (~> 2.6)
223 | rack
224 | raindrops (~> 0.7)
225 | websocket-driver (0.3.3)
226 | whenever (0.9.2)
227 | activesupport (>= 2.3.4)
228 | chronic (>= 0.6.3)
229 | xpath (2.0.0)
230 | nokogiri (~> 1.3)
231 |
232 | PLATFORMS
233 | ruby
234 |
235 | DEPENDENCIES
236 | bootstrap-sass
237 | capybara
238 | chronic_duration
239 | codeclimate-test-reporter
240 | coffee-rails
241 | font-awesome-rails
242 | generator_spec
243 | haml
244 | jobbr!
245 | jquery-rails
246 | kaminari
247 | launchy
248 | less-rails
249 | mocha
250 | ohm (>= 2.0.1)
251 | ohm-contrib
252 | poltergeist
253 | rails (>= 4.0.0)
254 | redis
255 | require_all
256 | rspec-rails (~> 2.99)
257 | sass-rails (>= 4.0.2)
258 | sidekiq
259 | therubyracer
260 | timecop
261 | turbolinks
262 | unicorn
263 | whenever
264 |
--------------------------------------------------------------------------------
/spec/dummy/README.rdoc:
--------------------------------------------------------------------------------
1 | == Welcome to Rails
2 |
3 | Rails is a web-application framework that includes everything needed to create
4 | database-backed web applications according to the Model-View-Control pattern.
5 |
6 | This pattern splits the view (also called the presentation) into "dumb"
7 | templates that are primarily responsible for inserting pre-built data in between
8 | HTML tags. The model contains the "smart" domain objects (such as Account,
9 | Product, Person, Post) that holds all the business logic and knows how to
10 | persist themselves to a database. The controller handles the incoming requests
11 | (such as Save New Account, Update Product, Show Post) by manipulating the model
12 | and directing data to the view.
13 |
14 | In Rails, the model is handled by what's called an object-relational mapping
15 | layer entitled Active Record. This layer allows you to present the data from
16 | database rows as objects and embellish these data objects with business logic
17 | methods. You can read more about Active Record in
18 | link:files/vendor/rails/activerecord/README.html.
19 |
20 | The controller and view are handled by the Action Pack, which handles both
21 | layers by its two parts: Action View and Action Controller. These two layers
22 | are bundled in a single package due to their heavy interdependence. This is
23 | unlike the relationship between the Active Record and Action Pack that is much
24 | more separate. Each of these packages can be used independently outside of
25 | Rails. You can read more about Action Pack in
26 | link:files/vendor/rails/actionpack/README.html.
27 |
28 |
29 | == Getting Started
30 |
31 | 1. At the command prompt, create a new Rails application:
32 | rails new myapp (where myapp is the application name)
33 |
34 | 2. Change directory to myapp and start the web server:
35 | cd myapp; rails server (run with --help for options)
36 |
37 | 3. Go to http://localhost:3000/ and you'll see:
38 | "Welcome aboard: You're riding Ruby on Rails!"
39 |
40 | 4. Follow the guidelines to start developing your application. You can find
41 | the following resources handy:
42 |
43 | * The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html
44 | * Ruby on Rails Tutorial Book: http://www.railstutorial.org/
45 |
46 |
47 | == Debugging Rails
48 |
49 | Sometimes your application goes wrong. Fortunately there are a lot of tools that
50 | will help you debug it and get it back on the rails.
51 |
52 | First area to check is the application log files. Have "tail -f" commands
53 | running on the server.log and development.log. Rails will automatically display
54 | debugging and runtime information to these files. Debugging info will also be
55 | shown in the browser on requests from 127.0.0.1.
56 |
57 | You can also log your own messages directly into the log file from your code
58 | using the Ruby logger class from inside your controllers. Example:
59 |
60 | class WeblogController < ActionController::Base
61 | def destroy
62 | @weblog = Weblog.find(params[:id])
63 | @weblog.destroy
64 | logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
65 | end
66 | end
67 |
68 | The result will be a message in your log file along the lines of:
69 |
70 | Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1!
71 |
72 | More information on how to use the logger is at http://www.ruby-doc.org/core/
73 |
74 | Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are
75 | several books available online as well:
76 |
77 | * Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe)
78 | * Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
79 |
80 | These two books will bring you up to speed on the Ruby language and also on
81 | programming in general.
82 |
83 |
84 | == Debugger
85 |
86 | Debugger support is available through the debugger command when you start your
87 | Mongrel or WEBrick server with --debugger. This means that you can break out of
88 | execution at any point in the code, investigate and change the model, and then,
89 | resume execution! You need to install ruby-debug to run the server in debugging
90 | mode. With gems, use sudo gem install ruby-debug. Example:
91 |
92 | class WeblogController < ActionController::Base
93 | def index
94 | @posts = Post.all
95 | debugger
96 | end
97 | end
98 |
99 | So the controller will accept the action, run the first line, then present you
100 | with a IRB prompt in the server window. Here you can do things like:
101 |
102 | >> @posts.inspect
103 | => "[#nil, "body"=>nil, "id"=>"1"}>,
105 | #"Rails", "body"=>"Only ten..", "id"=>"2"}>]"
107 | >> @posts.first.title = "hello from a debugger"
108 | => "hello from a debugger"
109 |
110 | ...and even better, you can examine how your runtime objects actually work:
111 |
112 | >> f = @posts.first
113 | => #nil, "body"=>nil, "id"=>"1"}>
114 | >> f.
115 | Display all 152 possibilities? (y or n)
116 |
117 | Finally, when you're ready to resume execution, you can enter "cont".
118 |
119 |
120 | == Console
121 |
122 | The console is a Ruby shell, which allows you to interact with your
123 | application's domain model. Here you'll have all parts of the application
124 | configured, just like it is when the application is running. You can inspect
125 | domain models, change values, and save to the database. Starting the script
126 | without arguments will launch it in the development environment.
127 |
128 | To start the console, run rails console from the application
129 | directory.
130 |
131 | Options:
132 |
133 | * Passing the -s, --sandbox argument will rollback any modifications
134 | made to the database.
135 | * Passing an environment name as an argument will load the corresponding
136 | environment. Example: rails console production.
137 |
138 | To reload your controllers and models after launching the console run
139 | reload!
140 |
141 | More information about irb can be found at:
142 | link:http://www.rubycentral.org/pickaxe/irb.html
143 |
144 |
145 | == dbconsole
146 |
147 | You can go to the command line of your database directly through rails
148 | dbconsole. You would be connected to the database with the credentials
149 | defined in database.yml. Starting the script without arguments will connect you
150 | to the development database. Passing an argument will connect you to a different
151 | database, like rails dbconsole production. Currently works for MySQL,
152 | PostgreSQL and SQLite 3.
153 |
154 | == Description of Contents
155 |
156 | The default directory structure of a generated Ruby on Rails application:
157 |
158 | |-- app
159 | | |-- assets
160 | | |-- images
161 | | |-- javascripts
162 | | `-- stylesheets
163 | | |-- controllers
164 | | |-- helpers
165 | | |-- mailers
166 | | |-- models
167 | | `-- views
168 | | `-- layouts
169 | |-- config
170 | | |-- environments
171 | | |-- initializers
172 | | `-- locales
173 | |-- db
174 | |-- doc
175 | |-- lib
176 | | `-- tasks
177 | |-- log
178 | |-- public
179 | |-- script
180 | |-- test
181 | | |-- fixtures
182 | | |-- functional
183 | | |-- integration
184 | | |-- performance
185 | | `-- unit
186 | |-- tmp
187 | | |-- cache
188 | | |-- pids
189 | | |-- sessions
190 | | `-- sockets
191 | `-- vendor
192 | |-- assets
193 | `-- stylesheets
194 | `-- plugins
195 |
196 | app
197 | Holds all the code that's specific to this particular application.
198 |
199 | app/assets
200 | Contains subdirectories for images, stylesheets, and JavaScript files.
201 |
202 | app/controllers
203 | Holds controllers that should be named like weblogs_controller.rb for
204 | automated URL mapping. All controllers should descend from
205 | ApplicationController which itself descends from ActionController::Base.
206 |
207 | app/models
208 | Holds models that should be named like post.rb. Models descend from
209 | ActiveRecord::Base by default.
210 |
211 | app/views
212 | Holds the template files for the view that should be named like
213 | weblogs/index.html.erb for the WeblogsController#index action. All views use
214 | eRuby syntax by default.
215 |
216 | app/views/layouts
217 | Holds the template files for layouts to be used with views. This models the
218 | common header/footer method of wrapping views. In your views, define a layout
219 | using the layout :default and create a file named default.html.erb.
220 | Inside default.html.erb, call <% yield %> to render the view using this
221 | layout.
222 |
223 | app/helpers
224 | Holds view helpers that should be named like weblogs_helper.rb. These are
225 | generated for you automatically when using generators for controllers.
226 | Helpers can be used to wrap functionality for your views into methods.
227 |
228 | config
229 | Configuration files for the Rails environment, the routing map, the database,
230 | and other dependencies.
231 |
232 | db
233 | Contains the database schema in schema.rb. db/migrate contains all the
234 | sequence of Migrations for your schema.
235 |
236 | doc
237 | This directory is where your application documentation will be stored when
238 | generated using rake doc:app
239 |
240 | lib
241 | Application specific libraries. Basically, any kind of custom code that
242 | doesn't belong under controllers, models, or helpers. This directory is in
243 | the load path.
244 |
245 | public
246 | The directory available for the web server. Also contains the dispatchers and the
247 | default HTML files. This should be set as the DOCUMENT_ROOT of your web
248 | server.
249 |
250 | script
251 | Helper scripts for automation and generation.
252 |
253 | test
254 | Unit and functional tests along with fixtures. When using the rails generate
255 | command, template test files will be generated for you and placed in this
256 | directory.
257 |
258 | vendor
259 | External libraries that the application depends on. Also includes the plugins
260 | subdirectory. If the app has frozen rails, those gems also go here, under
261 | vendor/rails/. This directory is in the load path.
262 |
--------------------------------------------------------------------------------