Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,20 +146,18 @@ puts result.issues.to_a.map(&:message)

## Rails Usage

For an app that uses Crawlscope through rake tasks, load the task entrypoint
only when a Crawlscope task is requested. In the application's `Rakefile`,
before `require_relative "config/application"`:
Run the install generator after adding the gem:

```ruby
list_tasks = Rake.application.options.show_tasks || ARGV.any? { |argument| ["-T", "--tasks"].include?(argument) }
require "crawlscope/tasks" if list_tasks || ARGV.any? { |argument| argument.start_with?("crawlscope:") }
```bash
bin/rails generate crawlscope:install
```

This preserves `bin/rails --tasks` and the `crawlscope:*` tasks without loading
Crawlscope during normal Rails boot. Runtime callers should explicitly
`require "crawlscope"` immediately before configuring or using the gem.
The generator creates `config/initializers/crawlscope.rb` with an idempotent
`CrawlscopeConfiguration.apply` loader and adds conditional `crawlscope/tasks`
loading to the application's `Rakefile`. This preserves `bin/rails --tasks` and
the `crawlscope:*` tasks without loading Crawlscope during normal Rails boot.

In an initializer:
Customize the `Crawlscope.configure` block inside the generated initializer:

```ruby
Crawlscope.configure do |config|
Expand All @@ -170,6 +168,17 @@ Crawlscope.configure do |config|
end
```

Runtime callers must apply the generated configuration before using
Crawlscope:

```ruby
CrawlscopeConfiguration.apply
Crawlscope.configuration.audit
```

Rake tasks apply it automatically because `crawlscope/tasks` loads the gem
before Rails evaluates the initializer.

Then run:

```bash
Expand Down
1 change: 1 addition & 0 deletions lib/crawlscope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ def reset!
end

Crawlscope.instance_variable_set(:@loader, Zeitwerk::Loader.for_gem)
Crawlscope.loader.ignore("#{__dir__}/generators")
Crawlscope.loader.ignore("#{__dir__}/tasks")
Crawlscope.loader.ignore("#{__dir__}/crawlscope/railtie.rb")
Crawlscope.loader.ignore("#{__dir__}/crawlscope/tasks.rb")
Expand Down
37 changes: 37 additions & 0 deletions lib/generators/crawlscope/install_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

require "rails/generators"

module Crawlscope
module Generators
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path("templates", __dir__)

desc "Create a lazy-safe Crawlscope initializer and task loader"

def create_initializer
template "initializer.rb.tt", "config/initializers/crawlscope.rb"
end

def configure_rakefile
rakefile = File.join(destination_root, "Rakefile")
return unless File.exist?(rakefile)
return if File.read(rakefile).include?('require "crawlscope/tasks"')

inject_into_file "Rakefile", rakefile_setup, before: /^require_relative ["']config\/application["']/
end

private

def rakefile_setup
<<~RUBY
crawlscope_tasks_requested = Rake.application.options.show_tasks ||
ARGV.any? { |argument| ["-T", "--tasks"].include?(argument) }
require "crawlscope/tasks" if crawlscope_tasks_requested ||
ARGV.any? { |argument| argument.start_with?("crawlscope:") }

RUBY
end
end
end
end
27 changes: 27 additions & 0 deletions lib/generators/crawlscope/templates/initializer.rb.tt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module CrawlscopeConfiguration
MUTEX = Mutex.new

def self.apply
return if @applied

MUTEX.synchronize do
return if @applied

require "crawlscope"

Crawlscope.configure do |config|
config.base_url = -> { ENV.fetch("CRAWLSCOPE_BASE_URL", "http://localhost:3000") }
config.sitemap_path = lambda {
ENV.fetch("SITEMAP", Rails.public_path.join("sitemap.xml").to_s)
}
config.site_name = ENV.fetch("CRAWLSCOPE_SITE_NAME", "Application")
end

@applied = true
end
end
end

CrawlscopeConfiguration.apply if defined?(Crawlscope)
58 changes: 58 additions & 0 deletions test/crawlscope/install_generator_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# frozen_string_literal: true

require "test_helper"
require "generators/crawlscope/install_generator"
require "open3"
require "rbconfig"

class CrawlscopeInstallGeneratorTest < Minitest::Test
def test_generates_lazy_initializer_and_task_loader
Dir.mktmpdir do |destination|
File.write(File.join(destination, "Rakefile"), <<~RUBY)
require_relative "config/application"
Rails.application.load_tasks
RUBY

Crawlscope::Generators::InstallGenerator.start([], destination_root: destination)

initializer = File.join(destination, "config/initializers/crawlscope.rb")
rakefile = File.read(File.join(destination, "Rakefile"))

assert File.exist?(initializer)
assert_includes File.read(initializer), "module CrawlscopeConfiguration"
assert_includes File.read(initializer), "CrawlscopeConfiguration.apply if defined?(Crawlscope)"
assert_includes rakefile, 'require "crawlscope/tasks"'
assert_operator rakefile.index('require "crawlscope/tasks"'), :<, rakefile.index('require_relative "config/application"')

_stdout, stderr, status = Open3.capture3(RbConfig.ruby, initializer)
assert status.success?, stderr

script = <<~RUBY
require "pathname"

module Rails
def self.public_path
Pathname.new("public")
end
end

require "crawlscope"
load ARGV.fetch(0)

configuration = Crawlscope.configuration
CrawlscopeConfiguration.apply
abort "configuration replaced" unless Crawlscope.configuration.equal?(configuration)
abort "base URL missing" unless configuration.base_url == "http://localhost:3000"
abort "sitemap missing" unless configuration.sitemap_path == "public/sitemap.xml"
RUBY
_stdout, stderr, status = Open3.capture3(
RbConfig.ruby,
"-I#{File.expand_path("../../lib", __dir__)}",
"-e",
script,
initializer
)
assert status.success?, stderr
end
end
end
Loading