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