Ward W. Vuillemot
Ward W. Vuillemot

Reputation: 23

Overriding Scaffold Controller in Rails 7.1

Briefly, I want to override and/or add additional files when running rails generate scaffold MyModel name:string for example.

  1. [DONE] Override the views
  2. [????] Override the controller.rb
  3. [????] Add to the views such as destroy.turbo_stream.haml, _mymodel.html.haml

I've spent the afternoon reading documentation reading how to override templates with rails generators at https://guides.rubyonrails.org/generators.html#overriding-rails-generator-templates along with a slew of posts on how to over-ride various parts of controllers.

While I was able to override some of the views, I've yet been able find the path magic to override the controller.rb used during scaffold generation.

Per this, I should be able to add the controller file somewhere in lib/templates but nothing is working.

I wanted to add before_action :authenticate_user! and some other changes that I want to ensure are always in scaffolded controllers.

Any thoughts as to the specific location within railties ... per documentation it is this: https://github.com/rails/rails/blob/main/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt

But then where in lib/ does it go?

I'd love some help. HELP!! :)

P.S. And if you know how to add additional views (e.g. destroy.turbo_stream.haml to the mix in the views along with _MODEL.html.haml I'd love to figure that out, too.

Upvotes: 1

Views: 477

Answers (2)

Alex
Alex

Reputation: 30101

Override the views

https://github.com/haml/haml-rails/tree/master/lib/generators/haml/scaffold/templates

# lib/generators/haml/scaffold/templates/show.html.haml
#             .--'    |                  |
#             |    .--'     .------------'
#             v    v        v
lib/templates/haml/scaffold/show.html.haml
$ bin/rails g scaffold Testing
...
$ cat app/views/testings/show.html.haml
hi haml

Override the controller.rb

Override default controller template from railties gem:

# lib/rails/generators/rails/scaffold_controller/templates/controller.rb.tt
#             .--------'     |                             |
#             |     .--------'          .------------------'
#             v     v                   v
lib/templates/rails/scaffold_controller/controller.rb.tt

Add to the views such as destroy.turbo_stream.haml.

Generate a generator in rails namespace:

$ bin/rails g generator rails/my_scaffold
# lib/generators/rails/my_scaffold/my_scaffold_generator.rb

class Rails::MyScaffoldGenerator < Rails::Generators::NamedBase
  # run default scaffold
  invoke "scaffold_controller"

  # add new stuff
  include Rails::Generators::ResourceHelpers
  source_root File.expand_path("templates", __dir__)
  def copy_view_files
    ["destroy.turbo_stream.haml"].each do |filename|
      template filename, File.join("app/views", controller_file_path, filename)
    end
  end
end
# lib/generators/rails/my_scaffold/templates/destroy.turbo_stream.haml.tt

# TODO: make a template

Change default scaffold controller generator:

# config/application.rb

config.generators do |g|
  g.scaffold_controller :my_scaffold
end
$ bin/rails g scaffold Testing   
...
     create    app/views/testings/destroy.turbo_stream.haml

You could get a bit more granular if you want:

# config/application.rb

config.generators do |g|
  g.template_engine :my_scaffold
end

and then instead of invoking the whole scaffold just invoke "haml:scaffold" the rest is the same.


This isn't the prettiest way of adding to the scaffold, because the scaffold log is nested under my_scaffold. If you want to add this generator to be invoked by the scaffold instead, you'll have to add a new hook:

https://api.rubyonrails.org/classes/Rails/Generators/Base.html#method-c-hook_for

For example, have a look at how jbuilder does it:
https://github.com/rails/jbuilder/blob/v2.11.5/lib/jbuilder/railtie.rb#L29
https://github.com/rails/jbuilder/tree/v2.11.5/lib/generators/rails

Upvotes: 2

Ward W. Vuillemot
Ward W. Vuillemot

Reputation: 23

For sake of adding some more texture to answer from Alex which 100% nailed my questions.

I wanted to added a partial view such as app/views/my_models/_my_model.html which is populated with the model attributes and values.

Out of the box, the above example from Alex ended up crapping out till I stumbled upon https://garrettdimon.com/journal/posts/creating-custom-rails-generators, see Figure 13 for specifics.

This resulted in some minor tweaks, or:

# lib/generators/rails/my_scaffold/my_scaffold_generator.rb

class Rails::MyScaffoldGenerator < Rails::Generators::NamedBase
   # run default scaffold
  invoke 'scaffold_controller'
  argument :attributes, type: :array, default: [], 
    banner: "field[:type][:index] field[:type][:index]"

  # add new stuff
  include Rails::Generators::ResourceHelpers
  source_root File.expand_path('templates', __dir__)
  def copy_view_files
    ['destroy.turbo_stream.haml'].each do |filename|
      template filename, File.join('app/views', controller_file_path, filename)
    end
    ['partial.html.haml'].each do |filename|
      template filename, File.join('app/views', controller_file_path, "_#{singular_table_name}.html.haml")
    end
  end

end

More specifically, two things I added are:

    argument :attributes, type: :array, default: [], 
    banner: "field[:type][:index] field[:type][:index]"

which ensures you can access model attributes in the template.

    ['partial.html.haml'].each do |filename|
      template filename, File.join('app/views', controller_file_path, "_#{singular_table_name}.html.haml")
    end

adds a partial and renames it to the singular model name

And then I added to my generator templates:

# lib/generators/rails/my_scaffold/templates/partial.html.haml.tt

.container
<%- attributes.each do |attribute| -%>
    %p
        %b <%= attribute.name.humanize %>:
        =<%= singular_table_name %>.<%= attribute.name %>
<%- end -%>

    = link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>), class: 'btn btn-secondary m-1'

Note, I did not find the .tt entirely necessary since I'm using haml vs erb as tt is itself a variant of tt, or so I understand.

And that, coupled to Alex's answer above ensures

  1. Ability to override the default controller
  2. Ability to override the default views
  3. Ability to add new views

Upvotes: 1

Related Questions