Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generators: decorators should respect --model-name #931

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
11 changes: 9 additions & 2 deletions lib/generators/controller_override.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
require "rails/generators"
require "rails/generators/rails/controller/controller_generator"
require "rails/generators/rails/scaffold_controller/scaffold_controller_generator"
require "rails/generators/model_helpers"

module Rails
module Generators
class ControllerGenerator
include Rails::Generators::ModelHelpers

class_option :model_name, type: :string, desc: "ModelName to be used"
Copy link
Collaborator

@y-yagi y-yagi Sep 3, 2024

Choose a reason for hiding this comment

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

Hmm. Basically, controller doesn't need to know about model. And, the current implementation depends on controller name for naming. So overring controller name by model_name looks strange to me.
How about using decorator_name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

scaffold_controller already has that interface and I'd prefer controller to be uniform with it. Rails already has --model-name option and I'd rather use it.

I agree that model dependency for controller is dubious, but we already have a decorator hook for controller that needs to know about a model. That's why controller may also need to.

Another option is to remove decorator hook from controller and add it to model. In my opinion, decorators are bound to models rather than controllers or views. However, I'd keep the existing API for now rather than introducing another braking change.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree that it might be good to hook my models, not controllers. But, it seems that the current behavior is intended #553.
So generating decorators by controller names is the intended behavior(At least, so far). Setting a decorator name by --model-name is inconsistent with that behavior I think.

Copy link
Collaborator Author

@Alexander-Senko Alexander-Senko Sep 4, 2024

Choose a reason for hiding this comment

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

So generating decorators by controller names is the intended behavior.

Yes, it is. And it's not being changed.

Models are also generated based on controller names, but we have --model-name to override that when needed.

scaffold_controller already has that interface…

So, why shouldn't a decorator respect it as well when it's specified explicitly?

I'm afraid, I'm missing your point… What is your solution for #919?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, it is. And it's not being changed.

I think this PR changes the behavior. For example, if you run ./bin/rails g scaffold admin/book name:string --model-name book, the result will be change between master branch and this PR.

To be honest, I don't think #919 is a bug. That is the intended behavior of the current generator structure(I can't understand why draper only hooks for controller though).
But of course, I understand the user's expectations. So adding decorator_name(or something like that) to override decorators name is a good solution I think.


hook_for :decorator, type: :boolean, default: true do |generator|
invoke generator, [name.singularize]
invoke generator, [options[:model_name] || name]
end
end

class ScaffoldControllerGenerator
hook_for :decorator, type: :boolean, default: true
hook_for :decorator, type: :boolean, default: true do |generator|
invoke generator, [options[:model_name] || name]
end
end
end
end
62 changes: 54 additions & 8 deletions spec/generators/controller/controller_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,65 @@
require 'generators/rails/decorator_generator'
SimpleCov.command_name 'test:generator'

describe Rails::Generators::ControllerGenerator do
destination File.expand_path("../tmp", __FILE__)
describe Rails::Generators do
destination Rails.root / 'tmp/generated'

subject { file path }

let(:controller_name) { 'YourModels' }
let(:model_name) { controller_name.singularize }
let(:decorator_name) { "#{model_name}Decorator" }
let(:options) { {} }
let(:args) { options.map { |k, v| "--#{k.to_s.dasherize}=#{v}" } }

before { prepare_destination }
after(:all) { FileUtils.rm_rf destination_root }
before { run_generator [controller_name, *args] }

shared_context :namespaceable do
let(:controller_name) { 'Namespace::YourModels' }
let(:model_name) { options[:model_name] or super() }

include_examples :naming

context "with the same namespace" do
let(:options) { super().merge model_name: 'Namespace::OtherModel' }

include_examples :naming
end

context "with another namespace" do
let(:options) { super().merge model_name: 'OtherNamespace::YourModel' }

include_examples :naming
end

context "without namespace" do
let(:options) { super().merge model_name: 'YourModel' }

include_examples :naming
end
end

describe "the generated decorator" do
subject { file("app/decorators/your_model_decorator.rb") }
describe "decorator class" do
let(:path) { "app/decorators/#{decorator_name.underscore}.rb" }

shared_examples :naming do
it 'is properly named' do
is_expected.to exist
is_expected.to contain "class #{decorator_name}"
end
end

describe Rails::Generators::ControllerGenerator do
include_examples :naming
it_behaves_like :namespaceable
end

describe "naming" do
before { run_generator %w(YourModels) }
describe Rails::Generators::ScaffoldControllerGenerator do
let(:options) { { skip_routes: true } }

it { is_expected.to contain "class YourModelDecorator" }
include_examples :naming
it_behaves_like :namespaceable
end
end
end
108 changes: 60 additions & 48 deletions spec/generators/decorator/decorator_generator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,102 @@
require 'generators/rails/decorator_generator'

describe Rails::Generators::DecoratorGenerator do
destination File.expand_path("../tmp", __FILE__)
destination Rails.root / 'tmp/generated'

subject { file path }

let(:model_name) { 'YourModel' }
let(:decorator_name) { "#{model_name}Decorator" }
let(:options) { {} }
let(:args) { options.map { |k, v| "--#{k.to_s.dasherize}=#{v}" } }

before { prepare_destination }
after(:all) { FileUtils.rm_rf destination_root }
before { run_generator [model_name, *args] }

describe "the generated decorator" do
subject { file("app/decorators/your_model_decorator.rb") }
shared_context :namespaceable do
let(:model_name) { 'Namespace::YourModel' }

describe "naming" do
before { run_generator %w(YourModel) }
include_examples :naming
end

it { is_expected.to contain "class YourModelDecorator" }
end
describe "decorator class" do
let(:path) { "app/decorators/#{decorator_name.underscore}.rb" }

describe "namespacing" do
subject { file("app/decorators/namespace/your_model_decorator.rb") }
before { run_generator %w(Namespace::YourModel) }
it { is_expected.to have_correct_syntax }

it { is_expected.to contain "class Namespace::YourModelDecorator" }
shared_examples :naming do
it 'is properly named' do
is_expected.to exist
is_expected.to contain "class #{decorator_name}"
end
end

include_examples :naming
it_behaves_like :namespaceable

describe "inheritance" do
context "by default" do
before { run_generator %w(YourModel) }
let(:parent) { 'Draper::Decorator' }

it { is_expected.to contain "class YourModelDecorator < Draper::Decorator" }
shared_examples :inheritance do
it { is_expected.to contain "class #{decorator_name} < #{parent}" }
end

context "with the --parent option" do
before { run_generator %w(YourModel --parent=FooDecorator) }
include_examples :inheritance

context "with --parent" do
let(:options) { { parent: 'FooDecorator' } }
let(:parent) { options[:parent] }

it { is_expected.to contain "class YourModelDecorator < FooDecorator" }
include_examples :inheritance
end

context "with an ApplicationDecorator" do
before do
let(:parent) { 'ApplicationDecorator' }

let :options do # HACK: run before the generator
allow_any_instance_of(Object).to receive(:require).and_call_original
allow_any_instance_of(Object).to receive(:require).with("application_decorator").and_return(
stub_const "ApplicationDecorator", Class.new
)
super()
end

before { run_generator %w(YourModel) }

it { is_expected.to contain "class YourModelDecorator < ApplicationDecorator" }
include_examples :inheritance
end
end
end

context "with -t=rspec" do
describe "the generated spec" do
subject { file("spec/decorators/your_model_decorator_spec.rb") }
describe "spec" do
let(:options) { { test_framework: :rspec } }
let(:path) { "spec/decorators/#{decorator_name.underscore}_spec.rb" }

describe "naming" do
before { run_generator %w(YourModel -t=rspec) }
it { is_expected.to have_correct_syntax }

it { is_expected.to contain "describe YourModelDecorator" }
end

describe "namespacing" do
subject { file("spec/decorators/namespace/your_model_decorator_spec.rb") }
before { run_generator %w(Namespace::YourModel -t=rspec) }

it { is_expected.to contain "describe Namespace::YourModelDecorator" }
shared_examples :naming do
it 'is properly named' do
is_expected.to exist
is_expected.to contain "describe #{decorator_name}"
end
end
end

context "with -t=test_unit" do
describe "the generated test" do
subject { file("test/decorators/your_model_decorator_test.rb") }

describe "naming" do
before { run_generator %w(YourModel -t=test_unit) }
include_examples :naming
it_behaves_like :namespaceable
end

it { is_expected.to contain "class YourModelDecoratorTest < Draper::TestCase" }
end
describe "test" do
let(:options) { { test_framework: :test_unit } }
let(:path) { "test/decorators/#{decorator_name.underscore}_test.rb" }

describe "namespacing" do
subject { file("test/decorators/namespace/your_model_decorator_test.rb") }
before { run_generator %w(Namespace::YourModel -t=test_unit) }
it { is_expected.to have_correct_syntax }

it { is_expected.to contain "class Namespace::YourModelDecoratorTest < Draper::TestCase" }
shared_examples :naming do
it 'is properly named' do
is_expected.to exist
is_expected.to contain "class #{decorator_name}Test < Draper::TestCase"
end
end

include_examples :naming
it_behaves_like :namespaceable
end
end