Skip to content
Open
5 changes: 5 additions & 0 deletions lib/generators/stimulus/USAGE
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ Examples:
bin/rails generate stimulus nested/chat

creates: app/javascript/controllers/nested/chat_controller.js


bin/rails generate stimulus chat --controllers-path=app/frontend/controllers

creates: app/frontend/controllers/chat_controller.js
18 changes: 16 additions & 2 deletions lib/generators/stimulus/stimulus_generator.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
require "rails/generators"
require "rails/generators/actions"
require "rails/generators/named_base"
require "stimulus/manifest"

class StimulusGenerator < Rails::Generators::NamedBase # :nodoc:
source_root File.expand_path("templates", __dir__)

class_option :skip_manifest, type: :boolean, default: false, desc: "Don't update the stimulus manifest"
class_option :controllers_path, type: :string, default: "app/javascript/controllers", desc: "Root path for controller files"

def copy_view_files
validate_controllers_path

@attribute = stimulus_attribute_value(controller_name)
template "controller.js", "app/javascript/controllers/#{controller_name}_controller.js"
Stimulus::Manifest.write_index_from(Rails.root.join("app/javascript/controllers")) unless update_manifest_index?
template "controller.js", "#{options.controllers_path}/#{controller_name}_controller.js"
rails_command "stimulus:manifest:update" unless update_manifest_index?
end

private

def validate_controllers_path
if options.controllers_path.blank?
raise "controllers-path cannot be empty"
elsif options.controllers_path.start_with?("/")
raise "controllers-path cannot be an absolute path: #{options.controllers_path}"
end
end

def controller_name
name.underscore.gsub(/_controller$/, "")
end
Expand Down
12 changes: 9 additions & 3 deletions lib/tasks/stimulus_tasks.rake
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "stimulus/manifest"
require "generators/stimulus/stimulus_generator"

module Stimulus
module Tasks
Expand All @@ -7,6 +8,11 @@ module Stimulus
system RbConfig.ruby, "./bin/rails", "app:template", "LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
end

def controllers_root
Rails.root.join \
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a little hard to read and still too long in my opinion, I believe the suggestion from @dhh should be good

def controllers_path
  generator = StimulusGenerator.new(["stimulus"], Rails.application.config.generators.options.dig(:stimulus), {}) 
  Rails.root.join generator.options.js_root, "controllers"
end

StimulusGenerator.new(["stimulus"], Rails.application.config.generators.options.dig(:stimulus), {}).options.controllers_path
end

def using_bun?
Rails.root.join("bun.config.js").exist?
end
Expand Down Expand Up @@ -47,12 +53,12 @@ namespace :stimulus do
namespace :manifest do
desc "Show the current Stimulus manifest (all installed controllers)"
task :display do
puts Stimulus::Manifest.generate_from(Rails.root.join("app/javascript/controllers"))
puts Stimulus::Manifest.generate_from(Stimulus::Tasks.controllers_root)
end

desc "Update the Stimulus manifest (will overwrite controllers/index.js)"
task :update do
Stimulus::Manifest.write_index_from(Rails.root.join("app/javascript/controllers"))
task update: :environment do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to add the environment task as a dependency? It doesn't seem necessary.

Stimulus::Manifest.write_index_from(Stimulus::Tasks.controllers_root)
end
end
end
25 changes: 24 additions & 1 deletion test/generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,30 @@ class StimulusGeneratorTest < Rails::Generators::TestCase
test "removes tailing 'controller' in namespaced string" do
run_generator ["Hello/WorldController"]

# Access the generator's class variables
# Make assertions against the config
# assert_equal expected_value, config[:some_key]

assert_file "app/javascript/controllers/hello/world_controller.js", /data-controller="hello--world"/
end

end
test "writes controller to a custom path if specified" do
custom_path = "app/custom/controllers"
run_generator ["Hello", "--controllers-path=#{custom_path}"]

assert_file "#{custom_path}/hello_controller.js", /data-controller="hello/
end

test "fails if controllers path is empty" do
assert_raises RuntimeError, "controllers-path cannot be empty" do
run_generator ["Hello", "--controllers-path="]
end
end

test "fails if controllers path is an absolute path" do
absolute_path = "/mypath"
assert_raises RuntimeError, "controllers-path cannot be an absolute path: #{absolute_path}" do
run_generator ["Hello", "--controllers-path=#{absolute_path}"]
end
end
end