Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
@annikoff
Copy link
Author

annikoff commented May 17, 2022

@james-em There might be some issues with require when the default loader is ZeitWerk.

@james-em
Copy link

james-em commented May 17, 2022

@james-em There might be some issues with require when the default loader is ZeitWerk.

That is my guess too. What should be done instead?

@annikoff
Copy link
Author

annikoff commented May 17, 2022

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

@james-em
Copy link

james-em commented May 17, 2022

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

annikoff commented May 17, 2022

Thanks for the effort.

@james-em
Copy link

james-em commented May 17, 2022

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

annikoff commented Jun 20, 2022

@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.

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