wor-simple_crud 0.2.1

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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +52 -0
  3. data/.gitignore +338 -0
  4. data/.overcommit.yml +15 -0
  5. data/.rubocop.yml +16 -0
  6. data/Appraisals +40 -0
  7. data/CHANGELOG.md +12 -0
  8. data/Gemfile +42 -0
  9. data/LICENSE.md +22 -0
  10. data/README.md +286 -0
  11. data/Rakefile +6 -0
  12. data/lib/simple_crud/authorization/action_policy_adapter.rb +22 -0
  13. data/lib/simple_crud/authorization/adapter.rb +20 -0
  14. data/lib/simple_crud/authorization/can_can_can_adapter.rb +22 -0
  15. data/lib/simple_crud/authorization/pundit_adapter.rb +21 -0
  16. data/lib/simple_crud/config.rb +24 -0
  17. data/lib/simple_crud/pagination/adapter.rb +14 -0
  18. data/lib/simple_crud/pagination/kaminari_adapter.rb +19 -0
  19. data/lib/simple_crud/pagination/pagy_adapter.rb +22 -0
  20. data/lib/simple_crud/pagination/will_paginate_adapter.rb +22 -0
  21. data/lib/simple_crud/pagination/wor_paginate_adapter.rb +17 -0
  22. data/lib/simple_crud/rspec.rb +11 -0
  23. data/lib/simple_crud/simple_crud_controller.rb +125 -0
  24. data/lib/simple_crud/version.rb +5 -0
  25. data/lib/simple_crud.rb +12 -0
  26. data/lib/spec/matchers/have_been_serialized_with.rb +30 -0
  27. data/lib/spec/response_helper.rb +10 -0
  28. data/lib/spec/shared_contexts/authenticate_user.rb +11 -0
  29. data/lib/spec/shared_examples/helpers.rb +50 -0
  30. data/lib/spec/shared_examples/simple_crud_for_create.rb +49 -0
  31. data/lib/spec/shared_examples/simple_crud_for_destroy.rb +68 -0
  32. data/lib/spec/shared_examples/simple_crud_for_index.rb +56 -0
  33. data/lib/spec/shared_examples/simple_crud_for_show.rb +51 -0
  34. data/lib/spec/shared_examples/simple_crud_for_update.rb +61 -0
  35. data/lib/spec/shared_examples/unauthorized_when_not_logged_in.rb +10 -0
  36. data/pull_request_template.md +3 -0
  37. data/spec/dummy/Rakefile +8 -0
  38. data/spec/dummy/app/assets/config/manifest.js +6 -0
  39. data/spec/dummy/app/controllers/application_controller.rb +38 -0
  40. data/spec/dummy/app/controllers/dummy_models_controller.rb +23 -0
  41. data/spec/dummy/app/controllers/without_pagination/dummy_models_controller.rb +15 -0
  42. data/spec/dummy/app/models/application_record.rb +5 -0
  43. data/spec/dummy/app/models/concerns/.keep +0 -0
  44. data/spec/dummy/app/models/dummy_model.rb +7 -0
  45. data/spec/dummy/app/models/dummy_model_policy.rb +34 -0
  46. data/spec/dummy/app/models/user.rb +11 -0
  47. data/spec/dummy/app/serializers/dummy_model_serializer.rb +5 -0
  48. data/spec/dummy/bin/bundle +5 -0
  49. data/spec/dummy/bin/rails +6 -0
  50. data/spec/dummy/bin/rake +6 -0
  51. data/spec/dummy/bin/setup +35 -0
  52. data/spec/dummy/bin/update +30 -0
  53. data/spec/dummy/config/application.rb +17 -0
  54. data/spec/dummy/config/boot.rb +10 -0
  55. data/spec/dummy/config/database.yml +25 -0
  56. data/spec/dummy/config/environment.rb +7 -0
  57. data/spec/dummy/config/environments/development.rb +46 -0
  58. data/spec/dummy/config/environments/test.rb +47 -0
  59. data/spec/dummy/config/initializers/devise.rb +303 -0
  60. data/spec/dummy/config/locales/devise.en.yml +65 -0
  61. data/spec/dummy/config/puma.rb +49 -0
  62. data/spec/dummy/config/routes.rb +10 -0
  63. data/spec/dummy/config/secrets.yml +22 -0
  64. data/spec/dummy/config/spring.rb +8 -0
  65. data/spec/dummy/config.ru +7 -0
  66. data/spec/dummy/db/migrate/20170626184159_create_dummy_models.rb +10 -0
  67. data/spec/dummy/db/migrate/20200110191019_devise_create_users.rb +43 -0
  68. data/spec/dummy/db/migrate/20200120165657_add_user_id_to_dummy_models.rb +7 -0
  69. data/spec/dummy/db/schema.rb +36 -0
  70. data/spec/dummy/lib/assets/.keep +0 -0
  71. data/spec/dummy/log/.keep +0 -0
  72. data/spec/dummy/spec/factories/dummy_models.rb +9 -0
  73. data/spec/dummy/spec/factories/users.rb +8 -0
  74. data/spec/dummy_controller_spec.rb +19 -0
  75. data/spec/dummy_model_policy_spec.rb +29 -0
  76. data/spec/dummy_model_serializer_spec.rb +36 -0
  77. data/spec/simple_crud/authorization/action_policy_adapter_spec.rb +58 -0
  78. data/spec/simple_crud/authorization/can_can_can_adapter_spec.rb +53 -0
  79. data/spec/simple_crud/pagination/adapters_spec.rb +48 -0
  80. data/spec/simple_crud_configuration_spec.rb +61 -0
  81. data/spec/simple_crud_controller_spec.rb +41 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/spy.rb +41 -0
  84. data/spec/without_pagination/dummy_models_controller_spec.rb +6 -0
  85. data/wor-simple_crud.gemspec +23 -0
  86. metadata +145 -0
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DeviseCreateUsers < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :users do |t|
6
+ ## Database authenticatable
7
+ t.string :email, null: false, default: ''
8
+ t.string :encrypted_password, null: false, default: ''
9
+
10
+ ## Recoverable
11
+ t.string :reset_password_token
12
+ t.datetime :reset_password_sent_at
13
+
14
+ ## Rememberable
15
+ t.datetime :remember_created_at
16
+
17
+ ## Trackable
18
+ # t.integer :sign_in_count, default: 0, null: false
19
+ # t.datetime :current_sign_in_at
20
+ # t.datetime :last_sign_in_at
21
+ # t.string :current_sign_in_ip
22
+ # t.string :last_sign_in_ip
23
+
24
+ ## Confirmable
25
+ # t.string :confirmation_token
26
+ # t.datetime :confirmed_at
27
+ # t.datetime :confirmation_sent_at
28
+ # t.string :unconfirmed_email # Only if using reconfirmable
29
+
30
+ ## Lockable
31
+ # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
32
+ # t.string :unlock_token # Only if unlock strategy is :email or :both
33
+ # t.datetime :locked_at
34
+
35
+ t.timestamps null: false
36
+ end
37
+
38
+ add_index :users, :email, unique: true
39
+ add_index :users, :reset_password_token, unique: true
40
+ # add_index :users, :confirmation_token, unique: true
41
+ # add_index :users, :unlock_token, unique: true
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddUserIdToDummyModels < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_reference :dummy_models, :user, foreign_key: true
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is auto-generated from the current state of the database. Instead
4
+ # of editing this file, please use the migrations feature of Active Record to
5
+ # incrementally modify your database, and then regenerate this schema definition.
6
+ #
7
+ # This file is the source Rails uses to define your schema when running `rails
8
+ # db:schema:load`. When creating a new database, `rails db:schema:load` tends to
9
+ # be faster and is potentially less error prone than running all of your
10
+ # migrations from scratch. Old migrations may fail to apply correctly if those
11
+ # migrations use external dependencies or application code.
12
+ #
13
+ # It's strongly recommended that you check this file into your version control system.
14
+
15
+ ActiveRecord::Schema.define(version: 20_200_120_165_657) do
16
+ create_table 'dummy_models', force: :cascade do |t|
17
+ t.string 'name'
18
+ t.integer 'something'
19
+ t.integer 'user_id'
20
+ t.index ['user_id'], name: 'index_dummy_models_on_user_id'
21
+ end
22
+
23
+ create_table 'users', force: :cascade do |t|
24
+ t.string 'email', default: '', null: false
25
+ t.string 'encrypted_password', default: '', null: false
26
+ t.string 'reset_password_token'
27
+ t.datetime 'reset_password_sent_at'
28
+ t.datetime 'remember_created_at'
29
+ t.datetime 'created_at', precision: 6, null: false
30
+ t.datetime 'updated_at', precision: 6, null: false
31
+ t.index ['email'], name: 'index_users_on_email', unique: true
32
+ t.index ['reset_password_token'], name: 'index_users_on_reset_password_token', unique: true
33
+ end
34
+
35
+ add_foreign_key 'dummy_models', 'users'
36
+ end
File without changes
File without changes
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :dummy_model do
5
+ name { rand(36 * 4).to_s(36) }
6
+ something { rand(100) }
7
+ user
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :user do
5
+ email { Faker::Internet.email }
6
+ password { Faker::Internet.password }
7
+ end
8
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ describe DummyModelsController, type: :controller do
5
+ include_examples 'simple crud for update'
6
+ include_examples 'simple crud for show'
7
+ include_examples 'simple crud for create'
8
+ include_examples 'simple crud for index'
9
+ include_examples 'simple crud for destroy'
10
+
11
+ describe 'POST #create with invalid attributes' do
12
+ include_context 'with authenticated user'
13
+
14
+ it 'responds with unprocessable entity' do
15
+ post :create, params: { something: 1, user_id: current_user.id }
16
+ expect(response).to have_http_status(ApplicationController::UNPROCESSABLE_STATUS)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe DummyModelPolicy do
6
+ let(:user) { create(:user) }
7
+ let(:other_user) { create(:user) }
8
+ let(:dummy_model) { create(:dummy_model, user: user) }
9
+
10
+ describe '#destroy?' do
11
+ it 'allows the owning user' do
12
+ expect(described_class.new(user, dummy_model).destroy?).to be true
13
+ end
14
+
15
+ it 'denies a different user' do
16
+ expect(described_class.new(other_user, dummy_model).destroy?).to be false
17
+ end
18
+ end
19
+
20
+ describe '#index?' do
21
+ it 'allows the owning user' do
22
+ expect(described_class.new(user, dummy_model).index?).to be true
23
+ end
24
+
25
+ it 'denies a different user' do
26
+ expect(described_class.new(other_user, dummy_model).index?).to be false
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe DummyModelSerializer do
6
+ let(:user) { create(:user) }
7
+ let(:dummy_model) { create(:dummy_model, user: user) }
8
+ let(:full_response) { { 'id' => dummy_model.id, 'something' => dummy_model.something, 'user_id' => user.id } }
9
+
10
+ it 'matches a response including all serializer attributes' do
11
+ expect(full_response).to have_been_serialized_with(described_class)
12
+ end
13
+
14
+ it 'does not match a response missing serializer attributes' do
15
+ expect({ 'id' => dummy_model.id }).not_to have_been_serialized_with(described_class)
16
+ end
17
+
18
+ it 'describes itself' do
19
+ expect(have_been_serialized_with(described_class).description)
20
+ .to eq("checks to see if all the serializer's attributes are in the JSON response")
21
+ end
22
+
23
+ it 'produces a failure message when it does not match' do
24
+ matcher = have_been_serialized_with(described_class)
25
+ matcher.matches?({ 'id' => dummy_model.id })
26
+
27
+ expect(matcher.failure_message).to match(/expected attributes of/)
28
+ end
29
+
30
+ it 'produces a negated failure message when it matches but was expected not to' do
31
+ matcher = have_been_serialized_with(described_class)
32
+ matcher.matches?(full_response)
33
+
34
+ expect(matcher.failure_message_when_negated).to match(/not to be included in/)
35
+ end
36
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'simple_crud/authorization/action_policy_adapter'
5
+
6
+ class ActionPolicyTestRecord
7
+ attr_reader :owner_id
8
+
9
+ def initialize(owner_id)
10
+ @owner_id = owner_id
11
+ end
12
+ end
13
+
14
+ class ActionPolicyTestRecordPolicy < ActionPolicy::Base
15
+ def show?
16
+ user == record.owner_id
17
+ end
18
+ end
19
+
20
+ class ActionPolicyTestController
21
+ include ActionPolicy::Controller
22
+ authorize :user, through: :current_user
23
+
24
+ attr_accessor :action_name, :current_user
25
+ end
26
+
27
+ describe SimpleCrud::Authorization::ActionPolicyAdapter do
28
+ subject(:adapter) { described_class.new }
29
+
30
+ let(:controller) do
31
+ ActionPolicyTestController.new.tap do |c|
32
+ c.action_name = 'show'
33
+ c.current_user = 1
34
+ end
35
+ end
36
+
37
+ describe '#authorize' do
38
+ it 'passes for a record the policy allows' do
39
+ expect { adapter.authorize(controller, ActionPolicyTestRecord.new(1)) }.not_to raise_error
40
+ end
41
+
42
+ it 'raises for a record the policy denies' do
43
+ expect do
44
+ adapter.authorize(controller, ActionPolicyTestRecord.new(2))
45
+ end.to raise_error(ActionPolicy::Unauthorized)
46
+ end
47
+ end
48
+
49
+ describe '#policy_defined?' do
50
+ it 'is true when a matching policy class exists' do
51
+ expect(adapter.policy_defined?(ActionPolicyTestRecord)).to be true
52
+ end
53
+
54
+ it 'is false when no matching policy class exists' do
55
+ expect(adapter.policy_defined?(String)).to be false
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'simple_crud/authorization/can_can_can_adapter'
5
+
6
+ class CanCanCanTestRecord
7
+ attr_reader :owner_id
8
+
9
+ def initialize(owner_id)
10
+ @owner_id = owner_id
11
+ end
12
+ end
13
+
14
+ class Ability
15
+ include CanCan::Ability
16
+
17
+ def initialize(user_id)
18
+ can :manage, CanCanCanTestRecord, owner_id: user_id
19
+ end
20
+ end
21
+
22
+ class CanCanCanTestController
23
+ include CanCan::ControllerAdditions
24
+
25
+ attr_accessor :action_name, :current_user
26
+ end
27
+
28
+ describe SimpleCrud::Authorization::CanCanCanAdapter do
29
+ subject(:adapter) { described_class.new }
30
+
31
+ let(:controller) do
32
+ CanCanCanTestController.new.tap do |c|
33
+ c.action_name = 'show'
34
+ c.current_user = 1
35
+ end
36
+ end
37
+
38
+ describe '#authorize' do
39
+ it 'passes for a record the ability grants' do
40
+ expect { adapter.authorize(controller, CanCanCanTestRecord.new(1)) }.not_to raise_error
41
+ end
42
+
43
+ it 'raises for a record the ability denies' do
44
+ expect { adapter.authorize(controller, CanCanCanTestRecord.new(2)) }.to raise_error(CanCan::AccessDenied)
45
+ end
46
+ end
47
+
48
+ describe '#policy_defined?' do
49
+ it 'is true when an Ability class exists' do
50
+ expect(adapter.policy_defined?(CanCanCanTestRecord)).to be true
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'simple_crud/pagination/kaminari_adapter'
5
+ require 'simple_crud/pagination/will_paginate_adapter'
6
+ require 'simple_crud/pagination/pagy_adapter'
7
+
8
+ describe 'pagination adapters', type: :request do
9
+ let(:user) { create(:user) }
10
+ let(:auth_headers) { Devise::JWT::TestHelpers.auth_headers({}, user) }
11
+
12
+ before do
13
+ create_list(:dummy_model, 15, user: user)
14
+ # Index authorizes against a blank klass.new (no owner), so the real
15
+ # ownership-based policy can never pass here.
16
+ allow(DummyModelPolicy).to receive(:new).and_return(instance_double(DummyModelPolicy, index?: true))
17
+ end
18
+
19
+ around do |example|
20
+ original_adapter = SimpleCrud::Config.pagination_adapter
21
+ example.run
22
+ SimpleCrud::Config.pagination_adapter = original_adapter
23
+ end
24
+
25
+ it 'paginates with Kaminari' do
26
+ SimpleCrud::Config.pagination_adapter = SimpleCrud::Pagination::KaminariAdapter.new
27
+
28
+ get '/dummy_models', params: { page: 2, per_page: 5 }, headers: auth_headers
29
+
30
+ expect(response.parsed_body.size).to eq(5)
31
+ end
32
+
33
+ it 'paginates with will_paginate' do
34
+ SimpleCrud::Config.pagination_adapter = SimpleCrud::Pagination::WillPaginateAdapter.new
35
+
36
+ get '/dummy_models', params: { page: 2, per_page: 5 }, headers: auth_headers
37
+
38
+ expect(response.parsed_body.size).to eq(5)
39
+ end
40
+
41
+ it 'paginates with Pagy' do
42
+ SimpleCrud::Config.pagination_adapter = SimpleCrud::Pagination::PagyAdapter.new
43
+
44
+ get '/dummy_models', params: { page: 2, limit: 5 }, headers: auth_headers
45
+
46
+ expect(response.parsed_body.size).to eq(5)
47
+ end
48
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'simple_crud/authorization/pundit_adapter'
5
+ require 'simple_crud/pagination/wor_paginate_adapter'
6
+
7
+ class IncompleteAuthorizationAdapter
8
+ include SimpleCrud::Authorization::Adapter
9
+ end
10
+
11
+ class IncompletePaginationAdapter
12
+ include SimpleCrud::Pagination::Adapter
13
+ end
14
+
15
+ describe SimpleCrud do
16
+ describe '.configure' do
17
+ it 'yields SimpleCrud::Config' do
18
+ expect { |b| described_class.configure(&b) }.to yield_with_args(SimpleCrud::Config)
19
+ end
20
+
21
+ it 'lets a custom authorization adapter replace the Pundit default' do
22
+ original_adapter = SimpleCrud::Config.authorization_adapter
23
+ custom_adapter = instance_double(SimpleCrud::Authorization::PunditAdapter)
24
+
25
+ described_class.configure { |config| config.authorization_adapter = custom_adapter }
26
+ expect(SimpleCrud::Config.authorization_adapter).to eq(custom_adapter)
27
+
28
+ SimpleCrud::Config.authorization_adapter = original_adapter
29
+ end
30
+
31
+ it 'lets a custom pagination adapter replace the wor-paginate default' do
32
+ original_adapter = SimpleCrud::Config.pagination_adapter
33
+ custom_adapter = instance_double(SimpleCrud::Pagination::WorPaginateAdapter)
34
+
35
+ described_class.configure { |config| config.pagination_adapter = custom_adapter }
36
+ expect(SimpleCrud::Config.pagination_adapter).to eq(custom_adapter)
37
+
38
+ SimpleCrud::Config.pagination_adapter = original_adapter
39
+ end
40
+ end
41
+
42
+ describe SimpleCrud::Authorization::Adapter do
43
+ subject(:adapter) { IncompleteAuthorizationAdapter.new }
44
+
45
+ it 'requires #authorize to be implemented' do
46
+ expect { adapter.authorize(nil, nil) }.to raise_error(NotImplementedError)
47
+ end
48
+
49
+ it 'requires #policy_defined? to be implemented' do
50
+ expect { adapter.policy_defined?(nil) }.to raise_error(NotImplementedError)
51
+ end
52
+ end
53
+
54
+ describe SimpleCrud::Pagination::Adapter do
55
+ subject(:adapter) { IncompletePaginationAdapter.new }
56
+
57
+ it 'requires #paginate to be implemented' do
58
+ expect { adapter.paginate(nil, nil, nil) }.to raise_error(NotImplementedError)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ class UnpoliceableThing; end
6
+
7
+ class UnpoliceableThingsController
8
+ extend SimpleCrudController
9
+ end
10
+
11
+ describe SimpleCrudController do
12
+ describe '.check_policies' do
13
+ it 'returns when a matching policy exists' do
14
+ expect { DummyModelsController.check_policies(authorize: true) }.not_to raise_error
15
+ end
16
+
17
+ it 'throws when no matching policy exists' do
18
+ expect do
19
+ UnpoliceableThingsController.check_policies(authorize: true)
20
+ end.to raise_error(UncaughtThrowError)
21
+ end
22
+ end
23
+
24
+ describe '.check_serializer' do
25
+ it 'returns when the serializer option is blank' do
26
+ expect { DummyModelsController.check_serializer({}) }.not_to raise_error
27
+ end
28
+
29
+ it 'returns when a matching serializer exists' do
30
+ expect do
31
+ DummyModelsController.check_serializer(serializer: 'DummyModelSerializer')
32
+ end.not_to raise_error
33
+ end
34
+
35
+ it 'throws when no matching serializer exists' do
36
+ expect do
37
+ DummyModelsController.check_serializer(serializer: 'NonExistentSerializer')
38
+ end.to raise_error(UncaughtThrowError)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV['RAILS_ENV'] ||= 'test'
4
+
5
+ require 'simplecov'
6
+ SimpleCov.start do
7
+ minimum_coverage 100
8
+ end
9
+
10
+ require File.expand_path('../spec/dummy/config/environment.rb', __dir__)
11
+ require 'simple_crud'
12
+ require 'rspec/rails'
13
+ require 'pundit/rspec'
14
+ require 'wor/paginate/rspec'
15
+ require 'simple_crud/rspec'
16
+ require 'devise'
17
+ require 'devise/jwt/test_helpers'
18
+
19
+ Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f }
20
+
21
+ ActiveRecord::Migrator.migrations_paths = [File.expand_path('../spec/dummy/db/migrate', __dir__)]
22
+
23
+ RSpec.configure do |config|
24
+ config.include ActionDispatch::TestProcess
25
+ config.file_fixture_path = Rails.root.join('spec', 'support', 'fixtures')
26
+
27
+ config.infer_spec_type_from_file_location!
28
+ config.include Devise::Test::ControllerHelpers, type: :controller
29
+ config.include Devise::Test::IntegrationHelpers, type: :request
30
+ config.include FactoryBot::Syntax::Methods
31
+ config.include Response::JSONParser, type: :controller
32
+ config.order = 'random'
33
+
34
+ # DatabaseCleaner already wraps each example in a transaction below.
35
+ # rspec-rails's own wrapping double-nests it and was observed to leak
36
+ # committed rows. Disable it here.
37
+ config.use_transactional_fixtures = false
38
+
39
+ config.before(:suite) do
40
+ DatabaseCleaner.strategy = :transaction
41
+ DatabaseCleaner.clean_with(:truncation)
42
+
43
+ # Warms the schema cache before any DatabaseCleaner transaction opens.
44
+ # Otherwise the first example touching a table triggers a PRAGMA
45
+ # table_info query mid-transaction, which silently ends it and commits
46
+ # that example's rows for real instead of rolling back.
47
+ conn = ActiveRecord::Base.connection
48
+ conn.data_sources.each { |t| conn.schema_cache.add(t) }
49
+ end
50
+
51
+ config.around do |example|
52
+ DatabaseCleaner.cleaning do
53
+ example.run
54
+ end
55
+ end
56
+ end
57
+
58
+ ActiveRecord::Migration.maintain_test_schema!
59
+
60
+ require 'byebug'
data/spec/spy.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Class used in tests to keep track of amount of calls for a method in a klass.
5
+ #
6
+ # Example in a controller spec:
7
+ #
8
+ # before do
9
+ # Spy.spy(controller, 'authenticate_entity')
10
+ # perform_some_request
11
+ # end
12
+ #
13
+ # it 'calls authenticate_entity defined by the user' do
14
+ # count = controller.spied_authenticate_entity_counter
15
+ # expect(count).to be(1)
16
+ # end
17
+ #
18
+ class Spy
19
+ # Defines a counter for the given method and overrides it to update
20
+ # the counter in every call before being executed.
21
+ def self.spy(klass, method)
22
+ define_counter(klass, method)
23
+ klass.singleton_class.class_eval do
24
+ define_method(method) do |*args, &block|
25
+ instance_variable_set("@spied_#{method}_counter", instance_variable_get("@spied_#{method}_counter") + 1)
26
+ super(*args, &block)
27
+ end
28
+ end
29
+ end
30
+
31
+ # Defines a counter (and its getter) for the given method as an instance variable of the given klass.
32
+ def self.define_counter(klass, method)
33
+ klass.instance_variable_set("@spied_#{method}_counter".to_sym, 0)
34
+
35
+ klass.singleton_class.class_eval do
36
+ define_method("spied_#{method}_counter".to_sym) do
37
+ instance_variable_get("@spied_#{method}_counter")
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ describe WithoutPagination::DummyModelsController, type: :controller do
5
+ include_examples 'simple crud for index'
6
+ end
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "simple_crud/version"
4
+ require 'date'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "wor-simple_crud"
8
+ s.version = SimpleCrud::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.date = Date.today
11
+ s.authors = ["icoluccio"]
12
+ s.email = ["ignacio.coluccio@gmail.com"]
13
+ s.homepage = "https://github.com/icoluccio/simple-crud"
14
+ s.summary = "Simplified CRUD endpoints for Rails API controllers"
15
+ s.description = "Simple CRUD is a gem for Rails that simplifies CRUD controllers, with default implementations and specs for common tasks"
16
+ s.license = "MIT"
17
+ s.required_ruby_version = '>= 3.2'
18
+
19
+ s.files = `git ls-files -z`.split("\x0")
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_dependency 'rails', '>= 6.1', '< 9'
23
+ end