Skip to content

Instantly share code, notes, and snippets.

@annikoff
Last active February 22, 2024 14:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save annikoff/331f785aa7a207a7945b1eca6eff526b to your computer and use it in GitHub Desktop.
Save annikoff/331f785aa7a207a7945b1eca6eff526b to your computer and use it in GitHub Desktop.
Custom generators

The main generator

# lib/generators/rails/policy/policy_generator.rb

module Rails
  module Generators
    class PolicyGenerator < NamedBase
      source_root File.expand_path('templates', __dir__)

      def copy_policy_file
        template 'policy.erb', File.join("app/policies", class_path, "#{file_name}_policy.rb")
      end

      hook_for :test_framework
    end
  end
end

The generator's template

# lib/generators/rails/policy/templates/policy.erb

class <%= class_name %>Policy
  # Add default methods
end

A hook to invoke the custom generator with scaffolding or with controller's generators

# lib/generators/rails/policy/hooks.rb

require 'rails/generators'
require 'rails/generators/rails/scaffold/scaffold_generator'
require 'rails/generators/rails/controller/controller_generator'

Rails::Generators::ScaffoldGenerator.hook_for :policy, default: true, type: :boolean # invoke with scaffolding generators
Rails::Generators::ControllerGenerator.hook_for :policy, default: true, type: :boolean # invoke with the controllers' generator.

The generator of a spec file

# lib/generators/rspec/policy/policy_generator.rb

module Rspec
  module Generators
    class PolicyGenerator < Rails::Generators::NamedBase
      source_root File.expand_path('templates', __dir__)

      def copy_policy_spec_file
        template 'policy_spec.erb',  File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
      end
    end
  end
end

The spec's template

# lib/generators/rspec/policy/templates/policy_spec.erb

require 'spec_helper'

describe <%= class_name %>Policy do
  pending "add some examples to (or delete) #{__FILE__}"
end

Application config to hook up custom generators

# config/application.rb

module YourAppName
  class Application < Rails::Application
    # ...

    config.generators do |g|
      g.test_framework  :rspec
      require './lib/generators/rails/policy/hooks'
    end
  end
end

Generate scaffolding

$ rails g scaffold user
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      invoke    rspec
      create      spec/requests/users_spec.rb
      create      spec/views/users/edit.html.erb_spec.rb
      create      spec/views/users/index.html.erb_spec.rb
      create      spec/views/users/new.html.erb_spec.rb
      create      spec/views/users/show.html.erb_spec.rb
      create      spec/routing/users_routing_spec.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke      rspec
      create        spec/helpers/users_helper_spec.rb
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder
      create      app/views/users/_user.json.jbuilder
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/users.scss
      invoke  css
   identical    app/assets/stylesheets/scaffold.css
      invoke  policy
      create    app/policies/user_policy.rb
      invoke    rspec
      create      spec/policies/user_policy_spec.rb

$ cat app/policies/user_policy.rb
class UserPolicy
  # Add default methods
end

$ cat spec/policies/user_policy_spec.rb
require 'spec_helper'

describe UserPolicy do
  pending "add some examples to (or delete) #{__FILE__}"
end

Generate a controller inside a module

$ rails g controller api/project
      create  app/controllers/api/project_controller.rb
      invoke  erb
      create    app/views/api/project
      invoke  helper
      create    app/helpers/api/project_helper.rb
      invoke    rspec
      create      spec/helpers/api/project_helper_spec.rb
      invoke  assets
      invoke    scss
      create      app/assets/stylesheets/api/project.scss
      invoke  policy
      create    app/policies/api/project_policy.rb
      invoke    rspec
      create      spec/policies/api/project_policy_spec.rb

$ cat app/policies/api/project_policy.rb
class Api::ProjectPolicy
  # Add default methods
end

$ cat spec/policies/api/project_policy_spec.rb
require 'spec_helper'

describe Api::ProjectPolicy do
  pending "add some examples to (or delete) #{__FILE__}"
end
@james-em
Copy link

Maybe remove generators/pundit/policy/policy_generator, zeitwerk should find and load Pundit::Generators::PolicyGenerator automatically, I guess.

I have tested without the require already since I know issues are coming from it.

I have tested all sort of events too but there is one more I haven't tried yet and it's the loader.on_load from Zeitwork. I will give it a try later

The only file I have is the initializer

Edit: Didn't work. Got any idea?

@james-em
Copy link

james-em commented May 17, 2022

I gave up and disabled pundit generator. Created my own generators and no more issue.

bundle exec rails generate generator rails/custom_policy
bundle exec rails generate generator rails/custom_policy

Initializer

require "rails/railtie"
require "rails/generators"

module CustomPolicyGenerator
  module ControllerGenerator
    extend ActiveSupport::Concern

    included do
      hook_for :custom_policy, in: nil, default: true, type: :boolean do |generator|
        invoke generator, [name.singularize]
      end
    end
  end

  module ScaffoldControllerGenerator
    extend ActiveSupport::Concern

    included do
      hook_for :custom_policy, in: nil, default: true, type: :boolean
    end
  end
end

module ActiveModel
  class Railtie < Rails::Railtie
    generators do |app|
      Rails::Generators.configure! app.config.generators
      Rails::Generators::ControllerGenerator.include CustomPolicyGenerator::ControllerGenerator
      Rails::Generators::ScaffoldControllerGenerator.include CustomPolicyGenerator::ScaffoldControllerGenerator
    end
  end
end

Rails.application.config.generators do |g|
  g.policy false
  g.custom_policy true
end
module Rails
  class CustomPolicyGenerator < Rails::Generators::NamedBase
    # source_root File.expand_path("templates", __dir__)

    def create_policy
      template "policy.rb", File.join("app/policies", class_path, "#{file_name}_policy.rb")
    end

    hook_for :test_framework
  end
end
module Rspec
  class CustomPolicyGenerator < Rails::Generators::NamedBase
    # source_root File.expand_path("templates", __dir__)

    def create_policy_spec
      template "policy_spec.rb", File.join("spec/policies", class_path, "#{file_name}_policy_spec.rb")
    end
  end
end

Placed template files in
lib/templates/rails/custom_policy/policy.rb.tt
lib/templates/rspec/custom_policy/policy_spec.rb.tt

@annikoff
Copy link
Author

Thanks for the effort.

@james-em
Copy link

Np !

Unsure if this

module ActiveModel
  class Railtie < Rails::Railtie
    generators do |app|
      Rails::Generators.configure! app.config.generators
      Rails::Generators::ControllerGenerator.include CustomPolicyGenerator::ControllerGenerator
      Rails::Generators::ScaffoldControllerGenerator.include CustomPolicyGenerator::ScaffoldControllerGenerator
    end
  end
end

is the best way but outside of that, Rails::Generators::ScaffoldControllerGenerator is not defined and I am forced to use a require. If I use the requires:

require "rails/generators"
require "rails/generators/rails/controller/controller_generator"
require "rails/generators/rails/scaffold_controller/scaffold_controller_generator"

my custom templates for controller are ignored.

@dyeje
Copy link

dyeje commented Jun 18, 2022

Thanks yall. I wrote a blogpost to cover what ended up working for me. I did end up needing the requires, but I wasn't using custom templates so not sure if it breaks that like you mentioned.

Adding a Custom Generator to Rails Scaffold

@annikoff
Copy link
Author

@dyeje That's cool. A lot of attention to this gist :)

@dyeje
Copy link

dyeje commented Jun 20, 2022

I assumed it would be as simple as plugging in a config, so I was surprised when this turned into a multi-hour adventure.

@tbrammar
Copy link

@james-em I'm not too sure that your controller scaffold templates are supposed to end with .tt 🤔

Have you tried simply ending them with .rb and seeing whether rails picks them up?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment