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
data/README.md ADDED
@@ -0,0 +1,286 @@
1
+ SimpleCrud
2
+ =============
3
+
4
+ [![CI](https://github.com/icoluccio/simple-crud/actions/workflows/ci.yml/badge.svg)](https://github.com/icoluccio/simple-crud/actions/workflows/ci.yml)
5
+ [![Gem Version](https://badge.fury.io/rb/wor-simple_crud.svg)](https://badge.fury.io/rb/wor-simple_crud)
6
+
7
+ # Table of contents
8
+ - [Description](#description)
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [Setup](#setup)
12
+ - [Application controller](#application-controller)
13
+ - [Each controller](#each-controller)
14
+ - [Options](#options)
15
+ - [Paginate](#paginate)
16
+ - [Authorize](#authorize)
17
+ - [Serializer](#serializer)
18
+ - [Shared examples](#shared-examples)
19
+ - [Contributing](#contributing)
20
+ - [Releases](#releases)
21
+ - [About](#about)
22
+ - [License](#license)
23
+
24
+ -----------------------
25
+
26
+ ## Description
27
+
28
+ Simple Crud is a gem for Rails that simplifies writing standard CRUD actions, while also adding tools so it's not needed to write tests for them. Its main objective is replacing generally copy-pasted code like generic paginated, authenticated index methods for simple lines such as
29
+ ```ruby
30
+ simple_crud_for :index
31
+ ```
32
+ It includes support for index, create, destroy, update and show, and options to specify whether it should apply pagination, authorization and if a particular serializer should be used. Keep in mind, though, that the idea is not to replace writing methods in controllers altogether, but only to replace most standard cases.
33
+
34
+ ## Installation
35
+ Add the following line to your application's Gemfile. The gem is named `wor-simple_crud`, but the require path is `simple_crud`, so tell Bundler where to find it:
36
+
37
+ ```ruby
38
+ gem 'wor-simple_crud', require: 'simple_crud'
39
+ ```
40
+
41
+ And then execute:
42
+ ```bash
43
+ $ bundle
44
+ ```
45
+
46
+ Or install it yourself as:
47
+ ```bash
48
+ $ gem install wor-simple_crud
49
+ ```
50
+
51
+ simple_crud only depends on Rails itself. Authorization (Pundit by default) and pagination (wor-paginate by default) are opt-in: add whichever libraries you actually use to your own Gemfile too.
52
+
53
+ ```ruby
54
+ gem 'pundit'
55
+ gem 'wor-paginate'
56
+ ```
57
+
58
+ See [Paginate](#paginate) and [Authorize](#authorize) below for the other supported libraries, or to skip either feature (`authorize: false` / `paginate: false`) and depend on neither.
59
+
60
+ ## Usage
61
+ ### Setup
62
+ #### Application controller
63
+ Before SimpleCrud can be used, some boilerplate is needed. Add the following to your ApplicationController (or every controller in case you don't want it included in all controllers)
64
+ ```ruby
65
+ include Pundit::Authorization
66
+ include Wor::Paginate
67
+ extend SimpleCrud
68
+
69
+ before_action :set_params
70
+
71
+ def set_params
72
+ SimpleCrudController.params = params
73
+ end
74
+ ```
75
+
76
+ (Skip `include Pundit::Authorization` / `include Wor::Paginate` if you're using a different adapter, or `authorize: false` / `paginate: false` everywhere.)
77
+
78
+
79
+ ### Each controller
80
+ In case you need either update or create, create a method with the valid input params using standard rails:
81
+ ```ruby
82
+ def author_params
83
+ params.permit(:first_name, :email, :last_name, :institution, :role)
84
+ end
85
+ ```
86
+
87
+ For the actual crud methods, just use the lines you need
88
+ ```ruby
89
+ simple_crud_for :update
90
+ simple_crud_for :show
91
+ simple_crud_for :index
92
+ simple_crud_for :create
93
+ simple_crud_for :destroy
94
+ ```
95
+
96
+ Each method supports different options, as in:
97
+ ```
98
+ simple_crud_for :index, paginate: false, authorize: false, serializer: CustomSerializer
99
+ ```
100
+
101
+ - Paginate: whether it should paginate or not. `true` paginates via the configured pagination adapter (wor-paginate by default), `false` doesn't paginate
102
+ - Authorize: whether it should check authorization via the configured authorization adapter (Pundit by default)
103
+ - Authenticate: whether it should use Devise to check for a current_user
104
+ - Serializer: specify a particular serializer you should use
105
+
106
+ You'll need a few things so they work correctly:
107
+
108
+ ### Options
109
+ #### Paginate
110
+ Pagination defaults to [wor-paginate](https://github.com/icoluccio/wor-paginate) and needs no extra options. Check its docs if you want to customize the output.
111
+
112
+ Three other pagination libraries have adapters built in too:
113
+
114
+ ```ruby
115
+ # Kaminari (https://github.com/kaminari/kaminari)
116
+ require 'simple_crud/pagination/kaminari_adapter'
117
+ SimpleCrud.configure { |config| config.pagination_adapter = SimpleCrud::Pagination::KaminariAdapter.new }
118
+
119
+ # will_paginate (https://github.com/mislav/will_paginate)
120
+ require 'simple_crud/pagination/will_paginate_adapter'
121
+ SimpleCrud.configure { |config| config.pagination_adapter = SimpleCrud::Pagination::WillPaginateAdapter.new }
122
+
123
+ # Pagy (https://github.com/ddnexus/pagy) -- requires `include Pagy::Method` in your ApplicationController
124
+ require 'simple_crud/pagination/pagy_adapter'
125
+ SimpleCrud.configure { |config| config.pagination_adapter = SimpleCrud::Pagination::PagyAdapter.new }
126
+ ```
127
+
128
+ They render a plain JSON array rather than wor-paginate's `{page:, count:, total_pages:, ...}` envelope. None of the four pagination gems is installed automatically, so add whichever one you pick to your own Gemfile.
129
+
130
+ Want something else, or nothing at all? Write your own `SimpleCrud::Pagination::Adapter`:
131
+
132
+ ```ruby
133
+ class MyPaginationAdapter
134
+ include SimpleCrud::Pagination::Adapter
135
+
136
+ # Called from inside the generated index action when paginate: true.
137
+ # Must render the response itself.
138
+ def paginate(controller, klass, options)
139
+ records = klass.some_pagination_method(controller.params[:page])
140
+ controller.render({ json: records }.merge(options))
141
+ end
142
+ end
143
+
144
+ SimpleCrud.configure { |config| config.pagination_adapter = MyPaginationAdapter.new }
145
+ ```
146
+
147
+ #### Authorize
148
+ Authorization checks go through [Pundit](https://github.com/varvet/pundit) by default. Name the policy after the model plus `Policy`, e.g. `AuthorPolicy`, written as a regular Pundit policy:
149
+
150
+ ```ruby
151
+ class AuthorPolicy
152
+ attr_reader :user, :author
153
+
154
+ def initialize(user, author)
155
+ @user = user
156
+ @author = author
157
+ end
158
+
159
+ def show?
160
+ user.present?
161
+ end
162
+ end
163
+
164
+ ```
165
+
166
+ Prefer CanCanCan or Action Policy instead? Both have adapters ready to go:
167
+
168
+ ```ruby
169
+ # CanCanCan (https://github.com/CanCanCommunity/cancancan)
170
+ require 'simple_crud/authorization/can_can_can_adapter'
171
+ SimpleCrud.configure do |config|
172
+ config.authorization_adapter = SimpleCrud::Authorization::CanCanCanAdapter.new
173
+ end
174
+ ```
175
+
176
+ ```ruby
177
+ # Action Policy (https://github.com/palkan/action_policy)
178
+ require 'simple_crud/authorization/action_policy_adapter'
179
+ SimpleCrud.configure do |config|
180
+ config.authorization_adapter = SimpleCrud::Authorization::ActionPolicyAdapter.new
181
+ end
182
+ ```
183
+
184
+ As with pagination, simple_crud ships the adapter code but not the library itself. Add `cancancan` or `action_policy` to your own Gemfile, whichever you pick.
185
+
186
+ Using something else, or skipping authorization entirely? Write your own `SimpleCrud::Authorization::Adapter`:
187
+
188
+ ```ruby
189
+ class MyAuthorizationAdapter
190
+ include SimpleCrud::Authorization::Adapter
191
+
192
+ # Called from inside a generated CRUD action. Raise (or otherwise halt
193
+ # the request) when the current user may not act on +record+.
194
+ def authorize(controller, record)
195
+ controller.authorize!(record)
196
+ end
197
+
198
+ # Called once, when simple_crud_for is invoked, to fail fast if the
199
+ # given model has no authorization rules defined at all.
200
+ def policy_defined?(model_class)
201
+ Kernel.const_defined?("#{model_class}Policy")
202
+ end
203
+ end
204
+
205
+ SimpleCrud.configure do |config|
206
+ config.authorization_adapter = MyAuthorizationAdapter.new
207
+ end
208
+ ```
209
+
210
+ #### Authenticate
211
+ SimpleCrud will assume a current_user method. Future versions will support a custom model. Defining a current_user method in ApplicationController should work if you're using a different model, as of now.
212
+
213
+ #### Serializer
214
+ The name of the serializer, by default, is the name of the model followed by Serializer, as is the standard for [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers). It's possible to just pass a custom serializer class though. As for the serializer itself, it's a standard serializer, with the gotcha that you need to include `:id` for the SimpleCrud examples to work.
215
+
216
+ ```ruby
217
+ class AuthorSerializer < ActiveModel::Serializer
218
+ attributes :email, :first_name, :last_name, :institution, :role, :id
219
+ end
220
+ ```
221
+
222
+ ### Shared examples
223
+ While optional, using the included shared examples saves you from writing the standard test cases for the methods. You can even use them if you didn't use `simple_crud_for`, as a set of basic tests. To include them, just add `require 'simple_crud/rspec'` to your `rails_helper.rb` file and add the lines you need to your `*_spec.rb` files:
224
+ ```ruby
225
+ require 'rails_helper'
226
+
227
+ describe V1::Backoffice::AuthorsController do
228
+ include_examples 'simple crud for update'
229
+ include_examples 'simple crud for show'
230
+ include_examples 'simple crud for create'
231
+ include_examples 'simple crud for index'
232
+ include_examples 'simple crud for destroy'
233
+ end
234
+ ```
235
+
236
+ It's not needed to specify paginate: true and such, since the shared examples will use the configuration that was originally passed to simple_crud_for
237
+
238
+ ## Contributing
239
+
240
+ 1. Fork it
241
+ 2. Run `bundle install && bundle exec appraisal generate` once, to install dependencies and generate the per-Rails-version gemfiles (`gemfiles/rails_*.gemfile`) used for testing
242
+ 3. Run `bundle exec overcommit --install` once, to enable the pre-push hook (runs RuboCop and the full spec suite automatically on every `git push`)
243
+ 4. Create your feature branch (`git checkout -b my-new-feature`)
244
+ 5. Commit your changes (`git commit -am 'Add some feature'`)
245
+ 6. Run RuboCop lint (`bundle exec rubocop lib spec --format simple`)
246
+ 7. Run rspec tests (`BUNDLE_GEMFILE=gemfiles/rails_8.1.gemfile bundle exec rspec`)
247
+ 8. Push your branch (`git push origin my-new-feature`). The pre-push hook re-verifies both automatically
248
+ 9. Create a new Pull Request to `main` branch
249
+
250
+ ## Releases
251
+ 📢 [See what's changed in a recent version](https://github.com/icoluccio/simple-crud/releases)
252
+
253
+ ## About ##
254
+
255
+ The current maintainer of this gem is:
256
+ * [Ignacio Coluccio](https://github.com/icoluccio)
257
+
258
+ This project was developed by:
259
+ * [Ignacio Coluccio](https://github.com/icoluccio)
260
+
261
+ Originally at Wolox
262
+
263
+ ## License
264
+
265
+ **simple-crud** is available under the MIT [license](https://raw.githubusercontent.com/icoluccio/simple-crud/main/LICENSE.md).
266
+
267
+ Copyright (c) 2017 Wolox
268
+ Copyright (c) 2026 Ignacio Coluccio
269
+
270
+ Permission is hereby granted, free of charge, to any person obtaining a copy
271
+ of this software and associated documentation files (the "Software"), to deal
272
+ in the Software without restriction, including without limitation the rights
273
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
274
+ copies of the Software, and to permit persons to whom the Software is
275
+ furnished to do so, subject to the following conditions:
276
+
277
+ The above copyright notice and this permission notice shall be included in
278
+ all copies or substantial portions of the Software.
279
+
280
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
281
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
282
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
283
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
284
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
285
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
286
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_policy'
4
+ require_relative 'adapter'
5
+
6
+ module SimpleCrud
7
+ module Authorization
8
+ # Assumes ActionPolicy::Controller (not the bare Behaviour) is included,
9
+ # which defaults the rule to the current action name.
10
+ class ActionPolicyAdapter
11
+ include Adapter
12
+
13
+ def authorize(controller, record)
14
+ controller.send(:authorize!, record)
15
+ end
16
+
17
+ def policy_defined?(model_class)
18
+ Kernel.const_defined?("#{model_class}Policy")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCrud
4
+ module Authorization
5
+ # Implement to plug in an authorization library other than Pundit
6
+ # (the default, see PunditAdapter).
7
+ module Adapter
8
+ # Raise when +controller+'s current user may not act on +record+.
9
+ def authorize(controller, record)
10
+ raise NotImplementedError
11
+ end
12
+
13
+ # Called once, when simple_crud_for is invoked, to fail fast if
14
+ # +model_class+ has no authorization configured.
15
+ def policy_defined?(model_class)
16
+ raise NotImplementedError
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cancancan'
4
+ require_relative 'adapter'
5
+
6
+ module SimpleCrud
7
+ module Authorization
8
+ # A single Ability class governs everything, so #policy_defined? can
9
+ # only confirm one is configured, not that it covers this model.
10
+ class CanCanCanAdapter
11
+ include Adapter
12
+
13
+ def authorize(controller, record)
14
+ controller.send(:authorize!, controller.action_name.to_sym, record)
15
+ end
16
+
17
+ def policy_defined?(_model_class)
18
+ Kernel.const_defined?('Ability')
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'adapter'
4
+
5
+ module SimpleCrud
6
+ module Authorization
7
+ # Default authorization adapter: standard Pundit `#{Model}Policy` classes.
8
+ class PunditAdapter
9
+ include Adapter
10
+
11
+ def authorize(controller, record)
12
+ # Pundit::Authorization#authorize is protected, hence send.
13
+ controller.send(:authorize, record)
14
+ end
15
+
16
+ def policy_defined?(model_class)
17
+ Kernel.const_defined?("#{model_class}Policy")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCrud
4
+ # Holds simple_crud's configurable pieces, set via SimpleCrud.configure.
5
+ class Config
6
+ class << self
7
+ attr_writer :authorization_adapter, :pagination_adapter
8
+
9
+ def authorization_adapter
10
+ @authorization_adapter ||= begin
11
+ require_relative 'authorization/pundit_adapter'
12
+ Authorization::PunditAdapter.new
13
+ end
14
+ end
15
+
16
+ def pagination_adapter
17
+ @pagination_adapter ||= begin
18
+ require_relative 'pagination/wor_paginate_adapter'
19
+ Pagination::WorPaginateAdapter.new
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCrud
4
+ module Pagination
5
+ # Implement to plug in a pagination library other than wor-paginate
6
+ # (the default, see WorPaginateAdapter).
7
+ module Adapter
8
+ # Called when paginate: true. Must render the response itself.
9
+ def paginate(controller, klass, options)
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kaminari'
4
+ require 'kaminari/activerecord'
5
+ require_relative 'adapter'
6
+
7
+ module SimpleCrud
8
+ module Pagination
9
+ # https://github.com/kaminari/kaminari
10
+ class KaminariAdapter
11
+ include Adapter
12
+
13
+ def paginate(controller, klass, options)
14
+ records = klass.page(controller.params[:page]).per(controller.params[:per_page])
15
+ controller.render({ json: records }.merge(options))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pagy'
4
+ require_relative 'adapter'
5
+
6
+ module SimpleCrud
7
+ module Pagination
8
+ # Assumes the controller includes Pagy::Method.
9
+ class PagyAdapter
10
+ include Adapter
11
+
12
+ # 43.5.0 renamed client_max_limit -> max_limit. Ruby < 3.3 resolves to
13
+ # the pre-rename 43.4.x, so pick whichever key the loaded Pagy expects.
14
+ MAX_LIMIT_KEY = Gem::Version.new(Pagy::VERSION) >= Gem::Version.new('43.5.0') ? :max_limit : :client_max_limit
15
+
16
+ def paginate(controller, klass, options)
17
+ _pagy, records = controller.send(:pagy, klass.all, MAX_LIMIT_KEY => 100)
18
+ controller.render({ json: records }.merge(options))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'will_paginate'
4
+ require 'will_paginate/active_record'
5
+ require_relative 'adapter'
6
+
7
+ module SimpleCrud
8
+ module Pagination
9
+ # Adapter for will_paginate (https://github.com/mislav/will_paginate).
10
+ # Uses #paginate, not #page (also defined by Kaminari).
11
+ class WillPaginateAdapter
12
+ include Adapter
13
+
14
+ def paginate(controller, klass, options)
15
+ page = controller.params[:page] || 1
16
+ per_page = controller.params[:per_page] || WillPaginate.per_page
17
+ records = klass.paginate(page: page, per_page: per_page)
18
+ controller.render({ json: records }.merge(options))
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wor/paginate'
4
+ require_relative 'adapter'
5
+
6
+ module SimpleCrud
7
+ module Pagination
8
+ # Assumes the controller includes Wor::Paginate.
9
+ class WorPaginateAdapter
10
+ include Adapter
11
+
12
+ def paginate(controller, klass, options)
13
+ controller.send(:render_paginated, klass, options)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../spec/response_helper'
4
+ require_relative '../spec/shared_contexts/authenticate_user'
5
+ require_relative '../spec/shared_examples/simple_crud_for_update'
6
+ require_relative '../spec/shared_examples/simple_crud_for_show'
7
+ require_relative '../spec/shared_examples/simple_crud_for_index'
8
+ require_relative '../spec/shared_examples/simple_crud_for_destroy'
9
+ require_relative '../spec/shared_examples/simple_crud_for_create'
10
+ require_relative '../spec/shared_examples/unauthorized_when_not_logged_in'
11
+ require_relative '../spec/matchers/have_been_serialized_with'
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/all'
4
+ require_relative 'config'
5
+
6
+ # Extended onto a controller for CRUD actions.
7
+ module SimpleCrudController
8
+ cattr_accessor :params, :permitted
9
+
10
+ # Possible options:
11
+ ### authorize: check authorization via Config.authorization_adapter (Pundit by default)
12
+ ### paginate: paginate the list via Config.pagination_adapter (wor-paginate by default)
13
+ ### authenticate: use devise to authenticate
14
+ ### serializer: use a particular serializer (both each_serializer and serializer)
15
+ def simple_crud_for(method, parameters = {})
16
+ parameters = parameters_with_defaults(parameters)
17
+ klass = simple_crud_controller_model
18
+ check_valid_method(method)
19
+ check_policies(parameters)
20
+ check_serializer(parameters)
21
+ define_method(method, send("crud_lambda_for_#{method}", klass, parameters))
22
+ write_metadata(method, parameters)
23
+ end
24
+
25
+ def parameters_with_defaults(parameters)
26
+ defaults = { authorize: true, paginate: true, authenticate: true, serializer: nil }
27
+ defaults.each do |key, value|
28
+ parameters[key] = value unless parameters.key?(key)
29
+ end
30
+ parameters
31
+ end
32
+
33
+ def write_metadata(method, parameters)
34
+ @simple_crud_metadata ||= {}
35
+ @simple_crud_metadata[method] = parameters
36
+ end
37
+
38
+ def self.maybe_authorize(controller, record, parameters)
39
+ return unless parameters[:authorize] && parameters[:authenticate]
40
+
41
+ SimpleCrud::Config.authorization_adapter.authorize(controller, record)
42
+ end
43
+
44
+ def crud_lambda_for_show(klass, parameters = {})
45
+ lambda do
46
+ authenticate_user! if parameters[:authenticate]
47
+ requested = klass.find(params[:id])
48
+
49
+ options = {}.merge(serializer: parameters[:serializer]).compact
50
+ SimpleCrudController.maybe_authorize(self, requested, parameters)
51
+ render({ json: requested }.merge(options))
52
+ end
53
+ end
54
+
55
+ def crud_lambda_for_index(klass, parameters = {})
56
+ lambda do
57
+ authenticate_user! if parameters[:authenticate]
58
+ SimpleCrudController.maybe_authorize(self, klass.new, parameters)
59
+ options = {}.merge(each_serializer: parameters[:serializer]).compact
60
+ SimpleCrudController.render_index(self, klass, options, parameters)
61
+ end
62
+ end
63
+
64
+ def self.render_index(controller, klass, options, parameters)
65
+ if parameters[:paginate]
66
+ SimpleCrud::Config.pagination_adapter.paginate(controller, klass, options)
67
+ else
68
+ controller.render({ json: klass.all }.merge(options))
69
+ end
70
+ end
71
+
72
+ def crud_lambda_for_create(klass, parameters = {})
73
+ lambda do
74
+ authenticate_user! if parameters[:authenticate]
75
+ permitted_params = send("#{self.class.simple_crud_controller_model.to_s.underscore}_params")
76
+ SimpleCrudController.maybe_authorize(self, klass.new(permitted_params), parameters)
77
+ render json: klass.create!(permitted_params), status: :created
78
+ end
79
+ end
80
+
81
+ def crud_lambda_for_update(klass, parameters = {})
82
+ lambda do
83
+ authenticate_user! if parameters[:authenticate]
84
+ requested = klass.find(params[:id])
85
+ SimpleCrudController.maybe_authorize(self, requested, parameters)
86
+ permitted_params = send("#{self.class.simple_crud_controller_model.to_s.underscore}_params")
87
+ render json: requested.update!(permitted_params)
88
+ end
89
+ end
90
+
91
+ def crud_lambda_for_destroy(klass, parameters = {})
92
+ lambda do
93
+ authenticate_user! if parameters[:authenticate]
94
+ requested = klass.find(params[:id])
95
+ SimpleCrudController.maybe_authorize(self, requested, parameters)
96
+ render json: klass.find(params[:id]).destroy
97
+ end
98
+ end
99
+
100
+ def simple_crud_controller_model
101
+ to_s.split('::').last.sub('Controller', '').singularize.classify.constantize
102
+ end
103
+
104
+ def check_valid_method(method)
105
+ throw 'invalid method' unless %i[show index create update destroy].include? method
106
+ end
107
+
108
+ def check_policies(parameters)
109
+ return if !parameters.key?(:authorize) || !parameters[:authorize]
110
+
111
+ model = simple_crud_controller_model
112
+ return if SimpleCrud::Config.authorization_adapter.policy_defined?(model)
113
+
114
+ throw "no authorization policy configured for #{model}"
115
+ end
116
+
117
+ def check_serializer(parameters)
118
+ return if parameters[:serializer].blank?
119
+
120
+ serializer_name = parameters[:serializer].to_s
121
+ return if Kernel.const_defined?(serializer_name)
122
+
123
+ throw "create a valid serializer with name #{serializer_name}"
124
+ end
125
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCrud
4
+ VERSION = '0.2.1'
5
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'simple_crud/version'
4
+ require_relative 'simple_crud/config'
5
+ require_relative 'simple_crud/simple_crud_controller'
6
+
7
+ # Simplified CRUD endpoints for Rails API controllers.
8
+ module SimpleCrud
9
+ def self.configure
10
+ yield Config
11
+ end
12
+ end