rockstart 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +100 -0
- data/Rakefile +19 -0
- data/lib/generators/rockstart/USAGE +13 -0
- data/lib/generators/rockstart/devise/USAGE +9 -0
- data/lib/generators/rockstart/devise/devise_generator.rb +258 -0
- data/lib/generators/rockstart/devise/templates/controllers/passwords_controller.rb +56 -0
- data/lib/generators/rockstart/devise/templates/controllers/registrations_controller.rb +88 -0
- data/lib/generators/rockstart/devise/templates/controllers/sessions_controller.rb +32 -0
- data/lib/generators/rockstart/devise/templates/create_user_migration.rb.tt +11 -0
- data/lib/generators/rockstart/devise/templates/models/user.rb +42 -0
- data/lib/generators/rockstart/devise/templates/spec/factories/users.rb +17 -0
- data/lib/generators/rockstart/devise/templates/spec/models/user_spec.rb +64 -0
- data/lib/generators/rockstart/devise/templates/spec/requests/users/passwords_spec.rb +202 -0
- data/lib/generators/rockstart/devise/templates/spec/requests/users/registrations_spec.rb +445 -0
- data/lib/generators/rockstart/devise/templates/spec/requests/users/sessions_spec.rb +171 -0
- data/lib/generators/rockstart/devise/templates/spec/support/devise_request_spec_helper.rb +29 -0
- data/lib/generators/rockstart/devise/templates/translations.en.yml +4 -0
- data/lib/generators/rockstart/docker/USAGE +10 -0
- data/lib/generators/rockstart/docker/docker_generator.rb +86 -0
- data/lib/generators/rockstart/docker/templates/app/Dockerfile-app +47 -0
- data/lib/generators/rockstart/docker/templates/docker-compose.test.yml +29 -0
- data/lib/generators/rockstart/docker/templates/docker-compose.yml +47 -0
- data/lib/generators/rockstart/docker/templates/dockerignore +16 -0
- data/lib/generators/rockstart/docker/templates/dotenv.docker.tt +4 -0
- data/lib/generators/rockstart/docker/templates/localhost_domains.ext.tt +7 -0
- data/lib/generators/rockstart/docker/templates/setup-localhost.tt +27 -0
- data/lib/generators/rockstart/docker/templates/web/Dockerfile-web +15 -0
- data/lib/generators/rockstart/docker/templates/web/nginx.conf +62 -0
- data/lib/generators/rockstart/frontend_helpers/USAGE +8 -0
- data/lib/generators/rockstart/frontend_helpers/frontend_helpers_generator.rb +65 -0
- data/lib/generators/rockstart/frontend_helpers/templates/application_urls.rb +26 -0
- data/lib/generators/rockstart/frontend_helpers/templates/application_urls_helper.rb +20 -0
- data/lib/generators/rockstart/frontend_helpers/templates/titles.en.yml.tt +5 -0
- data/lib/generators/rockstart/logging/USAGE +8 -0
- data/lib/generators/rockstart/logging/logging_generator.rb +12 -0
- data/lib/generators/rockstart/logging/templates/rockstart/lograge_initializer.rb +50 -0
- data/lib/generators/rockstart/postgres/USAGE +8 -0
- data/lib/generators/rockstart/postgres/postgres_generator.rb +32 -0
- data/lib/generators/rockstart/postgres/templates/config/database.yml.tt +18 -0
- data/lib/generators/rockstart/postgres/templates/migration.rb.tt +7 -0
- data/lib/generators/rockstart/pundit/USAGE +8 -0
- data/lib/generators/rockstart/pundit/pundit_generator.rb +32 -0
- data/lib/generators/rockstart/pundit/templates/app/controllers/concerns/pundit_error_handling.rb +29 -0
- data/lib/generators/rockstart/pundit/templates/app/policies/application_policy.rb +71 -0
- data/lib/generators/rockstart/pundit/templates/app/policies/user_policy.rb +47 -0
- data/lib/generators/rockstart/pundit/templates/config/locales/pundit.en.yml +6 -0
- data/lib/generators/rockstart/pundit/templates/lib/templates/pundit/policy/policy.rb +36 -0
- data/lib/generators/rockstart/pundit/templates/lib/templates/rspec/policy/policy_spec.rb +58 -0
- data/lib/generators/rockstart/pundit/templates/spec/policies/user_policy_spec.rb +95 -0
- data/lib/generators/rockstart/pundit/templates/spec/support/pundit_matchers.rb +7 -0
- data/lib/generators/rockstart/quality/USAGE +10 -0
- data/lib/generators/rockstart/quality/quality_generator.rb +28 -0
- data/lib/generators/rockstart/quality/templates/quality.rake +4 -0
- data/lib/generators/rockstart/quality/templates/rubocop.rake +4 -0
- data/lib/generators/rockstart/quality/templates/rubocop.yml +45 -0
- data/lib/generators/rockstart/rockstart_generator.rb +77 -0
- data/lib/generators/rockstart/rspec/USAGE +8 -0
- data/lib/generators/rockstart/rspec/rspec_generator.rb +70 -0
- data/lib/generators/rockstart/rspec/templates/dotenv.development +1 -0
- data/lib/generators/rockstart/rspec/templates/dotenv.test +1 -0
- data/lib/generators/rockstart/rspec/templates/rspec_templates/model/model_spec.rb +13 -0
- data/lib/generators/rockstart/rspec/templates/support/factory_bot.rb +6 -0
- data/lib/generators/rockstart/rspec/templates/support/shoulda_matchers.rb +9 -0
- data/lib/generators/rockstart/rspec/templates/support/test_helpers.rb +9 -0
- data/lib/generators/rockstart/scaffold_templates/USAGE +8 -0
- data/lib/generators/rockstart/scaffold_templates/scaffold_templates_generator.rb +39 -0
- data/lib/generators/rockstart/scaffold_templates/templates/api_controller.rb.tt +96 -0
- data/lib/generators/rockstart/scaffold_templates/templates/controller.rb.tt +126 -0
- data/lib/generators/rockstart/scaffold_templates/templates/rspec/scaffold/api_request_spec.rb +139 -0
- data/lib/generators/rockstart/scaffold_templates/templates/rspec/scaffold/request_spec.rb +408 -0
- data/lib/generators/rockstart/security/USAGE +13 -0
- data/lib/generators/rockstart/security/security_generator.rb +108 -0
- data/lib/generators/rockstart/security/templates/brakeman.rake +6 -0
- data/lib/generators/rockstart/security/templates/bundler_audit.rake +4 -0
- data/lib/generators/rockstart/security/templates/cache_support.rb +18 -0
- data/lib/generators/rockstart/security/templates/content_security_policy_initializer.rb.tt +56 -0
- data/lib/generators/rockstart/security/templates/content_security_spec.rb.tt +83 -0
- data/lib/generators/rockstart/security/templates/csp_violations_controller.rb +39 -0
- data/lib/generators/rockstart/security/templates/rack_attack.rb +98 -0
- data/lib/generators/rockstart/security/templates/security.rake +9 -0
- data/lib/generators/rockstart/security/templates/session_store_initializer.rb.tt +7 -0
- data/lib/generators/rockstart/smtp_mailer/USAGE +8 -0
- data/lib/generators/rockstart/smtp_mailer/smtp_mailer_generator.rb +30 -0
- data/lib/generators/rockstart/smtp_mailer/templates/config/initializers/action_mailer.rb +10 -0
- data/lib/generators/rockstart/tailwindcss/USAGE +8 -0
- data/lib/generators/rockstart/tailwindcss/tailwindcss_generator.rb +30 -0
- data/lib/generators/rockstart/tailwindcss/templates/application.css +3 -0
- data/lib/generators/rockstart/tailwindcss/templates/postcss.config.js +32 -0
- data/lib/rockstart/base_generator.rb +32 -0
- data/lib/rockstart/env.rb +16 -0
- data/lib/rockstart/railtie.rb +6 -0
- data/lib/rockstart/version.rb +5 -0
- data/lib/rockstart.rb +9 -0
- data/lib/tasks/rockstart_tasks.rake +5 -0
- metadata +187 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rockstart::LoggingGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
def install_lograge
|
|
7
|
+
gem "lograge"
|
|
8
|
+
gem "logstash-event"
|
|
9
|
+
|
|
10
|
+
copy_file "rockstart/lograge_initializer.rb", "config/initializers/lograge.rb"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Rails.application.configure do
|
|
4
|
+
config.lograge.enabled = true
|
|
5
|
+
config.lograge.formatter = Lograge::Formatters::Logstash.new
|
|
6
|
+
|
|
7
|
+
# Update if using Rails in API mode
|
|
8
|
+
# config.lograge.base_controller_class = ['ActionController::API', 'ActionController::Base']
|
|
9
|
+
|
|
10
|
+
config.lograge.custom_options = lambda do |event|
|
|
11
|
+
exceptions = %w[controller action format id]
|
|
12
|
+
{
|
|
13
|
+
params: event.payload[:params].except(*exceptions)
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.lograge.custom_payload do |controller|
|
|
18
|
+
{
|
|
19
|
+
host: controller.request.host,
|
|
20
|
+
remote_ip: controller.request.remote_ip,
|
|
21
|
+
request_id: controller.request.request_id,
|
|
22
|
+
user_id: (controller.respond_to?(:current_user) && controller.current_user.try(:id)) || :guest
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Report for rack_attack throttle responses in lograge
|
|
28
|
+
ActiveSupport::Notifications.subscribe("throttle.rack_attack") do |_name, start, finish, request_id, payload|
|
|
29
|
+
req = payload[:request]
|
|
30
|
+
|
|
31
|
+
filter_parameters = Rails.application.config.filter_parameters
|
|
32
|
+
params = ActiveSupport::ParameterFilter.new(filter_parameters).filter(req.params)
|
|
33
|
+
|
|
34
|
+
message_payload = {
|
|
35
|
+
method: req.request_method,
|
|
36
|
+
path: req.path,
|
|
37
|
+
format: params[:format] || "html",
|
|
38
|
+
controller: Rack::Attack.name,
|
|
39
|
+
action: "throttle",
|
|
40
|
+
status: 429,
|
|
41
|
+
duration: (finish - start).to_f.round(2),
|
|
42
|
+
params: params.except("controller", "action", "format", "id"),
|
|
43
|
+
host: req.host,
|
|
44
|
+
remote_ip: req.ip,
|
|
45
|
+
request_id: request_id
|
|
46
|
+
}
|
|
47
|
+
formatted_message = Lograge.lograge_config.formatter.call(message_payload)
|
|
48
|
+
logger = Lograge.logger.presence || Rails.logger
|
|
49
|
+
logger.public_send(Lograge.log_level, formatted_message)
|
|
50
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rockstart::PostgresGenerator < Rails::Generators::Base
|
|
4
|
+
include Rails::Generators::AppName
|
|
5
|
+
include Rails::Generators::Migration
|
|
6
|
+
|
|
7
|
+
# Implement the required interface for Rails::Generators::Migration.
|
|
8
|
+
def self.next_migration_number(dirname)
|
|
9
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
10
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
source_root File.expand_path("templates", __dir__)
|
|
14
|
+
|
|
15
|
+
def copy_database_config
|
|
16
|
+
template "config/database.yml.tt", "config/database.yml"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def enable_uuid_support
|
|
20
|
+
migration_template "migration.rb.tt", "db/migrate/enable_uuid.rb"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def rails5_and_up?
|
|
26
|
+
Rails::VERSION::MAJOR >= 5
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def migration_version
|
|
30
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" if rails5_and_up?
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
default: &default
|
|
2
|
+
adapter: postgresql
|
|
3
|
+
encoding: unicode
|
|
4
|
+
# For details on connection pooling, see Rails configuration guide
|
|
5
|
+
# https://guides.rubyonrails.org/configuring.html#database-pooling
|
|
6
|
+
pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
|
7
|
+
|
|
8
|
+
development:
|
|
9
|
+
<<: *default
|
|
10
|
+
database: <%= app_name %>_development
|
|
11
|
+
|
|
12
|
+
test:
|
|
13
|
+
<<: *default
|
|
14
|
+
url: <%%= ENV.fetch('TEST_DATABASE_URL', "postgres://localhost/<%= app_name %>_test") %>
|
|
15
|
+
|
|
16
|
+
production:
|
|
17
|
+
<<: *default
|
|
18
|
+
url: <%%= ENV['DATABASE_URL'] %>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rockstart::PunditGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
def install_pundit
|
|
7
|
+
gem "pundit"
|
|
8
|
+
gem "pundit-matchers", group: :test
|
|
9
|
+
|
|
10
|
+
Bundler.clean_system("bundle install --quiet")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add_pundit_exception_handling
|
|
14
|
+
application <<~PUNDIT
|
|
15
|
+
# Treat Pundit authentication failures as forbidden
|
|
16
|
+
config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden
|
|
17
|
+
PUNDIT
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_pundit_to_application_controller
|
|
21
|
+
inject_into_file "app/controllers/application_controller.rb",
|
|
22
|
+
" include Pundit\n",
|
|
23
|
+
before: /^end$/
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def add_pundit_overrides_and_helpers
|
|
27
|
+
directory "app"
|
|
28
|
+
directory "config"
|
|
29
|
+
directory "lib"
|
|
30
|
+
directory "spec"
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/generators/rockstart/pundit/templates/app/controllers/concerns/pundit_error_handling.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Handling for any Pundit-thrown errors
|
|
4
|
+
module PunditErrorHandling
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
|
|
13
|
+
# redirect path for failed authentication attempts
|
|
14
|
+
def authentication_failed_redirect_path_for(_resource)
|
|
15
|
+
after_sign_in_path_for(current_user)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def user_not_authorized(exception)
|
|
21
|
+
policy_name = exception.policy.class.to_s.underscore
|
|
22
|
+
|
|
23
|
+
# e.g. I18n.t("pundit.example_policy.show?")
|
|
24
|
+
flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default
|
|
25
|
+
|
|
26
|
+
# redirect to the either the previous page or the default sign in page
|
|
27
|
+
redirect_to authentication_failed_redirect_path_for(exception.record)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Default application policy generated by rockstart
|
|
4
|
+
class ApplicationPolicy
|
|
5
|
+
attr_reader :user, :record
|
|
6
|
+
|
|
7
|
+
def initialize(user, record)
|
|
8
|
+
@user = user || User.new
|
|
9
|
+
@record = record
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Abilities
|
|
13
|
+
|
|
14
|
+
def index?
|
|
15
|
+
false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def show?
|
|
19
|
+
false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def new?
|
|
27
|
+
create?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def update?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def edit?
|
|
35
|
+
update?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def destroy?
|
|
39
|
+
false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Attributes
|
|
43
|
+
|
|
44
|
+
def permitted_attributes
|
|
45
|
+
[]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def permitted_attributes_for_create
|
|
49
|
+
permitted_attributes
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def permitted_attributes_for_update
|
|
53
|
+
permitted_attributes
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Scopes
|
|
57
|
+
|
|
58
|
+
# Scope filter used to limit access to resources
|
|
59
|
+
class Scope
|
|
60
|
+
attr_reader :user, :scope
|
|
61
|
+
|
|
62
|
+
def initialize(user, scope)
|
|
63
|
+
@user = user || User.new
|
|
64
|
+
@scope = scope
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resolve
|
|
68
|
+
scope.none
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Policy for updating profiles, provided by rockstart
|
|
4
|
+
class UserPolicy < ApplicationPolicy
|
|
5
|
+
# def index?
|
|
6
|
+
# false
|
|
7
|
+
# end
|
|
8
|
+
|
|
9
|
+
# def show?
|
|
10
|
+
# false
|
|
11
|
+
# end
|
|
12
|
+
|
|
13
|
+
# def create?
|
|
14
|
+
# false
|
|
15
|
+
# end
|
|
16
|
+
|
|
17
|
+
def update?
|
|
18
|
+
current_user?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def destroy?
|
|
22
|
+
# Prevent admins from destroying themselves
|
|
23
|
+
current_user? && !record.admin?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def permitted_attributes
|
|
27
|
+
if current_user?
|
|
28
|
+
# Allow a user to update their own details
|
|
29
|
+
%i[name]
|
|
30
|
+
else
|
|
31
|
+
[]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def current_user?
|
|
38
|
+
user.persisted? && user.id == record.id
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Safe scope for User
|
|
42
|
+
class Scope < Scope
|
|
43
|
+
def resolve
|
|
44
|
+
user.persisted? ? scope.where(id: user.id) : scope.none
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
<% module_namespacing do -%>
|
|
4
|
+
class <%= class_name %>Policy < ApplicationPolicy
|
|
5
|
+
def index?
|
|
6
|
+
user.persisted?
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def show?
|
|
10
|
+
user.persisted?
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create?
|
|
14
|
+
user.admin?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def update?
|
|
18
|
+
user.admin?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def destroy?
|
|
22
|
+
user.admin?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def permitted_attributes
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Safe scope for <%= class_name %>
|
|
30
|
+
class Scope < Scope
|
|
31
|
+
def resolve
|
|
32
|
+
user.persisted? ? scope.all : scope.none
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
<% end -%>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe <%= class_name %>Policy, type: :policy do
|
|
6
|
+
subject { described_class.new(user, <%= file_name %>_record) }
|
|
7
|
+
|
|
8
|
+
let(:resolved_scope) { described_class::Scope.new(user, <%= class_name %>.all).resolve }
|
|
9
|
+
|
|
10
|
+
let(:<%= file_name %>_record) { <%= class_name %>.new }
|
|
11
|
+
|
|
12
|
+
context "with a guest" do
|
|
13
|
+
let(:user) { nil }
|
|
14
|
+
|
|
15
|
+
it { is_expected.to forbid_action(:index) }
|
|
16
|
+
it { is_expected.to forbid_action(:show) }
|
|
17
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
18
|
+
it { is_expected.to forbid_actions(%i[edit update]) }
|
|
19
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
20
|
+
|
|
21
|
+
it "returns no items in scope" do
|
|
22
|
+
expect(resolved_scope.to_sql).to eq(<%= class_name %>.none.to_sql)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "with a user" do
|
|
27
|
+
let(:user) { build_stubbed(:user) }
|
|
28
|
+
|
|
29
|
+
it { is_expected.to permit_action(:index) }
|
|
30
|
+
it { is_expected.to permit_action(:show) }
|
|
31
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
32
|
+
it { is_expected.to forbid_actions(%i[edit update]) }
|
|
33
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
34
|
+
|
|
35
|
+
it "returns all items in scope" do
|
|
36
|
+
expect(resolved_scope.to_sql).to eq(<%= class_name %>.all.to_sql)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "with an admin" do
|
|
41
|
+
let(:user) { build_stubbed(:user, :admin) }
|
|
42
|
+
|
|
43
|
+
it { is_expected.to permit_action(:index) }
|
|
44
|
+
it { is_expected.to permit_action(:show) }
|
|
45
|
+
it { is_expected.to permit_actions(%i[new create]) }
|
|
46
|
+
it { is_expected.to permit_actions(%i[edit update]) }
|
|
47
|
+
it { is_expected.to permit_action(:destroy) }
|
|
48
|
+
|
|
49
|
+
# it { is_expected.to permit_mass_assignment_of(:name).for_action(:create) }
|
|
50
|
+
|
|
51
|
+
# it { is_expected.to permit_mass_assignment_of(:name).for_action(:update) }
|
|
52
|
+
# it { is_expected.to forbid_mass_assignment_of(:birthdate).for_action(:update) }
|
|
53
|
+
|
|
54
|
+
it "returns all items in scope" do
|
|
55
|
+
expect(resolved_scope.to_sql).to eq(<%= class_name %>.all.to_sql)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_helper"
|
|
4
|
+
|
|
5
|
+
RSpec.describe UserPolicy, type: :policy do
|
|
6
|
+
subject { described_class.new(user, user_record) }
|
|
7
|
+
|
|
8
|
+
let(:resolved_scope) { described_class::Scope.new(user, User.all).resolve }
|
|
9
|
+
|
|
10
|
+
let(:user_record) do
|
|
11
|
+
build_stubbed(:user).tap do |record|
|
|
12
|
+
allow(record).to receive(:id).and_return(123)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
context "with a guest" do
|
|
17
|
+
let(:user) { nil }
|
|
18
|
+
|
|
19
|
+
it { is_expected.to forbid_action(:index) }
|
|
20
|
+
it { is_expected.to forbid_action(:show) }
|
|
21
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
22
|
+
it { is_expected.to forbid_actions(%i[edit update]) }
|
|
23
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
24
|
+
|
|
25
|
+
it "returns no items in scope" do
|
|
26
|
+
expect(resolved_scope.to_sql).to eq(User.none.to_sql)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
context "with the same user" do
|
|
31
|
+
let(:user) { user_record }
|
|
32
|
+
|
|
33
|
+
it { is_expected.to permit_actions(%i[edit update]) }
|
|
34
|
+
it { is_expected.to permit_action(:destroy) }
|
|
35
|
+
|
|
36
|
+
it { is_expected.to forbid_action(:index) }
|
|
37
|
+
it { is_expected.to forbid_action(:show) }
|
|
38
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
39
|
+
|
|
40
|
+
it { is_expected.to permit_mass_assignment_of(:name).for_action(:update) }
|
|
41
|
+
|
|
42
|
+
it "returns the a scope with only the user" do
|
|
43
|
+
expect(resolved_scope.to_sql).to eq(User.where(id: user.id).to_sql)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context "when the user is an admin" do
|
|
47
|
+
before do
|
|
48
|
+
user.admin = true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it { is_expected.to permit_actions(%i[edit update]) }
|
|
52
|
+
|
|
53
|
+
it { is_expected.to forbid_action(:index) }
|
|
54
|
+
it { is_expected.to forbid_action(:show) }
|
|
55
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
56
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
57
|
+
|
|
58
|
+
it { is_expected.to permit_mass_assignment_of(:name).for_action(:update) }
|
|
59
|
+
|
|
60
|
+
it "returns the a scope with only the user" do
|
|
61
|
+
expect(resolved_scope.to_sql).to eq(User.where(id: user.id).to_sql)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
context "with a different user" do
|
|
67
|
+
let(:user) do
|
|
68
|
+
build_stubbed(:user).tap do |user|
|
|
69
|
+
allow(user).to receive(:id).and_return(987)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it { is_expected.to forbid_action(:index) }
|
|
74
|
+
it { is_expected.to forbid_action(:show) }
|
|
75
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
76
|
+
it { is_expected.to forbid_actions(%i[edit update]) }
|
|
77
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
78
|
+
|
|
79
|
+
it { is_expected.to forbid_mass_assignment_of(:name).for_action(:update) }
|
|
80
|
+
|
|
81
|
+
context "when the user is an admin" do
|
|
82
|
+
before do
|
|
83
|
+
user.admin = true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it { is_expected.to forbid_action(:index) }
|
|
87
|
+
it { is_expected.to forbid_action(:show) }
|
|
88
|
+
it { is_expected.to forbid_actions(%i[new create]) }
|
|
89
|
+
it { is_expected.to forbid_actions(%i[edit update]) }
|
|
90
|
+
it { is_expected.to forbid_action(:destroy) }
|
|
91
|
+
|
|
92
|
+
it { is_expected.to forbid_mass_assignment_of(:name).for_action(:update) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Description:
|
|
2
|
+
Adds code quality tools to a Rails project.
|
|
3
|
+
|
|
4
|
+
Example:
|
|
5
|
+
rails generate quality
|
|
6
|
+
|
|
7
|
+
This will create:
|
|
8
|
+
.rubocop.yml # with common default values
|
|
9
|
+
.rubocop_todo.yml # a todo-list of all outstanding issues
|
|
10
|
+
lib/tasks/quality.rake # A Rake task for running code quality tools
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Rockstart::QualityGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
desc "This generator configures code quality rules"
|
|
7
|
+
|
|
8
|
+
class_option :rebuild_todo, type: :boolean,
|
|
9
|
+
desc: "Bundle installs rubocop and regenerates .rubocop_todo.yml",
|
|
10
|
+
default: true
|
|
11
|
+
|
|
12
|
+
def add_quality_rake_task
|
|
13
|
+
copy_file "quality.rake", "lib/tasks/quality.rake"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def install_rubocop
|
|
17
|
+
copy_file "rubocop.yml", ".rubocop.yml"
|
|
18
|
+
|
|
19
|
+
gem "rubocop-rails", require: false
|
|
20
|
+
|
|
21
|
+
copy_file "rubocop.rake", "lib/tasks/rubocop.rake"
|
|
22
|
+
|
|
23
|
+
return unless options[:rebuild_todo]
|
|
24
|
+
|
|
25
|
+
# Rebuild .rubocop_todo.yml, ensuring only existing code is excluded
|
|
26
|
+
run "bundle install --quiet && bundle exec rubocop --auto-gen-config --exclude-limit 100"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
AllCops:
|
|
4
|
+
Exclude:
|
|
5
|
+
# Exclude auto-generated files
|
|
6
|
+
- bin/*
|
|
7
|
+
- db/schema.rb
|
|
8
|
+
- db/seeds.rb
|
|
9
|
+
- lib/templates/**/*
|
|
10
|
+
- node_modules/**/*
|
|
11
|
+
|
|
12
|
+
Layout/LineLength:
|
|
13
|
+
Max: 100 # Set line width to more sensible default
|
|
14
|
+
Exclude:
|
|
15
|
+
- config/environments/*
|
|
16
|
+
- config/initializers/*
|
|
17
|
+
- db/migrate/*
|
|
18
|
+
- spec/**/*
|
|
19
|
+
- test/**/*
|
|
20
|
+
|
|
21
|
+
Lint/RaiseException:
|
|
22
|
+
Enabled: true
|
|
23
|
+
|
|
24
|
+
Lint/StructNewOverride:
|
|
25
|
+
Enabled: true
|
|
26
|
+
|
|
27
|
+
Metrics/BlockLength:
|
|
28
|
+
Exclude:
|
|
29
|
+
- spec/**/*
|
|
30
|
+
- test/**/*
|
|
31
|
+
|
|
32
|
+
Style/StringLiterals:
|
|
33
|
+
EnforcedStyle: double_quotes # prefer double quotes
|
|
34
|
+
|
|
35
|
+
Style/FrozenStringLiteralComment:
|
|
36
|
+
EnforcedStyle: always_true # immutable code where possible
|
|
37
|
+
|
|
38
|
+
Style/HashEachMethods:
|
|
39
|
+
Enabled: false # Skip unsafe rules
|
|
40
|
+
|
|
41
|
+
Style/HashTransformKeys:
|
|
42
|
+
Enabled: false # Skip unsafe rules
|
|
43
|
+
|
|
44
|
+
Style/HashTransformValues:
|
|
45
|
+
Enabled: false # Skip unsafe rules
|