Skip to content

Commit

Permalink
Add scaffold generators
Browse files Browse the repository at this point in the history
  • Loading branch information
hoverjet committed Mar 18, 2024
1 parent 55f64cb commit 6d83627
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 0 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,21 @@ Use the supplied generator to generate policies:
``` sh
rails g pundit:policy post
```
The scaffold command:
```sh
rails g scaffold Post
```
will also generate a policy and a scaffolded controller with Pundit's authorization method calls for actions.
```sh
...
invoke scaffold_controller
create app/controllers/posts_controller.rb
...
invoke policy
create app/policies/post_policy.rb
invoke rspec
create spec/policies/post_policy_spec.rb
```

## Closed systems

Expand Down
11 changes: 11 additions & 0 deletions lib/generators/pundit/scaffold/scaffold_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

require "rails/generators/rails/scaffold/scaffold_generator"

module Rails
module Generators
class ScaffoldGenerator
hook_for :policy, in: :pundit, type: :boolean, default: true
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
before_action :set_<%= singular_table_name %>, only: %i[ show update destroy ]

# GET <%= route_url %>
def index
@<%= plural_table_name %> = policy_scope(<%= orm_class.all(class_name) %>)
render json: @<%= plural_table_name %>
end

# GET <%= route_url %>/1
def show
authorize @<%= singular_table_name %>
render json: @<%= singular_table_name %>
end

# POST <%= route_url %>
def create
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
authorize @<%= singular_table_name %>

if @<%= orm_instance.save %>
render json: @<%= singular_table_name %>, status: :created, location: @<%= singular_table_name %>
else
render json: @<%= orm_instance.errors %>, status: :unprocessable_entity
end
end

# PATCH/PUT <%= route_url %>/1
def update
authorize @<%= singular_table_name %>
if @<%= orm_instance.update("#{singular_table_name}_params") %>
render json: @<%= singular_table_name %>
else
render json: @<%= orm_instance.errors %>, status: :unprocessable_entity
end
end

# DELETE <%= route_url %>/1
def destroy
authorize @<%= singular_table_name %>
@<%= orm_instance.destroy %>
end

private
# Use callbacks to share common setup or constraints between actions.
def set_<%= singular_table_name %>
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
authorize @<%= singular_table_name %>, only: [:show, :update, :destroy]
end

# Only allow a list of trusted parameters through.
def <%= "#{singular_table_name}_params" %>
<%- if attributes_names.empty? -%>
params.fetch(:<%= singular_table_name %>, {})
<%- else -%>
params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
<%- end -%>
end
end
<% end -%>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
before_action :set_<%= singular_table_name %>, only: %i[show edit update destroy]

# GET <%= route_url %>
def index
@<%= plural_table_name %> = policy_scope(<%= orm_class.all(class_name) %>)
end

# GET <%= route_url %>/1
def show
authorize @<%= singular_table_name %>
end

# GET <%= route_url %>/new
def new
@<%= singular_table_name %> = <%= orm_class.build(class_name) %>
authorize @<%= singular_table_name %>
end

# GET <%= route_url %>/1/edit
def edit
authorize @<%= singular_table_name %>
end

# POST <%= route_url %>
def create
@<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
authorize @<%= singular_table_name %>

if @<%= orm_instance.save %>
redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully created.") %>
else
render :new, status: :unprocessable_entity
end
end

# PATCH/PUT <%= route_url %>/1
def update
authorize @<%= singular_table_name %>
if @<%= orm_instance.update("#{singular_table_name}_params") %>
redirect_to <%= redirect_resource_name %>, notice: <%= %("#{human_name} was successfully updated.") %>, status: :see_other
else
render :edit, status: :unprocessable_entity
end
end

# DELETE <%= route_url %>/1
def destroy
authorize @<%= singular_table_name %>
@<%= orm_instance.destroy %>
redirect_to <%= index_helper %>_url, notice: <%= %("#{human_name} was successfully destroyed.") %>, status: :see_other
end

private
# Use callbacks to share common setup or constraints between actions.
def set_<%= singular_table_name %>
@<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %>
authorize @<%= singular_table_name %>
end

# Only allow a list of trusted parameters through.
def <%= "#{singular_table_name}_params" %>
<%- if attributes_names.empty? -%>
params.fetch(:<%= singular_table_name %>, {})
<%- else -%>
params.require(:<%= singular_table_name %>).permit(<%= permitted_params %>)
<%- end -%>
end
end
<% end -%>
1 change: 1 addition & 0 deletions lib/pundit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require "active_support/core_ext/module/introspection"
require "active_support/dependencies/autoload"
require "pundit/authorization"
require "pundit/railtie" if defined?(Rails)

# @api private
# To avoid name clashes with common Error naming when mixing in Pundit,
Expand Down
14 changes: 14 additions & 0 deletions lib/pundit/railtie.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

require "rails/railtie"

module Pundit
class Railtie < Rails::Railtie
generators do |app|
Rails::Generators.configure! app.config.generators
templates_dir = File.expand_path("../generators/rails/scaffold_controller/templates", __dir__)
Rails::Generators::ScaffoldControllerGenerator.source_paths.unshift(templates_dir)
require "generators/pundit/scaffold/scaffold_generator"
end
end
end
1 change: 1 addition & 0 deletions pundit.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "actionpack", ">= 3.0.0"
gem.add_development_dependency "activemodel", ">= 3.0.0"
gem.add_development_dependency "bundler"
gem.add_development_dependency "generator_spec"
gem.add_development_dependency "pry"
gem.add_development_dependency "railties", ">= 3.0.0"
gem.add_development_dependency "rake"
Expand Down
49 changes: 49 additions & 0 deletions spec/generators/scaffold_controller_generator_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# frozen_string_literal: true

require "generator_spec"
require "rails/generators/rails/scaffold_controller/scaffold_controller_generator"

describe Rails::Generators::ScaffoldControllerGenerator, type: :generator do
destination File.expand_path("../../tmp", __dir__)
let(:args) { %w[Post --no-helper --skip-template-engine --skip-routes --skip-helpers] }

before do
prepare_destination
# NOTE: Set up the templates directory, similar to Pundit::Railtie
templates_dir = File.expand_path("../../lib/generators/rails/scaffold_controller/templates", __dir__)
Rails::Generators::ScaffoldControllerGenerator.source_paths.unshift(templates_dir)

run_generator(args)
end

it "generates a scaffold controller with Pundit integration" do
assert_file "app/controllers/posts_controller.rb" do |content|
expect(content).to include("class PostsController < ApplicationController")

%w[index show new edit create update destroy].each do |action|
expect(content).to include("def #{action}")
end

expect(content).to include("authorize @post")
expect(content).to include("policy_scope(Post.all)")
end
end

context "when API mode" do
let(:args) { %w[Post --api --no-helper --skip-template-engine --skip-routes --skip-helpers] }

it "generates a scaffold API controller with Pundit integration" do
assert_file "app/controllers/posts_controller.rb" do |content|
expect(content).to include("class PostsController < ApplicationController")
expect(content).to include("render json: @posts")

%w[index show create update destroy].each do |action|
expect(content).to include("def #{action}")
end

expect(content).to include("authorize @post")
expect(content).to include("policy_scope(Post.all)")
end
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
require "active_support/core_ext"
require "active_model/naming"
require "action_controller/metal/strong_parameters"
require "generator_spec"

class PostPolicy < Struct.new(:user, :post)
class Scope < Struct.new(:user, :scope)
Expand Down

0 comments on commit 6d83627

Please sign in to comment.