rockstart 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +100 -0
  4. data/Rakefile +19 -0
  5. data/lib/generators/rockstart/USAGE +13 -0
  6. data/lib/generators/rockstart/devise/USAGE +9 -0
  7. data/lib/generators/rockstart/devise/devise_generator.rb +258 -0
  8. data/lib/generators/rockstart/devise/templates/controllers/passwords_controller.rb +56 -0
  9. data/lib/generators/rockstart/devise/templates/controllers/registrations_controller.rb +88 -0
  10. data/lib/generators/rockstart/devise/templates/controllers/sessions_controller.rb +32 -0
  11. data/lib/generators/rockstart/devise/templates/create_user_migration.rb.tt +11 -0
  12. data/lib/generators/rockstart/devise/templates/models/user.rb +42 -0
  13. data/lib/generators/rockstart/devise/templates/spec/factories/users.rb +17 -0
  14. data/lib/generators/rockstart/devise/templates/spec/models/user_spec.rb +64 -0
  15. data/lib/generators/rockstart/devise/templates/spec/requests/users/passwords_spec.rb +202 -0
  16. data/lib/generators/rockstart/devise/templates/spec/requests/users/registrations_spec.rb +445 -0
  17. data/lib/generators/rockstart/devise/templates/spec/requests/users/sessions_spec.rb +171 -0
  18. data/lib/generators/rockstart/devise/templates/spec/support/devise_request_spec_helper.rb +29 -0
  19. data/lib/generators/rockstart/devise/templates/translations.en.yml +4 -0
  20. data/lib/generators/rockstart/docker/USAGE +10 -0
  21. data/lib/generators/rockstart/docker/docker_generator.rb +86 -0
  22. data/lib/generators/rockstart/docker/templates/app/Dockerfile-app +47 -0
  23. data/lib/generators/rockstart/docker/templates/docker-compose.test.yml +29 -0
  24. data/lib/generators/rockstart/docker/templates/docker-compose.yml +47 -0
  25. data/lib/generators/rockstart/docker/templates/dockerignore +16 -0
  26. data/lib/generators/rockstart/docker/templates/dotenv.docker.tt +4 -0
  27. data/lib/generators/rockstart/docker/templates/localhost_domains.ext.tt +7 -0
  28. data/lib/generators/rockstart/docker/templates/setup-localhost.tt +27 -0
  29. data/lib/generators/rockstart/docker/templates/web/Dockerfile-web +15 -0
  30. data/lib/generators/rockstart/docker/templates/web/nginx.conf +62 -0
  31. data/lib/generators/rockstart/frontend_helpers/USAGE +8 -0
  32. data/lib/generators/rockstart/frontend_helpers/frontend_helpers_generator.rb +65 -0
  33. data/lib/generators/rockstart/frontend_helpers/templates/application_urls.rb +26 -0
  34. data/lib/generators/rockstart/frontend_helpers/templates/application_urls_helper.rb +20 -0
  35. data/lib/generators/rockstart/frontend_helpers/templates/titles.en.yml.tt +5 -0
  36. data/lib/generators/rockstart/logging/USAGE +8 -0
  37. data/lib/generators/rockstart/logging/logging_generator.rb +12 -0
  38. data/lib/generators/rockstart/logging/templates/rockstart/lograge_initializer.rb +50 -0
  39. data/lib/generators/rockstart/postgres/USAGE +8 -0
  40. data/lib/generators/rockstart/postgres/postgres_generator.rb +32 -0
  41. data/lib/generators/rockstart/postgres/templates/config/database.yml.tt +18 -0
  42. data/lib/generators/rockstart/postgres/templates/migration.rb.tt +7 -0
  43. data/lib/generators/rockstart/pundit/USAGE +8 -0
  44. data/lib/generators/rockstart/pundit/pundit_generator.rb +32 -0
  45. data/lib/generators/rockstart/pundit/templates/app/controllers/concerns/pundit_error_handling.rb +29 -0
  46. data/lib/generators/rockstart/pundit/templates/app/policies/application_policy.rb +71 -0
  47. data/lib/generators/rockstart/pundit/templates/app/policies/user_policy.rb +47 -0
  48. data/lib/generators/rockstart/pundit/templates/config/locales/pundit.en.yml +6 -0
  49. data/lib/generators/rockstart/pundit/templates/lib/templates/pundit/policy/policy.rb +36 -0
  50. data/lib/generators/rockstart/pundit/templates/lib/templates/rspec/policy/policy_spec.rb +58 -0
  51. data/lib/generators/rockstart/pundit/templates/spec/policies/user_policy_spec.rb +95 -0
  52. data/lib/generators/rockstart/pundit/templates/spec/support/pundit_matchers.rb +7 -0
  53. data/lib/generators/rockstart/quality/USAGE +10 -0
  54. data/lib/generators/rockstart/quality/quality_generator.rb +28 -0
  55. data/lib/generators/rockstart/quality/templates/quality.rake +4 -0
  56. data/lib/generators/rockstart/quality/templates/rubocop.rake +4 -0
  57. data/lib/generators/rockstart/quality/templates/rubocop.yml +45 -0
  58. data/lib/generators/rockstart/rockstart_generator.rb +77 -0
  59. data/lib/generators/rockstart/rspec/USAGE +8 -0
  60. data/lib/generators/rockstart/rspec/rspec_generator.rb +70 -0
  61. data/lib/generators/rockstart/rspec/templates/dotenv.development +1 -0
  62. data/lib/generators/rockstart/rspec/templates/dotenv.test +1 -0
  63. data/lib/generators/rockstart/rspec/templates/rspec_templates/model/model_spec.rb +13 -0
  64. data/lib/generators/rockstart/rspec/templates/support/factory_bot.rb +6 -0
  65. data/lib/generators/rockstart/rspec/templates/support/shoulda_matchers.rb +9 -0
  66. data/lib/generators/rockstart/rspec/templates/support/test_helpers.rb +9 -0
  67. data/lib/generators/rockstart/scaffold_templates/USAGE +8 -0
  68. data/lib/generators/rockstart/scaffold_templates/scaffold_templates_generator.rb +39 -0
  69. data/lib/generators/rockstart/scaffold_templates/templates/api_controller.rb.tt +96 -0
  70. data/lib/generators/rockstart/scaffold_templates/templates/controller.rb.tt +126 -0
  71. data/lib/generators/rockstart/scaffold_templates/templates/rspec/scaffold/api_request_spec.rb +139 -0
  72. data/lib/generators/rockstart/scaffold_templates/templates/rspec/scaffold/request_spec.rb +408 -0
  73. data/lib/generators/rockstart/security/USAGE +13 -0
  74. data/lib/generators/rockstart/security/security_generator.rb +108 -0
  75. data/lib/generators/rockstart/security/templates/brakeman.rake +6 -0
  76. data/lib/generators/rockstart/security/templates/bundler_audit.rake +4 -0
  77. data/lib/generators/rockstart/security/templates/cache_support.rb +18 -0
  78. data/lib/generators/rockstart/security/templates/content_security_policy_initializer.rb.tt +56 -0
  79. data/lib/generators/rockstart/security/templates/content_security_spec.rb.tt +83 -0
  80. data/lib/generators/rockstart/security/templates/csp_violations_controller.rb +39 -0
  81. data/lib/generators/rockstart/security/templates/rack_attack.rb +98 -0
  82. data/lib/generators/rockstart/security/templates/security.rake +9 -0
  83. data/lib/generators/rockstart/security/templates/session_store_initializer.rb.tt +7 -0
  84. data/lib/generators/rockstart/smtp_mailer/USAGE +8 -0
  85. data/lib/generators/rockstart/smtp_mailer/smtp_mailer_generator.rb +30 -0
  86. data/lib/generators/rockstart/smtp_mailer/templates/config/initializers/action_mailer.rb +10 -0
  87. data/lib/generators/rockstart/tailwindcss/USAGE +8 -0
  88. data/lib/generators/rockstart/tailwindcss/tailwindcss_generator.rb +30 -0
  89. data/lib/generators/rockstart/tailwindcss/templates/application.css +3 -0
  90. data/lib/generators/rockstart/tailwindcss/templates/postcss.config.js +32 -0
  91. data/lib/rockstart/base_generator.rb +32 -0
  92. data/lib/rockstart/env.rb +16 -0
  93. data/lib/rockstart/railtie.rb +6 -0
  94. data/lib/rockstart/version.rb +5 -0
  95. data/lib/rockstart.rb +9 -0
  96. data/lib/tasks/rockstart_tasks.rake +5 -0
  97. metadata +187 -0
@@ -0,0 +1,5 @@
1
+ en:
2
+ titles:
3
+ # titles.application defaults to the sigficant portion of
4
+ # <%= Rails.application.class.name %>, which would be:
5
+ application: <%= default_title %>
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Adds better logging to a rails application
3
+
4
+ Example:
5
+ rails generate rockstart:logging
6
+
7
+ This will create:
8
+ - Adds and configures lograge
@@ -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,8 @@
1
+ Description:
2
+ Configures a rails application to use postgres in Heroku and Docker
3
+
4
+ Example:
5
+ rails generate rockstart:postgres
6
+
7
+ This will create:
8
+ A config/database.yml with known defaults
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class EnableUuid < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ enable_extension "pgcrypto"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Adds authentication to a project with simple authentication needs
3
+
4
+ Example:
5
+ rails generate rockstart:pundit
6
+
7
+ This will create:
8
+ Installs pundit gem
@@ -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
@@ -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,6 @@
1
+ en:
2
+ pundit:
3
+ default: 'You cannot perform this action.'
4
+ # example_policy:
5
+ # update?: 'You cannot edit this Example!'
6
+ # create?: 'You cannot create Examples!'
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pundit/matchers"
4
+
5
+ Pundit::Matchers.configure do |config|
6
+ config.user_alias = :user
7
+ 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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ desc "Run all code quality tools"
4
+ task quality: ["rubocop"]
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubocop/rake_task"
4
+ RuboCop::RakeTask.new
@@ -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