diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 6eaad8ae58..2d626a0528 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,9 @@ +* Add `config.generators.after_generate` for processing to generated files. + + Register a callback that will get called right after generators has finished. + + *Yuji Yaginuma* + * Make test file patterns configurable via Environment variables This makes test file patterns configurable via two environment variables: diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index b8c0637078..9a90456c55 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -108,7 +108,7 @@ module Rails class Generators #:nodoc: attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging, :api_only - attr_reader :hidden_namespaces + attr_reader :hidden_namespaces, :after_generate_callbacks def initialize @aliases = Hash.new { |h, k| h[k] = {} } @@ -118,6 +118,7 @@ module Rails @colorize_logging = true @api_only = false @hidden_namespaces = [] + @after_generate_callbacks = [] end def initialize_copy(source) @@ -131,6 +132,10 @@ module Rails @hidden_namespaces << namespace end + def after_generate(&block) + @after_generate_callbacks << block + end + def method_missing(method, *args) method = method.to_s.sub(/=$/, "").to_sym diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 098838e8cb..6f6a664e6f 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -80,6 +80,7 @@ module Rails templates_path.concat config.templates templates_path.uniq! hide_namespaces(*config.hidden_namespaces) + after_generate_callbacks.replace config.after_generate_callbacks end def templates_path #:nodoc: @@ -94,6 +95,10 @@ module Rails @options ||= DEFAULT_OPTIONS.dup end + def after_generate_callbacks # :nodoc: + @after_generate_callbacks ||= [] + end + # Hold configured generators fallbacks. If a plugin developer wants a # generator group to fallback to another group in case of missing generators, # they can add a fallback. @@ -269,6 +274,7 @@ module Rails if klass = find_by_namespace(names.pop, names.any? && names.join(":")) args << "--help" if args.empty? && klass.arguments.any?(&:required?) klass.start(args, config) + run_after_generate_callback if config[:behavior] == :invoke else options = sorted_groups.flat_map(&:last) suggestion = Rails::Command::Spellchecker.suggest(namespace.to_s, from: options) @@ -281,6 +287,11 @@ module Rails end end + def add_generated_file(file) # :nodoc: + (@@generated_files ||= []) << file + file + end + private def print_list(base, namespaces) # :doc: namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) } @@ -314,6 +325,15 @@ module Rails def file_lookup_paths # :doc: @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ] end + + def run_after_generate_callback + if defined?(@@generated_files) && !@@generated_files.empty? + @after_generate_callbacks.each do |callback| + callback.call(@@generated_files) + end + @@generated_files = [] + end + end end end end diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb index 67a8139cd3..0a20f89cec 100644 --- a/railties/lib/rails/generators/actions/create_migration.rb +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -19,6 +19,11 @@ module Rails exists? && File.binread(existing_migration) == render end + def invoke! + invoked_file = super + File.exist?(@destination) ? invoked_file : relative_existing_migration + end + def revoke! say_destination = exists? ? relative_existing_migration : relative_destination say_status :remove, :red, say_destination diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index b6ec0160cf..e9e681e1fb 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -62,13 +62,14 @@ module Rails dir, base = File.split(destination) numbered_destination = File.join(dir, ["%migration_number%", base].join("_")) - create_migration numbered_destination, nil, config do + file = create_migration numbered_destination, nil, config do if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+ ERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer").result(context) else ERB.new(::File.binread(source), nil, "-", "@output_buffer").result(context) end end + Rails::Generators.add_generated_file(file) end end end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 6cdd6045c4..0cbaf0c275 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -22,7 +22,7 @@ module Rails no_tasks do def template(source, *args, &block) inside_template do - super + Rails::Generators.add_generated_file(super) end end diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index ba1b8b7cd2..0f8c686581 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -217,5 +217,37 @@ module ApplicationTests output = rails("generate", "model", "post", "title:string", "body:string", "--force") assert_no_match(/The name 'Post' is either already used in your application or reserved/, output) end + + test "generators with after_generate callback" do + model_file = File.join(app_path, "app/models/post.rb") + with_config do |c| + c.generators.after_generate do |files| + expected = %w( + db/migrate/20000101000000_create_posts.rb + app/models/post.rb + test/models/post_test.rb + test/fixtures/posts.yml + app/controllers/posts_controller.rb + app/views/posts/index.html.erb + app/views/posts/edit.html.erb + app/views/posts/show.html.erb + app/views/posts/new.html.erb + app/views/posts/_form.html.erb + test/controllers/posts_controller_test.rb + test/system/posts_test.rb + app/helpers/posts_helper.rb + ) + assert_equal expected, files + + File.open(model_file, "a") { |f| f.write("# Add comment to model") } + end + end + + travel_to Time.utc(2000, 1, 1) do + rails("generate", "scaffold", "post", "title:string") + end + + assert_match(/# Add comment to model/, File.read(model_file)) + end end end diff --git a/railties/test/generators/create_migration_test.rb b/railties/test/generators/create_migration_test.rb index 2ae38045c5..bcb480a55d 100644 --- a/railties/test/generators/create_migration_test.rb +++ b/railties/test/generators/create_migration_test.rb @@ -73,6 +73,15 @@ class CreateMigrationTest < Rails::Generators::TestCase assert_predicate @migration, :identical? end + def test_invoke_return_existing_file_when_exists_identical + migration_exists! + create_migration + + invoked_file = nil + quietly { invoked_file = @migration.invoke! } + assert_equal @existing_migration.relative_existing_migration, invoked_file + end + def test_invoke_when_exists_not_identical migration_exists! create_migration { "different content" }