diff --git a/app/controllers/maintenance_tasks/runs_controller.rb b/app/controllers/maintenance_tasks/runs_controller.rb index f1522819..3747262e 100644 --- a/app/controllers/maintenance_tasks/runs_controller.rb +++ b/app/controllers/maintenance_tasks/runs_controller.rb @@ -8,7 +8,7 @@ module MaintenanceTasks class RunsController < ApplicationController # Renders the /maintenance_tasks page, displaying available tasks to users. def index - @available_tasks = Task.descendants + @available_tasks = Task.available_tasks end # POST /maintenance_tasks/runs diff --git a/app/jobs/maintenance_tasks/task.rb b/app/jobs/maintenance_tasks/task.rb index 0a947c8d..3b3a8aab 100644 --- a/app/jobs/maintenance_tasks/task.rb +++ b/app/jobs/maintenance_tasks/task.rb @@ -8,6 +8,18 @@ class Task < ActiveJob::Base extend ActiveSupport::DescendantsTracker class << self + # Controls the value of abstract_class, which indicates whether the class + # is abstract or not. Abstract classes are excluded from the list of + # available_tasks. + # + # @return [Boolean] the value of abstract_class + attr_accessor :abstract_class + + # @return [Boolean] whether or not the class is abstract + def abstract_class? + defined?(@abstract_class) && @abstract_class == true + end + # Given the name of a Task, returns the Task subclass. Returns nil if # there's no task with that name. def named(name) @@ -16,12 +28,13 @@ def named(name) nil end - # Returns a list of classes that inherit from the Task superclass. + # Returns a list of concrete classes that inherit from + # the Task superclass. # # @return [Array] the list of classes. - def descendants + def available_tasks load_constants - super + descendants.reject(&:abstract_class?) end private diff --git a/lib/generators/maintenance_tasks/install_generator.rb b/lib/generators/maintenance_tasks/install_generator.rb new file mode 100644 index 00000000..114b0adf --- /dev/null +++ b/lib/generators/maintenance_tasks/install_generator.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +module MaintenanceTasks + # Generator used to set up the engine in the host application. + # It handles mounting the engine, installing migrations + # and creating some required files. + class InstallGenerator < Rails::Generators::Base + source_root File.expand_path('templates', __dir__) + + # Mounts the engine in the host application's config/routes.rb + def mount_engine + route("mount MaintenanceTasks::Engine => '/maintenance_tasks'") + end + + # Copies engine migrations to host application and migrates the database + def install_migrations + rake('maintenance_tasks:install:migrations') + rake('db:migrate') + end + + # Creates an initializer file for the engine in the host application + def create_initializer + template( + 'maintenance_tasks.rb', + 'config/initializers/maintenance_tasks.rb' + ) + end + + # Creates ApplicationTask class for task classes to subclass + def create_application_task + template( + 'application_task.rb', + 'app/jobs/maintenance/application_task.rb' + ) + end + end +end diff --git a/lib/generators/maintenance_tasks/templates/application_task.rb.tt b/lib/generators/maintenance_tasks/templates/application_task.rb.tt new file mode 100644 index 00000000..e04c71ed --- /dev/null +++ b/lib/generators/maintenance_tasks/templates/application_task.rb.tt @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module Maintenance + class ApplicationTask < MaintenanceTasks::Task + self.abstract_class = true + end +end diff --git a/lib/generators/maintenance_tasks/templates/maintenance_tasks.rb.tt b/lib/generators/maintenance_tasks/templates/maintenance_tasks.rb.tt new file mode 100644 index 00000000..cf5c2e8c --- /dev/null +++ b/lib/generators/maintenance_tasks/templates/maintenance_tasks.rb.tt @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Custom configuration for the tasks namespace can be defined here, ie. +# MaintenanceTasks.tasks_module = 'Maintenance' +<% unless Rails.autoloaders.zeitwerk_enabled? -%> + +# Your application does not use Zeitwerk, so task dependencies must be +# manually required. This block can be removed if zeitwerk mode is enabled. +# For more information on Zeitwerk, see https://guides.rubyonrails.org/autoloading_and_reloading_constants.html +Rails.application.config.to_prepare do + Dir["#{Rails.root}/app/jobs/maintenance/*.rb"].each do |file| + require_dependency(file) + end +end +<% end -%> diff --git a/test/dummy/app/jobs/maintenance/application_task.rb b/test/dummy/app/jobs/maintenance/application_task.rb new file mode 100644 index 00000000..e04c71ed --- /dev/null +++ b/test/dummy/app/jobs/maintenance/application_task.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module Maintenance + class ApplicationTask < MaintenanceTasks::Task + self.abstract_class = true + end +end diff --git a/test/dummy/config/initializers/maintenance_tasks.rb b/test/dummy/config/initializers/maintenance_tasks.rb index 992ff78d..990b5a64 100644 --- a/test/dummy/config/initializers/maintenance_tasks.rb +++ b/test/dummy/config/initializers/maintenance_tasks.rb @@ -2,10 +2,3 @@ # Custom configuration for the tasks namespace can be defined here, ie. # MaintenanceTasks.tasks_module = 'Maintenance' - -# If autoloader is not Zeitwerk, tasks must be manually required -# Rails.application.config.to_prepare do -# Dir["#{Rails.root}/app/jobs/maintenance/*.rb"].each do |file| -# require_dependency(file) -# end -# end diff --git a/test/dummy/db/schema.rb b/test/dummy/db/schema.rb index b9cc63d9..a3146306 100644 --- a/test/dummy/db/schema.rb +++ b/test/dummy/db/schema.rb @@ -32,5 +32,4 @@ t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end - end diff --git a/test/jobs/maintenance_tasks/task_test.rb b/test/jobs/maintenance_tasks/task_test.rb index 30f5c8cf..42485a55 100644 --- a/test/jobs/maintenance_tasks/task_test.rb +++ b/test/jobs/maintenance_tasks/task_test.rb @@ -3,9 +3,9 @@ module MaintenanceTasks class TaskTest < ActiveJob::TestCase - test '.descendants returns list of tasks that inherit from the Task superclass' do - expected_tasks = [Maintenance::UpdatePostsTask] - assert_equal expected_tasks, MaintenanceTasks::Task.descendants + test '.available_tasks returns list of tasks that inherit from the Task superclass' do + expected = ['Maintenance::UpdatePostsTask'] + assert_equal expected, MaintenanceTasks::Task.available_tasks.map(&:name) end test '.named returns the task based on its name' do diff --git a/test/lib/generators/maintenance_tasks/install_generator_test.rb b/test/lib/generators/maintenance_tasks/install_generator_test.rb new file mode 100644 index 00000000..ef1383dd --- /dev/null +++ b/test/lib/generators/maintenance_tasks/install_generator_test.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true +require 'test_helper' +require 'generators/maintenance_tasks/install_generator' + +module MaintenanceTasks + class InstallGeneratorTest < Rails::Generators::TestCase + tests InstallGenerator + SAMPLE_APP_PATH = Engine.root.join('tmp/sample_app') + destination SAMPLE_APP_PATH + setup :prepare_destination + + def setup + super + setup_sample_app + end + + def teardown + FileUtils.rm_rf(SAMPLE_APP_PATH) + end + + test 'generator mounts engine, runs migrations, and creates required files' do + Dir.chdir(SAMPLE_APP_PATH) do + run_generator + + assert_file('config/routes.rb') do |contents| + assert_match( + %r{mount MaintenanceTasks::Engine => '/maintenance_tasks'}, + contents + ) + end + + mig = 'db/migrate/create_maintenance_tasks_runs.maintenance_tasks.rb' + assert_migration(mig) + assert_file('db/schema.rb') do |contents| + assert_match(/create_table "maintenance_tasks_runs"/, contents) + end + + assert_file('app/jobs/maintenance/application_task.rb') + assert_file('config/initializers/maintenance_tasks.rb') + end + end + + private + + def setup_sample_app + FileUtils.copy_entry(Rails.root, SAMPLE_APP_PATH) + + Dir.chdir(SAMPLE_APP_PATH) do + FileUtils.rm_r('db') + FileUtils.rm('config/initializers/maintenance_tasks.rb') + FileUtils.rm('app/jobs/maintenance/application_task.rb') + end + end + end +end