├── test ├── dummy │ ├── log │ │ └── .keep │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── .ruby-version │ ├── public │ │ ├── favicon.ico │ │ ├── apple-touch-icon.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── app │ │ ├── assets │ │ │ ├── images │ │ │ │ └── .keep │ │ │ ├── javascripts │ │ │ │ ├── channels │ │ │ │ │ └── .keep │ │ │ │ ├── cable.js │ │ │ │ └── application.js │ │ │ ├── config │ │ │ │ └── manifest.js │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── models │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── application_record.rb │ │ ├── controllers │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── application_controller.rb │ │ ├── views │ │ │ └── layouts │ │ │ │ ├── mailer.text.erb │ │ │ │ ├── mailer.html.erb │ │ │ │ └── application.html.erb │ │ ├── helpers │ │ │ └── application_helper.rb │ │ ├── jobs │ │ │ └── application_job.rb │ │ ├── channels │ │ │ └── application_cable │ │ │ │ ├── channel.rb │ │ │ │ └── connection.rb │ │ └── mailers │ │ │ └── application_mailer.rb │ ├── package.json │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ ├── rails │ │ ├── yarn │ │ ├── update │ │ └── setup │ ├── config │ │ ├── spring.rb │ │ ├── routes.rb │ │ ├── environment.rb │ │ ├── initializers │ │ │ ├── mime_types.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── application_controller_renderer.rb │ │ │ ├── cookies_serializer.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── arask.rb │ │ │ ├── assets.rb │ │ │ ├── inflections.rb │ │ │ └── content_security_policy.rb │ │ ├── cable.yml │ │ ├── boot.rb │ │ ├── application.rb │ │ ├── database.yml │ │ ├── locales │ │ │ └── en.yml │ │ ├── storage.yml │ │ ├── puma.rb │ │ └── environments │ │ │ ├── test.rb │ │ │ ├── development.rb │ │ │ └── production.rb │ ├── config.ru │ ├── Rakefile │ └── db │ │ ├── migrate │ │ └── 20180615090101_create_arask_jobs.rb │ │ └── schema.rb ├── run_first_time_test.rb ├── activejob_test.rb ├── class_reference_test.rb ├── run_all_test.rb ├── exception_block_test.rb ├── cron_test.rb ├── test_helper.rb └── saves_to_db_test.rb ├── .travis.yml ├── lib ├── arask │ ├── version.rb │ ├── railtie.rb │ ├── run_jobs.rb │ ├── initialize.rb │ ├── arask_job.rb │ └── setup.rb ├── generators │ └── arask │ │ └── install_generator.rb └── arask.rb ├── bin └── test ├── .gitignore ├── .github └── workflows │ ├── test.yml │ └── build-release.yml ├── Rakefile ├── Gemfile ├── arask.gemspec ├── MIT-LICENSE ├── README.md └── Gemfile.lock /test/dummy/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/.ruby-version: -------------------------------------------------------------------------------- 1 | 2.4.1 -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 3.0 4 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /lib/arask/version.rb: -------------------------------------------------------------------------------- 1 | module Arask 2 | VERSION = '1.2.6' 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dummy", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.expand_path("../test", __dir__) 3 | 4 | require "bundler/setup" 5 | require "rails/plugin/test" 6 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ].each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/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 | -------------------------------------------------------------------------------- /lib/arask/railtie.rb: -------------------------------------------------------------------------------- 1 | module Arask 2 | class Railtie < ::Rails::Railtie 3 | # Executes when the rails server is running 4 | server do 5 | Arask.init_jobs 6 | end if Rails::VERSION::MAJOR>=6 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | log/*.log 3 | *.gem 4 | pkg/ 5 | test/dummy/db/*.sqlite3 6 | test/dummy/db/*.sqlite3-journal 7 | test/dummy/log/*.log 8 | test/dummy/node_modules/ 9 | test/dummy/yarn-error.log 10 | test/dummy/storage/ 11 | test/dummy/tmp/ 12 | coverage/* 13 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) 6 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20180615090101_create_arask_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateAraskJobs < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :arask_jobs do |t| 4 | t.string :job 5 | t.datetime :execute_at 6 | t.string :interval 7 | end 8 | add_index :arask_jobs, :execute_at 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= csrf_meta_tags %> 6 | <%= csp_meta_tag %> 7 | 8 | <%= stylesheet_link_tag 'application', media: 'all' %> 9 | <%= javascript_include_tag 'application' %> 10 | 11 | 12 | 13 | <%= yield %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/arask/run_jobs.rb: -------------------------------------------------------------------------------- 1 | module Arask 2 | class RunJobs < ActiveJob::Base 3 | queue_as :default 4 | 5 | def perform 6 | begin 7 | AraskJob.transaction do 8 | AraskJob.where('execute_at < ?', DateTime.now).lock.each do |job| 9 | job.run 10 | end 11 | end 12 | rescue 13 | end 14 | Arask.queue_self 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/run_first_time_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Arask::Test < ActiveSupport::TestCase 4 | 5 | test 'arask honours run_first_time' do 6 | # Stop time (Yes I am God) 7 | travel_to Date.today.noon 8 | 9 | Arask.setup(true) do |arask| 10 | arask.create script: 'random', cron: '0 0 1 1 1', run_first_time: true 11 | end 12 | assert(Arask::AraskJob.first.execute_at == Time.now) 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Set up Ruby 12 | uses: ruby/setup-ruby@v1 13 | with: 14 | ruby-version: "3.1" 15 | bundler-cache: true 16 | - name: Install dependencies 17 | run: bundle install 18 | - name: Run tests 19 | run: bundle exec rake 20 | -------------------------------------------------------------------------------- /test/activejob_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ImportCurrenciesJob < ApplicationJob 4 | def perform(*args) 5 | return :i_ran 6 | end 7 | end 8 | 9 | class Arask::Test < ActiveSupport::TestCase 10 | test 'can execute ActiveJob' do 11 | Arask.setup(true) do |arask| 12 | arask.create job: 'ImportCurrenciesJob', cron: '* * * * *', run_first_time: true 13 | end 14 | assert(Arask::AraskJob.first.run == :i_ran) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/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 | -------------------------------------------------------------------------------- /test/class_reference_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ClassReferenceTest < ApplicationJob 4 | def perform(*args) 5 | return :i_ran2 6 | end 7 | end 8 | 9 | class Arask::Test < ActiveSupport::TestCase 10 | test 'can reference class instead of string' do 11 | Arask.setup(true) do |arask| 12 | arask.create job: ClassReferenceTest, cron: '* * * * *', run_first_time: true 13 | end 14 | assert(Arask::AraskJob.first.run == :i_ran2) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/generators/arask/install_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | require "rails/generators/active_record" 3 | 4 | class Arask::InstallGenerator < Rails::Generators::Base 5 | def initialize(*args, &block) 6 | super 7 | 8 | Arask::InstallGenerator.source_root File.expand_path('../../arask', __FILE__) 9 | copy_file '../../arask/initialize.rb', 'config/initializers/arask.rb' 10 | 11 | generate 'migration', 'create_arask_jobs job:string execute_at:datetime:index interval:string' 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/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 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/arask.rb: -------------------------------------------------------------------------------- 1 | Arask.setup do |arask| 2 | # Examples 3 | #arask.create script: 'puts "IM ALIVE!"', interval: :daily, run_first_time: true 4 | #arask.create script: 'Attachment.process_new', interval: 5.hours if Rails.env.production? 5 | #arask.create task: 'send:logs', cron: '0 2 * * *' # At 02:00 every day 6 | #arask.create task: 'update:cache', cron: '*/5 * * * *' # Every 5 minutes 7 | # Run rake task: 8 | #arask.create task: 'my:awesome_task', interval: :hourly 9 | # On exceptions, send email 10 | #arask.on_exception email: 'errors@example.com' 11 | end 12 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Build release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Ruby 3.1 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 3.1 19 | bundler-cache: true 20 | - run: bundle install 21 | 22 | - name: Build gem 23 | run: | 24 | gem build arask 25 | - name: Release 26 | uses: softprops/action-gh-release@v1 27 | with: 28 | files: arask-*.gem 29 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | Bundler.require(*Rails.groups) 6 | require "arask" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | # Initialize configuration defaults for originally generated Rails version. 11 | config.load_defaults 5.2 12 | 13 | # Settings in config/environments/* take precedence over those specified here. 14 | # Application configuration can go into files in config/initializers 15 | # -- all .rb files in that directory are automatically loaded after loading 16 | # the framework and any gems in your application. 17 | end 18 | end 19 | 20 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = 'rdoc' 11 | rdoc.title = 'Arask' 12 | rdoc.options << '--line-numbers' 13 | rdoc.rdoc_files.include('README.md') 14 | rdoc.rdoc_files.include('lib/**/*.rb') 15 | end 16 | 17 | require 'bundler/gem_tasks' 18 | 19 | require 'rake/testtask' 20 | 21 | Rake::TestTask.new(:test) do |t| 22 | t.libs << 'test' 23 | t.pattern = 'test/**/*_test.rb' 24 | t.verbose = false 25 | t.warning = false 26 | end 27 | 28 | task default: :test 29 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | # Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | # Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /test/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 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /test/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. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # Declare your gem's dependencies in arask.gemspec. 5 | # Bundler will treat runtime dependencies like base dependencies, and 6 | # development dependencies will be added by default to the :development group. 7 | gemspec 8 | 9 | group :test do 10 | gem 'coveralls', :require => false 11 | end 12 | 13 | # Declare any dependencies that are still in development here instead of in 14 | # your gemspec. These might include edge Rails or gems from your path or 15 | # Git. Remember to move these dependencies to your gemspec before releasing 16 | # your gem to rubygems.org. 17 | 18 | # To use a debugger 19 | # gem 'byebug', group: [:development, :test] 20 | -------------------------------------------------------------------------------- /test/run_all_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Arask::Test < ActiveSupport::TestCase 4 | test 'actually runs all jobs' do 5 | # Stop time (Yes I am God) 6 | travel_to Time.utc(2000, 4, 5, 10, 0, 5) 7 | 8 | Arask.setup(true) do |arask| 9 | arask.create script: '1+1', cron: '* * * * *' 10 | arask.create script: '1+2', cron: '* * * * *' 11 | end 12 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 5, 10, 1, 0)) 13 | # Don't traverse time. Nothing should happen 14 | Arask::RunJobs.perform_now 15 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 5, 10, 1, 0)) 16 | 17 | travel 1.minute 18 | Arask::RunJobs.perform_now 19 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 5, 10, 2, 0)) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/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 any plugin's vendor/assets/javascripts directory 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 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require rails-ujs 14 | //= require activestorage 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /test/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 any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /arask.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("lib", __dir__) 2 | 3 | # Maintain your gem's version: 4 | require "arask/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "arask" 9 | s.version = Arask::VERSION 10 | s.authors = ["Esben Damgaard"] 11 | s.email = ["ebbe@hvemder.dk"] 12 | s.homepage = "http://github.com/Ebbe/arask" 13 | s.summary = "Automatic RAils taSKs (with minimal setup)" 14 | s.description = "With minimal setup, be able to regularly run tasks in Rails. Either user cron syntax or simply define an interval." 15 | s.license = "MIT" 16 | 17 | s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 18 | 19 | s.add_dependency "rails", ">= 5.0" 20 | s.add_dependency "fugit", "~> 1.1" 21 | 22 | s.add_development_dependency "sqlite3" 23 | end 24 | -------------------------------------------------------------------------------- /test/exception_block_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Arask::Test < ActiveSupport::TestCase 4 | test 'can execute an exception block' do 5 | old_stdout = $stdout 6 | $stdout = StringIO.new 7 | 8 | exception_text = nil 9 | exception_job_text = nil 10 | Arask.setup(true) do |arask| 11 | arask.create script: 'puts "no error"', cron: '* * * * *', run_first_time: true 12 | arask.create script: 'raise "Message!"', cron: '* * * * *', run_first_time: true 13 | arask.create script: 'puts "no error"', cron: '* * * * *', run_first_time: true 14 | 15 | arask.on_exception do |exception, job| 16 | exception_text = exception.to_s 17 | exception_job_text = job.job 18 | end 19 | end 20 | Arask::RunJobs.perform_now 21 | assert(exception_text == "Message!") 22 | assert(exception_job_text == 'raise "Message!"') 23 | 24 | $stdout = old_stdout 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/dummy/bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /test/cron_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Arask::Test < ActiveSupport::TestCase 4 | 5 | test 'can read normal cron syntax' do 6 | # Stop time (Yes I am God) 7 | travel_to Time.utc(2000, 4, 5, 10, 0, 5) 8 | 9 | Arask.setup(true) do |arask| 10 | arask.create script: 'random"', cron: '* * * * *' 11 | end 12 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 5, 10, 1, 0)) 13 | 14 | Arask.setup(true) do |arask| 15 | arask.create script: 'random"', cron: '0 * * * *' 16 | end 17 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 5, 11, 0, 0)) 18 | 19 | Arask.setup(true) do |arask| 20 | arask.create script: 'random"', cron: '0 2 * * *' 21 | end 22 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 6, 2, 0, 0)) 23 | 24 | Arask.setup(true) do |arask| 25 | arask.create script: 'random"', cron: '*/5 * * * *' 26 | end 27 | assert(Arask::AraskJob.first.execute_at == Time.utc(2000, 4, 5, 10, 5, 0)) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2018_06_15_090101) do 14 | 15 | create_table "arask_jobs", force: :cascade do |t| 16 | t.string "job" 17 | t.datetime "execute_at" 18 | t.string "interval" 19 | t.index ["execute_at"], name: "index_arask_jobs_on_execute_at" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:setup' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Esben Damgaard 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 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | # require 'coveralls' 5 | # Coveralls.wear! 6 | 7 | require_relative "../test/dummy/config/environment" 8 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)] 9 | require "rails/test_help" 10 | 11 | # Filter out Minitest backtrace while allowing backtrace from other libraries 12 | # to be shown. 13 | Minitest.backtrace_filter = Minitest::BacktraceFilter.new 14 | 15 | require "rails/test_unit/reporter" 16 | Rails::TestUnitReporter.executable = 'bin/test' 17 | 18 | # Load fixtures from the engine 19 | if ActiveSupport::TestCase.respond_to?(:fixture_path=) 20 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) 21 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path 22 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" 23 | ActiveSupport::TestCase.fixtures :all 24 | end 25 | 26 | 27 | # Setup db 28 | ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:" 29 | load File.dirname(__FILE__) + '/dummy/db/schema.rb' 30 | -------------------------------------------------------------------------------- /lib/arask/initialize.rb: -------------------------------------------------------------------------------- 1 | Arask.setup do |arask| 2 | ## Examples 3 | 4 | # Rake tasks with cron syntax 5 | #arask.create task: 'send:logs', cron: '0 2 * * *' # At 02:00 every day 6 | #arask.create task: 'update:cache', cron: '*/5 * * * *' # Every 5 minutes 7 | 8 | # Scripts with interval (when time of day or month etc doesn't matter) 9 | #arask.create script: 'puts "IM ALIVE!"', interval: :daily 10 | #arask.create task: 'my:awesome_task', interval: :hourly 11 | #arask.create task: 'my:awesome_task', interval: 3.minutes 12 | 13 | # Run an ActiveJob. 14 | #arask.create job: 'ImportCurrenciesJob', interval: 1.month 15 | 16 | # Only run on production 17 | #arask.create script: 'Attachment.process_new', interval: 5.hours if Rails.env.production? 18 | 19 | # Run first time. If the job didn't exist already when starting rails, run it: 20 | #arask.create script: 'Attachment.process_new', interval: 5.hours, run_first_time: true 21 | 22 | # On exceptions, send email with details 23 | #arask.on_exception email: 'errors@example.com' 24 | 25 | # Run code on exceptions 26 | #arask.on_exception do |exception, arask_job| 27 | # MyExceptionHandler.new(exception) 28 | #end 29 | end 30 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Report CSP violations to a specified URI 23 | # For further information see the following documentation: 24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # Rails.application.config.content_security_policy_report_only = true 26 | -------------------------------------------------------------------------------- /test/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /test/saves_to_db_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Arask::Test < ActiveSupport::TestCase 4 | include ActiveJob::TestHelper 5 | 6 | test "truth" do 7 | assert_kind_of Module, Arask 8 | end 9 | 10 | test 'sets correct execute_at and queues the next job' do 11 | assert(Arask.respond_to? :setup) 12 | 13 | assert_no_enqueued_jobs 14 | 15 | # Stop time (Yes I am God) 16 | travel_to Time.current 17 | 18 | 19 | assert_enqueued_with at: 2.minutes.from_now do 20 | Arask.setup(true) do |arask| 21 | arask.create script: 'random', interval: 2.minutes 22 | end 23 | assert(Arask::AraskJob.first.execute_at == 2.minutes.from_now) 24 | assert_enqueued_jobs 1 25 | end 26 | 27 | Arask.setup(true) do |arask| 28 | arask.create script: 'random2"', interval: 10.hours 29 | end 30 | assert(Arask::AraskJob.first.execute_at == 10.hours.from_now) 31 | 32 | Arask.setup(true) do |arask| 33 | arask.create script: 'random3', interval: 5.days 34 | end 35 | assert(Arask::AraskJob.first.execute_at == 5.days.from_now) 36 | end 37 | 38 | test 'maximum wait time is 1 day' do 39 | travel_to Time.current 40 | assert_enqueued_with at: 1.day.from_now do 41 | Arask.setup(true) do |arask| 42 | arask.create script: 'random"', interval: 25.hours 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/arask/arask_job.rb: -------------------------------------------------------------------------------- 1 | module Arask 2 | require 'rake' 3 | 4 | class AraskJob < ActiveRecord::Base 5 | after_create :calculate_new_execute_at 6 | 7 | def run 8 | calculate_new_execute_at 9 | begin 10 | if self.job.start_with?('Rake::Task') 11 | Rake.load_rakefile Rails.root.join('Rakefile') unless Rake::Task.task_defined?(self.job[12..-3]) 12 | eval(self.job + '.invoke') 13 | eval(self.job + '.reenable') 14 | else 15 | eval(self.job) 16 | end 17 | rescue Exception => e 18 | puts 'Arask: Job failed' 19 | p self 20 | puts e.message 21 | 22 | unless Arask.exception_block.nil? 23 | Arask.exception_block.call(e, self) 24 | end 25 | unless Arask.exception_email.nil? 26 | ActionMailer::Base.mail( 27 | from: Arask.exception_email_from, 28 | to: Arask.exception_email, 29 | subject: "Arask failed", 30 | body: "Job: #{self.inspect}\n\nException:\n#{e.message}" 31 | ).deliver 32 | end 33 | end 34 | end 35 | 36 | private 37 | def calculate_new_execute_at 38 | if self.interval.start_with?('cron: ') 39 | cron = Fugit::Cron.parse(self.interval.split(' ', 2)[1]) 40 | self.update_attribute(:execute_at, cron.next_time.to_t) 41 | else 42 | self.update_attribute(:execute_at, self.interval.to_i.seconds.from_now) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. 30 | # 31 | # preload_app! 32 | 33 | # Allow puma to be restarted by `rails restart` command. 34 | plugin :tmp_restart 35 | -------------------------------------------------------------------------------- /lib/arask.rb: -------------------------------------------------------------------------------- 1 | require "arask/railtie" 2 | require "arask/arask_job" 3 | require "arask/run_jobs" 4 | require "arask/setup" 5 | require 'fugit' # To parse cron 6 | 7 | module Arask 8 | class << self; attr_accessor :jobs_touched, :exception_email, :exception_email_from, :exception_block, :jobs_block; end 9 | 10 | def self.setup(force_run = false, &block) 11 | Arask.jobs_block = block 12 | if Rails::VERSION::MAJOR>=6 13 | # Make sure we only run setup now if we are testing. 14 | # Else we would run them at every cli execution. 15 | # railtie.rb inits the jobs when the server is ready 16 | Arask.init_jobs if force_run 17 | else # Rails is less than 6 18 | 19 | # Make sure we only run setup if Rails is actually run as a server or testing. 20 | return unless defined?(Rails::Server) or force_run 21 | # If we don't wait and rails is setup to use another language, ActiveJob 22 | # saves what is now (usually :en) and reloads that when trying to run the 23 | # job. Renderering an I18n error of unsupported language. 24 | ActiveSupport.on_load :after_initialize, yield: true do 25 | Arask.init_jobs 26 | end 27 | end 28 | end 29 | 30 | def self.init_jobs 31 | Arask.jobs_touched = [] 32 | Arask.jobs_block.call(Setup) 33 | Arask.jobs_block = nil 34 | begin 35 | AraskJob.all.where.not(id: Arask.jobs_touched).delete_all 36 | Arask.jobs_touched = nil 37 | Arask.queue_self 38 | rescue 39 | end 40 | end 41 | 42 | def self.queue_self 43 | begin 44 | next_job_run = AraskJob.order(execute_at: :asc).first.try(:execute_at) 45 | # At least check database for jobs every day 46 | next_job_run = 1.day.from_now if next_job_run.nil? or (next_job_run - DateTime.now)/60/60 > 24 47 | RunJobs.set(wait_until: next_job_run).perform_later 48 | rescue 49 | end 50 | end 51 | 52 | end 53 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.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 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Store uploaded files on the local file system in a temporary directory 32 | config.active_storage.service = :test 33 | 34 | config.action_mailer.perform_caching = false 35 | 36 | # Tell Action Mailer not to deliver emails to the real world. 37 | # The :test delivery method accumulates sent emails in the 38 | # ActionMailer::Base.deliveries array. 39 | config.action_mailer.delivery_method = :test 40 | 41 | # Print deprecation notices to the stderr. 42 | config.active_support.deprecation = :stderr 43 | 44 | # Raises error for missing translations 45 | # config.action_view.raise_on_missing_translations = true 46 | end 47 | -------------------------------------------------------------------------------- /lib/arask/setup.rb: -------------------------------------------------------------------------------- 1 | module Arask 2 | class Setup 3 | def self.on_exception(email: nil, from: 'robot@server', &block) 4 | Arask.exception_email = email unless email.nil? 5 | Arask.exception_email_from = from unless from.nil? 6 | puts "Arask could not parse parameter for on_exception!" unless email.nil? or email.class == String 7 | if block_given? 8 | Arask.exception_block = block 9 | end 10 | end 11 | 12 | def self.create(script: nil, task: nil, job: nil, interval: nil, cron: nil, run_first_time: false) 13 | interval = parse_interval_or_cron(interval, cron) 14 | if interval.nil? 15 | puts 'Arask: You did not specify neither cron: nor interval:! When should the task run?' 16 | return 17 | end 18 | unless task.nil? 19 | script = "Rake::Task['#{task}']" 20 | end 21 | unless job.nil? 22 | script = "#{job}.perform_now" 23 | end 24 | if script.nil? 25 | puts 'Arask: You did not specify a script or task to run!' 26 | return 27 | end 28 | begin 29 | job = AraskJob.where(job: script, interval: interval).first 30 | if job 31 | Arask.jobs_touched << job.id 32 | else 33 | job = AraskJob.create(job: script, interval: interval) 34 | Arask.jobs_touched << job.id 35 | if run_first_time===true 36 | job.update_attribute(:execute_at, Time.now) 37 | end 38 | end 39 | rescue ActiveRecord::StatementInvalid => e 40 | puts 'Could not create arask job! Looks like the database is not migrated? Remember to run `rails generate arask:install` and `rails db:migrate`' 41 | end 42 | end 43 | 44 | private 45 | def self.parse_interval_or_cron(interval, cron) 46 | unless interval.nil? 47 | case interval 48 | when :hourly 49 | interval = 1.hour 50 | when :daily 51 | interval = 1.day 52 | when :monthly 53 | interval = 1.month 54 | end 55 | interval = interval.to_s.to_i 56 | end 57 | unless cron.nil? 58 | interval = 'cron: ' + cron 59 | end 60 | return interval 61 | end 62 | 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.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 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | # Run rails dev:cache to toggle caching. 17 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 18 | config.action_controller.perform_caching = true 19 | 20 | config.cache_store = :memory_store 21 | config.public_file_server.headers = { 22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 23 | } 24 | else 25 | config.action_controller.perform_caching = false 26 | 27 | config.cache_store = :null_store 28 | end 29 | 30 | # Store uploaded files on the local file system (see config/storage.yml for options) 31 | config.active_storage.service = :local 32 | 33 | # Don't care if the mailer can't send. 34 | config.action_mailer.raise_delivery_errors = false 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Print deprecation notices to the Rails logger. 39 | config.active_support.deprecation = :log 40 | 41 | # Raise an error on page load if there are pending migrations. 42 | config.active_record.migration_error = :page_load 43 | 44 | # Highlight code that triggered database queries in logs. 45 | config.active_record.verbose_query_logs = true 46 | 47 | # Debug mode disables concatenation and preprocessing of assets. 48 | # This option may cause significant delays in view rendering with a large 49 | # number of complex assets. 50 | config.assets.debug = true 51 | 52 | # Suppress logger output for asset requests. 53 | config.assets.quiet = true 54 | 55 | # Raises error for missing translations 56 | # config.action_view.raise_on_missing_translations = true 57 | 58 | # Use an evented file watcher to asynchronously detect changes in source code, 59 | # routes, locales, etc. This feature depends on the listen gem. 60 | # config.file_watcher = ActiveSupport::EventedFileUpdateChecker 61 | end 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arask 2 | 3 | [![Gem Version](https://badge.fury.io/rb/arask.svg)](https://badge.fury.io/rb/arask) 4 | [![Coverage Status](https://coveralls.io/repos/github/Ebbe/arask/badge.svg?branch=master)](https://coveralls.io/github/Ebbe/arask?branch=master) 5 | 6 | Automatic RAils taSKs (with minimal setup). 7 | 8 | No need to setup anything outside of Rails. If Rails is running, so is Arask. If Rails has been stopped, the next time rails starts, Arask will go through overdue jobs and perform them. 9 | 10 | Use cron syntax or simply define the interval. 11 | 12 | ## Installation 13 | 14 | Add this line to your application's Gemfile: 15 | 16 | ```ruby 17 | gem 'arask' 18 | ``` 19 | 20 | Execute: 21 | 22 | ```bash 23 | $ bundle install 24 | $ rails generate arask:install 25 | $ rails db:migrate 26 | ``` 27 | 28 | Setup your tasks in config/initializers/arask.rb. Initially it looks [like this](lib/arask/initialize.rb). 29 | 30 | ## Usage 31 | 32 | After installation, you can edit config/initializers/arask.rb with your tasks. 33 | 34 | ### Examples of jobs in the initializer 35 | 36 | ```ruby 37 | # Rake tasks with cron syntax 38 | arask.create task: 'send:logs', cron: '0 2 * * *' # At 02:00 every day 39 | arask.create task: 'update:cache', cron: '*/5 * * * *' # Every 5 minutes 40 | 41 | # Scripts with interval (when time of day or month etc doesn't matter) 42 | arask.create script: 'puts "IM ALIVE!"', interval: :daily 43 | arask.create task: 'my:awesome_task', interval: :hourly 44 | arask.create task: 'my:awesome_task', interval: 3.minutes 45 | 46 | # Run an ActiveJob. 47 | arask.create job: 'ImportCurrenciesJob', interval: 1.month 48 | 49 | # Only run on production 50 | arask.create script: 'Attachment.process_new', interval: 5.hours if Rails.env.production? 51 | 52 | # Run first time. If the job didn't exist already when starting rails, run it: 53 | arask.create script: 'Attachment.process_new', interval: 5.hours, run_first_time: true 54 | 55 | # On exceptions, send email with details 56 | arask.on_exception email: 'errors@example.com' 57 | 58 | # Run code on exceptions 59 | arask.on_exception do |exception, arask_job| 60 | MyExceptionHandler.new(exception) 61 | end 62 | ``` 63 | 64 | ### About cron 65 | 66 | Arask uses [fugit](https://github.com/floraison/fugit) to parse cron and get next execution time. It follows normal cron syntax. You can test your cron at https://crontab.guru/. 67 | 68 | Not supported is `@reboot`. 69 | 70 | ### About interval 71 | 72 | The interval starts when the task has started running. If a task with the interval `:hourly` is run at 08:37PM, then it will run the next time at 09:37PM. 73 | 74 | ## Todos 75 | 76 | - Have a "try again" feature. For instance `arask.create script: 'raise "I failed"', interval: :daily, fail_retry: 5.minutes, retry_at_most: 2` 77 | - Be able to specify line and number that failed for an exception: 78 | 79 | ```ruby 80 | file,line,_ = caller.first.split(':') 81 | fileline = File.readlines(file)[line.to_i - 1].strip 82 | ``` 83 | 84 | ## Special environments 85 | 86 | ### Heroku 87 | 88 | Nothing special to setup. But if you use a hobby dyno and it falls to sleep, so will Arask. As soon as the dyno wakes up, Arask will run any pending jobs. 89 | 90 | ### Docker 91 | 92 | Nothing special to setup. 93 | 94 | ## Caveats 95 | 96 | If you reload a database dump, your jobs could be run again. 97 | 98 | ## Contributing 99 | 100 | Please use https://github.com/Ebbe/arask 101 | 102 | ## Running tests 103 | 104 | ```bash 105 | $ bundle install 106 | $ bundle exec rake test 107 | ``` 108 | 109 | ## License 110 | 111 | The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). 112 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.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 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 18 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 19 | # config.require_master_key = true 20 | 21 | # Disable serving static files from the `/public` folder by default since 22 | # Apache or NGINX already handles this. 23 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 24 | 25 | # Compress JavaScripts and CSS. 26 | config.assets.js_compressor = :uglifier 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 35 | # config.action_controller.asset_host = 'http://assets.example.com' 36 | 37 | # Specifies the header that your server uses for sending files. 38 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 39 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 40 | 41 | # Store uploaded files on the local file system (see config/storage.yml for options) 42 | config.active_storage.service = :local 43 | 44 | # Mount Action Cable outside main process or domain 45 | # config.action_cable.mount_path = nil 46 | # config.action_cable.url = 'wss://example.com/cable' 47 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 48 | 49 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 50 | # config.force_ssl = true 51 | 52 | # Use the lowest log level to ensure availability of diagnostic information 53 | # when problems arise. 54 | config.log_level = :debug 55 | 56 | # Prepend all log lines with the following tags. 57 | config.log_tags = [ :request_id ] 58 | 59 | # Use a different cache store in production. 60 | # config.cache_store = :mem_cache_store 61 | 62 | # Use a real queuing backend for Active Job (and separate queues per environment) 63 | # config.active_job.queue_adapter = :resque 64 | # config.active_job.queue_name_prefix = "dummy_#{Rails.env}" 65 | 66 | config.action_mailer.perform_caching = false 67 | 68 | # Ignore bad email addresses and do not raise email delivery errors. 69 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 70 | # config.action_mailer.raise_delivery_errors = false 71 | 72 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 73 | # the I18n.default_locale when a translation cannot be found). 74 | config.i18n.fallbacks = true 75 | 76 | # Send deprecation notices to registered listeners. 77 | config.active_support.deprecation = :notify 78 | 79 | # Use default logging formatter so that PID and timestamp are not suppressed. 80 | config.log_formatter = ::Logger::Formatter.new 81 | 82 | # Use a different logger for distributed setups. 83 | # require 'syslog/logger' 84 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 85 | 86 | if ENV["RAILS_LOG_TO_STDOUT"].present? 87 | logger = ActiveSupport::Logger.new(STDOUT) 88 | logger.formatter = config.log_formatter 89 | config.logger = ActiveSupport::TaggedLogging.new(logger) 90 | end 91 | 92 | # Do not dump schema after migrations. 93 | config.active_record.dump_schema_after_migration = false 94 | end 95 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | arask (1.2.6) 5 | fugit (~> 1.1) 6 | rails (>= 5.0) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | actioncable (7.1.5.2) 12 | actionpack (= 7.1.5.2) 13 | activesupport (= 7.1.5.2) 14 | nio4r (~> 2.0) 15 | websocket-driver (>= 0.6.1) 16 | zeitwerk (~> 2.6) 17 | actionmailbox (7.1.5.2) 18 | actionpack (= 7.1.5.2) 19 | activejob (= 7.1.5.2) 20 | activerecord (= 7.1.5.2) 21 | activestorage (= 7.1.5.2) 22 | activesupport (= 7.1.5.2) 23 | mail (>= 2.7.1) 24 | net-imap 25 | net-pop 26 | net-smtp 27 | actionmailer (7.1.5.2) 28 | actionpack (= 7.1.5.2) 29 | actionview (= 7.1.5.2) 30 | activejob (= 7.1.5.2) 31 | activesupport (= 7.1.5.2) 32 | mail (~> 2.5, >= 2.5.4) 33 | net-imap 34 | net-pop 35 | net-smtp 36 | rails-dom-testing (~> 2.2) 37 | actionpack (7.1.5.2) 38 | actionview (= 7.1.5.2) 39 | activesupport (= 7.1.5.2) 40 | nokogiri (>= 1.8.5) 41 | racc 42 | rack (>= 2.2.4) 43 | rack-session (>= 1.0.1) 44 | rack-test (>= 0.6.3) 45 | rails-dom-testing (~> 2.2) 46 | rails-html-sanitizer (~> 1.6) 47 | actiontext (7.1.5.2) 48 | actionpack (= 7.1.5.2) 49 | activerecord (= 7.1.5.2) 50 | activestorage (= 7.1.5.2) 51 | activesupport (= 7.1.5.2) 52 | globalid (>= 0.6.0) 53 | nokogiri (>= 1.8.5) 54 | actionview (7.1.5.2) 55 | activesupport (= 7.1.5.2) 56 | builder (~> 3.1) 57 | erubi (~> 1.11) 58 | rails-dom-testing (~> 2.2) 59 | rails-html-sanitizer (~> 1.6) 60 | activejob (7.1.5.2) 61 | activesupport (= 7.1.5.2) 62 | globalid (>= 0.3.6) 63 | activemodel (7.1.5.2) 64 | activesupport (= 7.1.5.2) 65 | activerecord (7.1.5.2) 66 | activemodel (= 7.1.5.2) 67 | activesupport (= 7.1.5.2) 68 | timeout (>= 0.4.0) 69 | activestorage (7.1.5.2) 70 | actionpack (= 7.1.5.2) 71 | activejob (= 7.1.5.2) 72 | activerecord (= 7.1.5.2) 73 | activesupport (= 7.1.5.2) 74 | marcel (~> 1.0) 75 | activesupport (7.1.5.2) 76 | base64 77 | benchmark (>= 0.3) 78 | bigdecimal 79 | concurrent-ruby (~> 1.0, >= 1.0.2) 80 | connection_pool (>= 2.2.5) 81 | drb 82 | i18n (>= 1.6, < 2) 83 | logger (>= 1.4.2) 84 | minitest (>= 5.1) 85 | mutex_m 86 | securerandom (>= 0.3) 87 | tzinfo (~> 2.0) 88 | base64 (0.3.0) 89 | benchmark (0.4.1) 90 | bigdecimal (3.2.2) 91 | builder (3.3.0) 92 | concurrent-ruby (1.3.5) 93 | connection_pool (2.5.3) 94 | coveralls (0.8.23) 95 | json (>= 1.8, < 3) 96 | simplecov (~> 0.16.1) 97 | term-ansicolor (~> 1.3) 98 | thor (>= 0.19.4, < 2.0) 99 | tins (~> 1.6) 100 | crass (1.0.6) 101 | date (3.4.1) 102 | docile (1.3.5) 103 | drb (2.2.3) 104 | erubi (1.13.0) 105 | et-orbi (1.2.11) 106 | tzinfo 107 | fugit (1.11.1) 108 | et-orbi (~> 1, >= 1.2.11) 109 | raabro (~> 1.4) 110 | globalid (1.2.1) 111 | activesupport (>= 6.1) 112 | i18n (1.14.7) 113 | concurrent-ruby (~> 1.0) 114 | io-console (0.7.2) 115 | irb (1.11.2) 116 | rdoc 117 | reline (>= 0.4.2) 118 | json (2.5.1) 119 | logger (1.7.0) 120 | loofah (2.23.1) 121 | crass (~> 1.0.2) 122 | nokogiri (>= 1.12.0) 123 | mail (2.8.1) 124 | mini_mime (>= 0.1.1) 125 | net-imap 126 | net-pop 127 | net-smtp 128 | marcel (1.0.4) 129 | mini_mime (1.1.2) 130 | mini_portile2 (2.8.9) 131 | minitest (5.25.5) 132 | mutex_m (0.3.0) 133 | net-imap (0.3.9) 134 | date 135 | net-protocol 136 | net-pop (0.1.2) 137 | net-protocol 138 | net-protocol (0.2.2) 139 | timeout 140 | net-smtp (0.3.3) 141 | net-protocol 142 | nio4r (2.5.8) 143 | nokogiri (1.18.9) 144 | mini_portile2 (~> 2.8.2) 145 | racc (~> 1.4) 146 | psych (5.1.2) 147 | stringio 148 | raabro (1.4.0) 149 | racc (1.8.1) 150 | rack (3.1.18) 151 | rack-session (2.1.1) 152 | base64 (>= 0.1.0) 153 | rack (>= 3.0.0) 154 | rack-test (2.1.0) 155 | rack (>= 1.3) 156 | rackup (2.1.0) 157 | rack (>= 3) 158 | webrick (~> 1.8) 159 | rails (7.1.5.2) 160 | actioncable (= 7.1.5.2) 161 | actionmailbox (= 7.1.5.2) 162 | actionmailer (= 7.1.5.2) 163 | actionpack (= 7.1.5.2) 164 | actiontext (= 7.1.5.2) 165 | actionview (= 7.1.5.2) 166 | activejob (= 7.1.5.2) 167 | activemodel (= 7.1.5.2) 168 | activerecord (= 7.1.5.2) 169 | activestorage (= 7.1.5.2) 170 | activesupport (= 7.1.5.2) 171 | bundler (>= 1.15.0) 172 | railties (= 7.1.5.2) 173 | rails-dom-testing (2.2.0) 174 | activesupport (>= 5.0.0) 175 | minitest 176 | nokogiri (>= 1.6) 177 | rails-html-sanitizer (1.6.2) 178 | loofah (~> 2.21) 179 | nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) 180 | railties (7.1.5.2) 181 | actionpack (= 7.1.5.2) 182 | activesupport (= 7.1.5.2) 183 | irb 184 | rackup (>= 1.0.0) 185 | rake (>= 12.2) 186 | thor (~> 1.0, >= 1.2.2) 187 | zeitwerk (~> 2.6) 188 | rake (13.0.6) 189 | rdoc (6.6.3.1) 190 | psych (>= 4.0.0) 191 | reline (0.4.3) 192 | io-console (~> 0.5) 193 | securerandom (0.4.1) 194 | simplecov (0.16.1) 195 | docile (~> 1.1) 196 | json (>= 1.8, < 3) 197 | simplecov-html (~> 0.10.0) 198 | simplecov-html (0.10.2) 199 | sqlite3 (1.4.2) 200 | stringio (3.1.0) 201 | sync (0.5.0) 202 | term-ansicolor (1.7.1) 203 | tins (~> 1.0) 204 | thor (1.4.0) 205 | timeout (0.4.3) 206 | tins (1.28.0) 207 | sync 208 | tzinfo (2.0.6) 209 | concurrent-ruby (~> 1.0) 210 | webrick (1.8.2) 211 | websocket-driver (0.7.5) 212 | websocket-extensions (>= 0.1.0) 213 | websocket-extensions (0.1.5) 214 | zeitwerk (2.6.7) 215 | 216 | PLATFORMS 217 | ruby 218 | 219 | DEPENDENCIES 220 | arask! 221 | coveralls 222 | sqlite3 223 | 224 | BUNDLED WITH 225 | 2.2.15 226 | --------------------------------------------------------------------------------