wor-simple_crud 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -0
- data/CHANGELOG.md +6 -0
- data/README.md +53 -0
- data/lib/simple_crud/action_context.rb +98 -0
- data/lib/simple_crud/action_lambdas.rb +4 -72
- data/lib/simple_crud/cache_helpers.rb +17 -0
- data/lib/simple_crud/create_context.rb +17 -0
- data/lib/simple_crud/destroy_context.rb +15 -0
- data/lib/simple_crud/edit_context.rb +13 -0
- data/lib/simple_crud/index_context.rb +64 -0
- data/lib/simple_crud/new_context.rb +13 -0
- data/lib/simple_crud/persistence_context.rb +33 -0
- data/lib/simple_crud/show_context.rb +27 -0
- data/lib/simple_crud/simple_crud_controller.rb +14 -3
- data/lib/simple_crud/update_context.rb +16 -0
- data/lib/simple_crud/version.rb +1 -1
- data/lib/spec/shared_examples/simple_crud_for_destroy.rb +1 -1
- data/spec/cached/dummy_models_controller_spec.rb +23 -0
- data/spec/cached_defaults/dummy_models_controller_spec.rb +34 -0
- data/spec/dummy/app/controllers/cached/dummy_models_controller.rb +14 -0
- data/spec/dummy/app/controllers/cached_defaults/dummy_models_controller.rb +11 -0
- data/spec/dummy/config/routes.rb +8 -0
- data/spec/simple_crud/action_context_spec.rb +22 -0
- data/spec/simple_crud/index_context_spec.rb +31 -0
- data/spec/simple_crud/show_context_spec.rb +14 -0
- metadata +19 -4
- data/lib/simple_crud/controller_helpers.rb +0 -128
- data/spec/simple_crud/controller_helpers_spec.rb +0 -49
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5b50548416811b034a75f72c96d4e94c41cdb9883d7112cdf720ad3febaeb11
|
|
4
|
+
data.tar.gz: 99ae1562bae38c2fa8ff1d697859f8643e2b818aaeb66f2e14220171fa9f28dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef7dd6acb64f882534e9951a9adf2d8deab27f2264451cb1dee2613c78e4842e38d10a638a2ed9ec623984d0d571aee558e2a3626f7bf8f016eca65dc3174ae3
|
|
7
|
+
data.tar.gz: 43a9b8fb46febb90d101814a33d48049ba0a5c2df3a3b14568c5423816ebd55325966fabe8673320ef3ed89b1a7bfe0ac10f4940c16b0e412649b100dd08e051
|
data/.rubocop.yml
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
plugins:
|
|
2
2
|
- rubocop-rspec
|
|
3
3
|
|
|
4
|
+
inherit_mode:
|
|
5
|
+
merge:
|
|
6
|
+
- Exclude
|
|
7
|
+
|
|
4
8
|
AllCops:
|
|
5
9
|
TargetRubyVersion: 3.2
|
|
6
10
|
NewCops: disable
|
|
@@ -14,3 +18,14 @@ RSpec/SpecFilePathFormat:
|
|
|
14
18
|
RSpec/SpecFilePathSuffix:
|
|
15
19
|
Exclude:
|
|
16
20
|
- spec/dummy_controller_spec.rb
|
|
21
|
+
|
|
22
|
+
# Context class names are self-documenting; comments would be noise.
|
|
23
|
+
Style/Documentation:
|
|
24
|
+
Exclude:
|
|
25
|
+
- lib/simple_crud/*_context.rb
|
|
26
|
+
|
|
27
|
+
# The destroy failure spec must stub find_record on the instance to intercept
|
|
28
|
+
# both default (klass.find) and custom-finder (lambda) lookup paths.
|
|
29
|
+
RSpec/AnyInstance:
|
|
30
|
+
Exclude:
|
|
31
|
+
- lib/spec/shared_examples/simple_crud_for_destroy.rb
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
## Change log
|
|
2
2
|
|
|
3
|
+
### V0.5.0
|
|
4
|
+
|
|
5
|
+
New options:
|
|
6
|
+
* `cache: { key:, ttl: }` on `simple_crud_for :show` and `:index` — skips the DB on cache hits. On a miss: find/query, authorize, run block, cache, render. `key` is a lambda receiving `params` or a plain string; both default when omitted (`"#{model}:#{action}:v1:#{request.fullpath}"` and 300 s). Block must return the payload hash, not call `render`. `@record`/`@records` is set before the block, so shared examples work unchanged. Controllers get a default `fetch_cached(key, ttl, &block)` backed by `Rails.cache`; override to use a different store.
|
|
7
|
+
* `expire_simple_crud_cache(action, path: request.fullpath)` on controllers — deletes the default cache key for an action without constructing it by hand. Only works for the default key format; custom `key:` lambdas must be deleted directly.
|
|
8
|
+
|
|
3
9
|
### V0.4.0
|
|
4
10
|
|
|
5
11
|
Breaking:
|
data/README.md
CHANGED
|
@@ -18,6 +18,7 @@ SimpleCrud
|
|
|
18
18
|
- [Serializer](#serializer)
|
|
19
19
|
- [HTML](#html)
|
|
20
20
|
- [Finder](#finder)
|
|
21
|
+
- [Cache](#cache)
|
|
21
22
|
- [Controller-level defaults](#controller-level-defaults)
|
|
22
23
|
- [Shared examples](#shared-examples)
|
|
23
24
|
- [Contributing](#contributing)
|
|
@@ -343,6 +344,58 @@ simple_crud_for :destroy, finder: ->(params) { current_user.models.find(params[:
|
|
|
343
344
|
|
|
344
345
|
When omitted it defaults to `klass.find(params[:id])`, and `not_found` is still returned whenever the finder finds no record.
|
|
345
346
|
|
|
347
|
+
#### Cache
|
|
348
|
+
Pass `cache: { key:, ttl: }` on `:show` or `:index` to skip the DB on cache hits.
|
|
349
|
+
|
|
350
|
+
`key` is a lambda receiving `params`, or a plain string. Both `key` and `ttl` are optional — defaults are `"#{model}:#{action}:v1:#{request.fullpath}"` and 300 seconds. With `cache:` the block must return the payload hash, not call `render`. `@record`/`@records` is set before the block, so shared examples work unchanged.
|
|
351
|
+
|
|
352
|
+
Controllers get a default `fetch_cached(key, ttl, &block)` backed by `Rails.cache`. Override to use a different store:
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
# Override with a direct Redis connection
|
|
356
|
+
def fetch_cached(key, ttl)
|
|
357
|
+
cached = redis.get(key)
|
|
358
|
+
return JSON.parse(cached, symbolize_names: true) if cached
|
|
359
|
+
|
|
360
|
+
result = yield
|
|
361
|
+
redis.setex(key, ttl, result.to_json)
|
|
362
|
+
result
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# All options explicit
|
|
366
|
+
simple_crud_for :show,
|
|
367
|
+
finder: ->(p) { Article.includes(:author).find(p[:id]) },
|
|
368
|
+
cache: { key: ->(p) { "article:v1:#{p[:id]}" }, ttl: 900 } do |article|
|
|
369
|
+
article_payload(article)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
# All defaults — key and TTL inferred from model name and params
|
|
373
|
+
simple_crud_for :index, paginate: false, cache: {} do |articles|
|
|
374
|
+
articles.map { |a| article_summary(a) }
|
|
375
|
+
end
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Invalidate on write. For the default key format, `expire_simple_crud_cache(action)` deletes it without constructing it by hand:
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
expire_simple_crud_cache(:show)
|
|
382
|
+
expire_simple_crud_cache(:index)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
For a custom `key:` lambda, delete the key yourself:
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
def update
|
|
389
|
+
article = current_user.articles.find(params[:id])
|
|
390
|
+
if article.update(article_params)
|
|
391
|
+
redis.del("article:v1:#{article.id}")
|
|
392
|
+
render json: article_payload(article)
|
|
393
|
+
else
|
|
394
|
+
render json: { errors: article.errors.full_messages }, status: :unprocessable_entity
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
```
|
|
398
|
+
|
|
346
399
|
### Shared examples
|
|
347
400
|
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, add `require 'simple_crud/rspec'` to your `rails_helper.rb` **after** `require "rspec/rails"`, then add the lines you need to your `*_spec.rb` files:
|
|
348
401
|
```ruby
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class ActionContext
|
|
5
|
+
DEFAULT_CACHE_TTL = 300
|
|
6
|
+
|
|
7
|
+
attr_reader :controller, :klass, :parameters, :block
|
|
8
|
+
|
|
9
|
+
def self.cache_key_for(model, action, path)
|
|
10
|
+
"#{model.model_name.singular}:#{action}:v1:#{path}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(controller, klass, parameters, &block)
|
|
14
|
+
@controller = controller
|
|
15
|
+
@klass = klass
|
|
16
|
+
@parameters = parameters
|
|
17
|
+
@block = block
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
controller.authenticate_user! if parameters[:authenticate]
|
|
22
|
+
run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
raise NotImplementedError, "#{self.class} must implement run"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def action_name
|
|
32
|
+
raise NotImplementedError, "#{self.class} must implement action_name to support caching"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def maybe_authorize(object)
|
|
36
|
+
return unless parameters[:authorize]
|
|
37
|
+
|
|
38
|
+
SimpleCrud::Config.authorization_adapter.authorize(controller, object)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def find_record
|
|
42
|
+
record = lookup_record
|
|
43
|
+
raise ActiveRecord::RecordNotFound, "couldn't find #{klass}" if record.nil?
|
|
44
|
+
unless record.is_a?(ActiveRecord::Base)
|
|
45
|
+
raise ActiveRecord::RecordNotFound, "#{klass} finder must return a single record"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
record
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def lookup_record
|
|
52
|
+
finder = parameters[:finder]
|
|
53
|
+
return klass.find(controller.params[:id]) if finder.nil?
|
|
54
|
+
return klass.send(finder, controller.params) unless finder.respond_to?(:call)
|
|
55
|
+
|
|
56
|
+
finder.call(controller.params)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_record
|
|
60
|
+
parameters[:build] ? controller.instance_exec(¶meters[:build]) : klass.new
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def permitted_params
|
|
64
|
+
controller.send("#{klass.model_name.singular}_params")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def serialize_opts(key)
|
|
68
|
+
{ key => parameters[:serializer] }.compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def render_record(record, template)
|
|
72
|
+
if parameters[:html] || block
|
|
73
|
+
controller.instance_variable_set(:@record, record)
|
|
74
|
+
block ? controller.instance_exec(record, &block) : controller.render(template)
|
|
75
|
+
else
|
|
76
|
+
controller.render({ json: record }.merge(serialize_opts(:serializer)))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def render_cached(cache_opts, &fetch_block)
|
|
81
|
+
key = resolve_cache_key(cache_opts)
|
|
82
|
+
ttl = cache_opts[:ttl] || DEFAULT_CACHE_TTL
|
|
83
|
+
result = controller.send(:fetch_cached, key, ttl, &fetch_block)
|
|
84
|
+
controller.render json: result
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def resolve_cache_key(cache_opts)
|
|
88
|
+
k = cache_opts[:key]
|
|
89
|
+
return default_cache_key if k.nil?
|
|
90
|
+
|
|
91
|
+
k.respond_to?(:call) ? controller.instance_exec(controller.params, &k) : k
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def default_cache_key
|
|
95
|
+
self.class.cache_key_for(klass, action_name, controller.request.fullpath)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -3,78 +3,10 @@
|
|
|
3
3
|
module SimpleCrud
|
|
4
4
|
# Builds the lambda installed as each action declared with simple_crud_for.
|
|
5
5
|
module ActionLambdas
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
options = {}.merge(serializer: parameters[:serializer]).compact
|
|
12
|
-
SimpleCrudController.maybe_authorize(self, requested, parameters)
|
|
13
|
-
SimpleCrudController.render_show(self, requested, options, parameters, &block)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def crud_lambda_for_new(klass, parameters = {}, &block)
|
|
18
|
-
lambda do
|
|
19
|
-
authenticate_user! if parameters[:authenticate]
|
|
20
|
-
record = SimpleCrudController.build_record(self, klass, parameters)
|
|
21
|
-
SimpleCrudController.maybe_authorize(self, record, parameters)
|
|
22
|
-
SimpleCrudController.render_new(self, record, parameters, &block)
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# The find-instead-of-build twin of :new.
|
|
27
|
-
def crud_lambda_for_edit(klass, parameters = {}, &block)
|
|
28
|
-
lambda do
|
|
29
|
-
authenticate_user! if parameters[:authenticate]
|
|
30
|
-
requested = SimpleCrudController.find_record(klass, self, parameters)
|
|
31
|
-
SimpleCrudController.maybe_authorize(self, requested, parameters)
|
|
32
|
-
SimpleCrudController.render_edit(self, requested, parameters, &block)
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def crud_lambda_for_index(klass, parameters = {}, &block)
|
|
37
|
-
lambda do
|
|
38
|
-
authenticate_user! if parameters[:authenticate]
|
|
39
|
-
SimpleCrudController.maybe_authorize(self, klass, parameters)
|
|
40
|
-
options = {}.merge(each_serializer: parameters[:serializer]).compact
|
|
41
|
-
SimpleCrudController.render_index(self, klass, options, parameters, &block)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def crud_lambda_for_create(klass, parameters = {}, &block)
|
|
46
|
-
lambda do
|
|
47
|
-
authenticate_user! if parameters[:authenticate]
|
|
48
|
-
permitted_params = send("#{self.class.simple_crud_controller_model.to_s.underscore}_params")
|
|
49
|
-
record = SimpleCrudController.build_record(self, klass, parameters)
|
|
50
|
-
record.assign_attributes(permitted_params)
|
|
51
|
-
SimpleCrudController.maybe_authorize(self, record, parameters)
|
|
52
|
-
persist = ->(bang:) { bang ? record.save! : record.save }
|
|
53
|
-
options = { status: :created, failure_template: :new }
|
|
54
|
-
SimpleCrudController.save_and_render(self, record, parameters, options, persist, &block)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def crud_lambda_for_update(klass, parameters = {}, &block)
|
|
59
|
-
lambda do
|
|
60
|
-
authenticate_user! if parameters[:authenticate]
|
|
61
|
-
requested = SimpleCrudController.find_record(klass, self, parameters)
|
|
62
|
-
SimpleCrudController.maybe_authorize(self, requested, parameters)
|
|
63
|
-
permitted_params = send("#{self.class.simple_crud_controller_model.to_s.underscore}_params")
|
|
64
|
-
persist = ->(bang:) { bang ? requested.update!(permitted_params) : requested.update(permitted_params) }
|
|
65
|
-
options = { status: :ok, failure_template: :edit }
|
|
66
|
-
SimpleCrudController.save_and_render(self, requested, parameters, options, persist, &block)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def crud_lambda_for_destroy(klass, parameters = {}, &block)
|
|
71
|
-
lambda do
|
|
72
|
-
authenticate_user! if parameters[:authenticate]
|
|
73
|
-
requested = SimpleCrudController.find_record(klass, self, parameters)
|
|
74
|
-
SimpleCrudController.maybe_authorize(self, requested, parameters)
|
|
75
|
-
options = { status: :ok, failure_template: :show, redirect: parameters[:redirect] || klass }
|
|
76
|
-
persist = ->(bang:) { bang ? requested.destroy! : requested.destroy }
|
|
77
|
-
SimpleCrudController.persist_and_render(self, requested, parameters, options, persist, &block)
|
|
6
|
+
%i[show new edit index create update destroy].each do |action|
|
|
7
|
+
ctx_class = SimpleCrud.const_get(:"#{action.to_s.capitalize}Context")
|
|
8
|
+
define_method(:"crud_lambda_for_#{action}") do |klass, parameters = {}, &block|
|
|
9
|
+
-> { ctx_class.new(self, klass, parameters, &block).call }
|
|
78
10
|
end
|
|
79
11
|
end
|
|
80
12
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
# Default fetch_cached included into controllers that extend SimpleCrudController.
|
|
5
|
+
# Override in the controller to use a different store.
|
|
6
|
+
module CacheHelpers
|
|
7
|
+
def fetch_cached(key, ttl, &block)
|
|
8
|
+
Rails.cache.fetch(key, expires_in: ttl, &block)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def expire_simple_crud_cache(action, path: request.fullpath)
|
|
12
|
+
Rails.cache.delete(
|
|
13
|
+
SimpleCrud::ActionContext.cache_key_for(self.class.simple_crud_controller_model, action, path)
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class CreateContext < PersistenceContext
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
attrs = permitted_params
|
|
9
|
+
record = build_record
|
|
10
|
+
record.assign_attributes(attrs)
|
|
11
|
+
maybe_authorize(record)
|
|
12
|
+
persist = ->(bang:) { bang ? record.save! : record.save }
|
|
13
|
+
options = { status: :created, failure_template: :new, redirect: parameters[:redirect] || record }
|
|
14
|
+
persist_and_render(record, options, persist)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class DestroyContext < PersistenceContext
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
record = find_record
|
|
9
|
+
maybe_authorize(record)
|
|
10
|
+
persist = ->(bang:) { bang ? record.destroy! : record.destroy }
|
|
11
|
+
options = { status: :ok, failure_template: :show, redirect: parameters[:redirect] || klass }
|
|
12
|
+
persist_and_render(record, options, persist)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class IndexContext < ActionContext
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def action_name = :index
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
maybe_authorize(klass)
|
|
11
|
+
cache_opts = parameters[:cache]
|
|
12
|
+
cache_opts ? render_cached(cache_opts) { payload } : render_index
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def payload
|
|
16
|
+
opts = serialize_opts(:each_serializer)
|
|
17
|
+
relation = index_relation
|
|
18
|
+
records = index_records(relation, opts)
|
|
19
|
+
controller.instance_variable_set(:@records, records)
|
|
20
|
+
block ? controller.instance_exec(records, &block) : records.as_json
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render_index
|
|
24
|
+
relation = index_relation
|
|
25
|
+
opts = serialize_opts(:each_serializer)
|
|
26
|
+
if parameters[:html] || block
|
|
27
|
+
render_records(relation, opts)
|
|
28
|
+
elsif parameters[:paginate]
|
|
29
|
+
SimpleCrud::Config.pagination_adapter.paginate(controller, relation, opts)
|
|
30
|
+
else
|
|
31
|
+
controller.render({ json: relation }.merge(opts))
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def render_records(relation, opts)
|
|
36
|
+
records = index_records(relation, opts)
|
|
37
|
+
controller.instance_variable_set(:@records, records)
|
|
38
|
+
block ? controller.instance_exec(records, &block) : controller.render(:index)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def index_relation
|
|
42
|
+
if parameters[:scope]
|
|
43
|
+
call_scope
|
|
44
|
+
elsif parameters[:authorize]
|
|
45
|
+
SimpleCrud::Config.authorization_adapter.policy_scope(controller, klass)
|
|
46
|
+
else
|
|
47
|
+
klass.all
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def call_scope
|
|
52
|
+
user_method = SimpleCrud::Config.user_method
|
|
53
|
+
user = controller.respond_to?(user_method) ? controller.public_send(user_method) : nil
|
|
54
|
+
scope = parameters[:scope]
|
|
55
|
+
scope.arity == 1 ? scope.call(user) : scope.call(user, controller.params)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def index_records(relation, opts)
|
|
59
|
+
return relation unless parameters[:paginate]
|
|
60
|
+
|
|
61
|
+
SimpleCrud::Config.pagination_adapter.paginated_records(controller, relation, opts)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class PersistenceContext < ActionContext
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def persist_and_render(record, options, persist)
|
|
8
|
+
saved = parameters[:raise_on_invalid] ? persist.call(bang: true) : persist.call(bang: false)
|
|
9
|
+
return render_persisted(record, saved, options) unless block || parameters[:html]
|
|
10
|
+
|
|
11
|
+
controller.instance_variable_set(:@record, record)
|
|
12
|
+
block ? controller.instance_exec(record, saved, &block) : render_html_redirect(record, saved, options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render_html_redirect(record, saved, options)
|
|
16
|
+
if saved
|
|
17
|
+
controller.redirect_to(redirect_target(record, options[:redirect]))
|
|
18
|
+
else
|
|
19
|
+
controller.render(options[:failure_template])
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def redirect_target(record, target)
|
|
24
|
+
target.is_a?(Proc) ? controller.instance_exec(record, &target) : target
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render_persisted(record, saved, options)
|
|
28
|
+
return controller.render(json: record, status: options[:status]) if saved
|
|
29
|
+
|
|
30
|
+
controller.render json: { errors: record.errors.full_messages }, status: 422
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class ShowContext < ActionContext
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def action_name = :show
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
cache_opts = parameters[:cache]
|
|
11
|
+
if cache_opts
|
|
12
|
+
render_cached(cache_opts) { payload }
|
|
13
|
+
else
|
|
14
|
+
record = find_record
|
|
15
|
+
maybe_authorize(record)
|
|
16
|
+
render_record(record, :show)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def payload
|
|
21
|
+
record = find_record
|
|
22
|
+
maybe_authorize(record)
|
|
23
|
+
controller.instance_variable_set(:@record, record)
|
|
24
|
+
block ? controller.instance_exec(record, &block) : record.as_json
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -2,15 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
require 'active_support/all'
|
|
4
4
|
require_relative 'config'
|
|
5
|
-
require_relative '
|
|
5
|
+
require_relative 'cache_helpers'
|
|
6
|
+
require_relative 'action_context'
|
|
7
|
+
require_relative 'persistence_context'
|
|
8
|
+
require_relative 'show_context'
|
|
9
|
+
require_relative 'index_context'
|
|
10
|
+
require_relative 'new_context'
|
|
11
|
+
require_relative 'edit_context'
|
|
12
|
+
require_relative 'create_context'
|
|
13
|
+
require_relative 'update_context'
|
|
14
|
+
require_relative 'destroy_context'
|
|
6
15
|
require_relative 'action_lambdas'
|
|
7
16
|
|
|
8
17
|
# Extended onto a controller for CRUD actions.
|
|
9
18
|
module SimpleCrudController
|
|
10
|
-
extend SimpleCrud::ControllerHelpers
|
|
11
|
-
# Include, not extend: controllers gain these by extending SimpleCrudController.
|
|
12
19
|
include SimpleCrud::ActionLambdas
|
|
13
20
|
|
|
21
|
+
def self.extended(base)
|
|
22
|
+
base.include(SimpleCrud::CacheHelpers)
|
|
23
|
+
end
|
|
24
|
+
|
|
14
25
|
# Possible options:
|
|
15
26
|
### authorize: check authorization via Config.authorization_adapter (Pundit by default)
|
|
16
27
|
### paginate: paginate the list via Config.pagination_adapter (wor-paginate by default)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCrud
|
|
4
|
+
class UpdateContext < PersistenceContext
|
|
5
|
+
private
|
|
6
|
+
|
|
7
|
+
def run
|
|
8
|
+
record = find_record
|
|
9
|
+
maybe_authorize(record)
|
|
10
|
+
attrs = permitted_params
|
|
11
|
+
persist = ->(bang:) { bang ? record.update!(attrs) : record.update(attrs) }
|
|
12
|
+
options = { status: :ok, failure_template: :edit, redirect: parameters[:redirect] || record }
|
|
13
|
+
persist_and_render(record, options, persist)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/simple_crud/version.rb
CHANGED
|
@@ -52,7 +52,7 @@ RSpec.shared_examples 'simple crud for destroy' do
|
|
|
52
52
|
before do
|
|
53
53
|
model
|
|
54
54
|
allow(model).to receive(:destroy).and_return(false)
|
|
55
|
-
|
|
55
|
+
allow_any_instance_of(SimpleCrud::DestroyContext).to receive(:find_record).and_return(model)
|
|
56
56
|
delete :destroy, params: with_route_params(record_param(:destroy, model)), format: request_format(:destroy)
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe Cached::DummyModelsController, type: :controller do
|
|
6
|
+
before { Rails.cache.clear }
|
|
7
|
+
|
|
8
|
+
include_examples 'simple crud for show with finder'
|
|
9
|
+
|
|
10
|
+
describe 'GET #show' do
|
|
11
|
+
let!(:record) { create(:dummy_model) }
|
|
12
|
+
|
|
13
|
+
it 'serves from cache on repeated requests' do
|
|
14
|
+
get :show, params: { slug: record.slug }
|
|
15
|
+
first_body = response.parsed_body
|
|
16
|
+
|
|
17
|
+
record.update!(name: 'updated')
|
|
18
|
+
get :show, params: { slug: record.slug }
|
|
19
|
+
|
|
20
|
+
expect(response.parsed_body).to eq(first_body)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe CachedDefaults::DummyModelsController, type: :controller do
|
|
6
|
+
before { Rails.cache.clear }
|
|
7
|
+
|
|
8
|
+
include_examples 'simple crud for show'
|
|
9
|
+
|
|
10
|
+
describe 'GET #index' do
|
|
11
|
+
before { create_list(:dummy_model, 2) }
|
|
12
|
+
|
|
13
|
+
it 'returns all records' do
|
|
14
|
+
get :index
|
|
15
|
+
expect(response.parsed_body.length).to eq(2)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe 'expire_simple_crud_cache' do
|
|
20
|
+
let!(:record) { create(:dummy_model) }
|
|
21
|
+
|
|
22
|
+
context 'when called after a cached show' do
|
|
23
|
+
before { get :show, params: { id: record.id } }
|
|
24
|
+
|
|
25
|
+
it 'clears the entry so the next request fetches fresh data' do
|
|
26
|
+
first_body = response.parsed_body
|
|
27
|
+
record.update!(name: 'updated')
|
|
28
|
+
controller.expire_simple_crud_cache(:show)
|
|
29
|
+
get :show, params: { id: record.id }
|
|
30
|
+
expect(response.parsed_body).not_to eq(first_body)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cached
|
|
4
|
+
class DummyModelsController < ApplicationController
|
|
5
|
+
extend SimpleCrudController
|
|
6
|
+
simple_crud_defaults authorize: false, authenticate: false
|
|
7
|
+
|
|
8
|
+
simple_crud_for :show,
|
|
9
|
+
finder: ->(p) { DummyModel.find_by!(slug: p[:slug]) },
|
|
10
|
+
cache: { key: ->(p) { "dm:#{p[:slug]}" }, ttl: 60 } do |record|
|
|
11
|
+
{ cached_id: record.id }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CachedDefaults
|
|
4
|
+
class DummyModelsController < ApplicationController
|
|
5
|
+
extend SimpleCrudController
|
|
6
|
+
simple_crud_defaults authorize: false, authenticate: false
|
|
7
|
+
|
|
8
|
+
simple_crud_for :show, cache: {}
|
|
9
|
+
simple_crud_for :index, paginate: false, cache: {}
|
|
10
|
+
end
|
|
11
|
+
end
|
data/spec/dummy/config/routes.rb
CHANGED
|
@@ -4,6 +4,14 @@ Rails.application.routes.draw do
|
|
|
4
4
|
devise_for :users
|
|
5
5
|
resources :dummy_models
|
|
6
6
|
|
|
7
|
+
namespace :cached do
|
|
8
|
+
resources :dummy_models, only: :show, param: :slug
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
namespace :cached_defaults do
|
|
12
|
+
resources :dummy_models, only: %i[show index]
|
|
13
|
+
end
|
|
14
|
+
|
|
7
15
|
namespace :without_pagination do
|
|
8
16
|
resources :dummy_models, only: :index
|
|
9
17
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe SimpleCrud::ActionContext do
|
|
6
|
+
let(:ctx) { described_class.new(double, double, {}) }
|
|
7
|
+
|
|
8
|
+
it 'raises NotImplementedError when run is not overridden' do
|
|
9
|
+
expect { ctx.send(:run) }.to raise_error(NotImplementedError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'raises NotImplementedError when action_name is not overridden' do
|
|
13
|
+
expect { ctx.send(:action_name) }.to raise_error(NotImplementedError)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
describe '.cache_key_for' do
|
|
17
|
+
it 'builds the standard cache key' do
|
|
18
|
+
key = described_class.cache_key_for(DummyModel, :show, '/dummy_models/1')
|
|
19
|
+
expect(key).to eq('dummy_model:show:v1:/dummy_models/1')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe SimpleCrud::IndexContext do
|
|
6
|
+
describe '#call_scope' do
|
|
7
|
+
after { SimpleCrud::Config.user_method = :current_user }
|
|
8
|
+
|
|
9
|
+
let(:controller) { double(params: { status: 'active' }) }
|
|
10
|
+
|
|
11
|
+
it 'passes nil when controller has no current_user' do
|
|
12
|
+
scope = ->(u) { u ? :scoped : :unscoped }
|
|
13
|
+
ctx = described_class.new(controller, DummyModel, { scope: scope, authorize: false })
|
|
14
|
+
expect(ctx.send(:call_scope)).to eq(:unscoped)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'passes params as second arg when scope has arity 2' do
|
|
18
|
+
scope = ->(u, p) { [u, p[:status]] }
|
|
19
|
+
ctx = described_class.new(controller, DummyModel, { scope: scope, authorize: false })
|
|
20
|
+
expect(ctx.send(:call_scope)).to eq([nil, 'active'])
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'resolves user via overridden Config.user_method' do
|
|
24
|
+
SimpleCrud::Config.user_method = :current_admin
|
|
25
|
+
admin = double
|
|
26
|
+
ctrl = double(params: {}, current_admin: admin)
|
|
27
|
+
ctx = described_class.new(ctrl, DummyModel, { scope: ->(u) { u }, authorize: false })
|
|
28
|
+
expect(ctx.send(:call_scope)).to eq(admin)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe SimpleCrud::ShowContext do
|
|
6
|
+
describe '#find_record' do
|
|
7
|
+
it 'raises when a custom finder returns something other than a record' do
|
|
8
|
+
finder = ->(_params) { DummyModel.where(name: 'x') }
|
|
9
|
+
ctx = described_class.new(double(params: {}), DummyModel, { finder: finder, authorize: false })
|
|
10
|
+
expect { ctx.send(:find_record) }
|
|
11
|
+
.to raise_error(ActiveRecord::RecordNotFound, /must return a single record/)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: wor-simple_crud
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- icoluccio
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-09-01 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: rails
|
|
@@ -48,18 +48,25 @@ files:
|
|
|
48
48
|
- README.md
|
|
49
49
|
- Rakefile
|
|
50
50
|
- lib/simple_crud.rb
|
|
51
|
+
- lib/simple_crud/action_context.rb
|
|
51
52
|
- lib/simple_crud/action_lambdas.rb
|
|
52
53
|
- lib/simple_crud/authorization/action_policy_adapter.rb
|
|
53
54
|
- lib/simple_crud/authorization/adapter.rb
|
|
54
55
|
- lib/simple_crud/authorization/can_can_can_adapter.rb
|
|
55
56
|
- lib/simple_crud/authorization/pundit_adapter.rb
|
|
57
|
+
- lib/simple_crud/cache_helpers.rb
|
|
56
58
|
- lib/simple_crud/config.rb
|
|
57
|
-
- lib/simple_crud/
|
|
59
|
+
- lib/simple_crud/create_context.rb
|
|
60
|
+
- lib/simple_crud/destroy_context.rb
|
|
61
|
+
- lib/simple_crud/edit_context.rb
|
|
62
|
+
- lib/simple_crud/index_context.rb
|
|
63
|
+
- lib/simple_crud/new_context.rb
|
|
58
64
|
- lib/simple_crud/pagination/adapter.rb
|
|
59
65
|
- lib/simple_crud/pagination/kaminari_adapter.rb
|
|
60
66
|
- lib/simple_crud/pagination/pagy_adapter.rb
|
|
61
67
|
- lib/simple_crud/pagination/will_paginate_adapter.rb
|
|
62
68
|
- lib/simple_crud/pagination/wor_paginate_adapter.rb
|
|
69
|
+
- lib/simple_crud/persistence_context.rb
|
|
63
70
|
- lib/simple_crud/rspec.rb
|
|
64
71
|
- lib/simple_crud/rspec/config.rb
|
|
65
72
|
- lib/simple_crud/rspec/helpers.rb
|
|
@@ -69,7 +76,9 @@ files:
|
|
|
69
76
|
- lib/simple_crud/rspec/helpers/policies.rb
|
|
70
77
|
- lib/simple_crud/rspec/helpers/requests.rb
|
|
71
78
|
- lib/simple_crud/rspec/helpers/settings.rb
|
|
79
|
+
- lib/simple_crud/show_context.rb
|
|
72
80
|
- lib/simple_crud/simple_crud_controller.rb
|
|
81
|
+
- lib/simple_crud/update_context.rb
|
|
73
82
|
- lib/simple_crud/version.rb
|
|
74
83
|
- lib/spec/matchers/have_been_serialized_with.rb
|
|
75
84
|
- lib/spec/response_helper.rb
|
|
@@ -108,6 +117,8 @@ files:
|
|
|
108
117
|
- spec/block_redirect/dummy_models_controller_spec.rb
|
|
109
118
|
- spec/block_show/dummy_models_controller_spec.rb
|
|
110
119
|
- spec/built/dummy_models_controller_spec.rb
|
|
120
|
+
- spec/cached/dummy_models_controller_spec.rb
|
|
121
|
+
- spec/cached_defaults/dummy_models_controller_spec.rb
|
|
111
122
|
- spec/dummy/Rakefile
|
|
112
123
|
- spec/dummy/app/assets/config/manifest.js
|
|
113
124
|
- spec/dummy/app/controllers/application_controller.rb
|
|
@@ -121,6 +132,8 @@ files:
|
|
|
121
132
|
- spec/dummy/app/controllers/block_redirect/dummy_models_controller.rb
|
|
122
133
|
- spec/dummy/app/controllers/block_show/dummy_models_controller.rb
|
|
123
134
|
- spec/dummy/app/controllers/built/dummy_models_controller.rb
|
|
135
|
+
- spec/dummy/app/controllers/cached/dummy_models_controller.rb
|
|
136
|
+
- spec/dummy/app/controllers/cached_defaults/dummy_models_controller.rb
|
|
124
137
|
- spec/dummy/app/controllers/dummy_models_controller.rb
|
|
125
138
|
- spec/dummy/app/controllers/finder/dummy_models_controller.rb
|
|
126
139
|
- spec/dummy/app/controllers/html/dummy_models_controller.rb
|
|
@@ -211,12 +224,14 @@ files:
|
|
|
211
224
|
- spec/redirect_auth/dummy_models_controller_spec.rb
|
|
212
225
|
- spec/scoped/dummy_models_controller_spec.rb
|
|
213
226
|
- spec/scoped_params/dummy_models_controller_spec.rb
|
|
227
|
+
- spec/simple_crud/action_context_spec.rb
|
|
214
228
|
- spec/simple_crud/authorization/action_policy_adapter_spec.rb
|
|
215
229
|
- spec/simple_crud/authorization/can_can_can_adapter_spec.rb
|
|
216
230
|
- spec/simple_crud/authorization/pundit_adapter_spec.rb
|
|
217
|
-
- spec/simple_crud/
|
|
231
|
+
- spec/simple_crud/index_context_spec.rb
|
|
218
232
|
- spec/simple_crud/pagination/adapters_spec.rb
|
|
219
233
|
- spec/simple_crud/rspec_config_spec.rb
|
|
234
|
+
- spec/simple_crud/show_context_spec.rb
|
|
220
235
|
- spec/simple_crud_configuration_spec.rb
|
|
221
236
|
- spec/simple_crud_controller_spec.rb
|
|
222
237
|
- spec/spec_helper.rb
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SimpleCrud
|
|
4
|
-
# Class-level helpers shared by the CRUD lambdas defined in SimpleCrudController.
|
|
5
|
-
module ControllerHelpers
|
|
6
|
-
def maybe_authorize(controller, record, parameters)
|
|
7
|
-
return unless parameters[:authorize]
|
|
8
|
-
|
|
9
|
-
SimpleCrud::Config.authorization_adapter.authorize(controller, record)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def render_index(controller, klass, options, parameters, &block)
|
|
13
|
-
relation = index_relation(controller, klass, parameters)
|
|
14
|
-
|
|
15
|
-
if parameters[:html] || block
|
|
16
|
-
records = index_records(controller, relation, options, parameters)
|
|
17
|
-
controller.instance_variable_set(:@records, records)
|
|
18
|
-
block ? controller.instance_exec(records, &block) : controller.render(:index)
|
|
19
|
-
elsif parameters[:paginate]
|
|
20
|
-
SimpleCrud::Config.pagination_adapter.paginate(controller, relation, options)
|
|
21
|
-
else
|
|
22
|
-
controller.render({ json: relation }.merge(options))
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def render_show(controller, record, options, parameters, &block)
|
|
27
|
-
if parameters[:html] || block
|
|
28
|
-
controller.instance_variable_set(:@record, record)
|
|
29
|
-
block ? controller.instance_exec(record, &block) : controller.render(:show)
|
|
30
|
-
else
|
|
31
|
-
controller.render({ json: record }.merge(options))
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def render_new(controller, record, parameters, &block)
|
|
36
|
-
render_form(controller, record, :new, parameters, &block)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def render_edit(controller, record, parameters, &block)
|
|
40
|
-
render_form(controller, record, :edit, parameters, &block)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def render_form(controller, record, template, parameters, &block)
|
|
44
|
-
if parameters[:html] || block
|
|
45
|
-
controller.instance_variable_set(:@record, record)
|
|
46
|
-
block ? controller.instance_exec(record, &block) : controller.render(template)
|
|
47
|
-
else
|
|
48
|
-
options = {}.merge(serializer: parameters[:serializer]).compact
|
|
49
|
-
controller.render({ json: record }.merge(options))
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def build_record(controller, klass, parameters)
|
|
54
|
-
parameters[:build] ? controller.instance_exec(¶meters[:build]) : klass.new
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def index_relation(controller, klass, parameters)
|
|
58
|
-
if parameters[:scope]
|
|
59
|
-
call_scope(parameters[:scope], controller)
|
|
60
|
-
elsif parameters[:authorize]
|
|
61
|
-
SimpleCrud::Config.authorization_adapter.policy_scope(controller, klass)
|
|
62
|
-
else
|
|
63
|
-
klass.all
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def call_scope(scope, controller)
|
|
68
|
-
user_method = SimpleCrud::Config.user_method
|
|
69
|
-
user = controller.respond_to?(user_method) ? controller.public_send(user_method) : nil
|
|
70
|
-
return scope.call(user) if scope.arity == 1
|
|
71
|
-
|
|
72
|
-
scope.call(user, controller.params)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def index_records(controller, relation, options, parameters)
|
|
76
|
-
return relation unless parameters[:paginate]
|
|
77
|
-
|
|
78
|
-
SimpleCrud::Config.pagination_adapter.paginated_records(controller, relation, options)
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def find_record(klass, controller, parameters)
|
|
82
|
-
record = lookup_record(klass, controller, parameters)
|
|
83
|
-
raise ActiveRecord::RecordNotFound, "couldn't find #{klass}" if record.nil?
|
|
84
|
-
unless record.is_a?(ActiveRecord::Base)
|
|
85
|
-
raise ActiveRecord::RecordNotFound, "#{klass} finder must return a single record"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
record
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def lookup_record(klass, controller, parameters)
|
|
92
|
-
finder = parameters[:finder]
|
|
93
|
-
return klass.find(controller.params[:id]) if finder.nil?
|
|
94
|
-
return klass.send(finder, controller.params) unless finder.respond_to?(:call)
|
|
95
|
-
|
|
96
|
-
finder.call(controller.params)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def persist_and_render(controller, record, parameters, options, persist, &block)
|
|
100
|
-
saved = parameters[:raise_on_invalid] ? persist.call(bang: true) : persist.call(bang: false)
|
|
101
|
-
return render_persisted(controller, record, saved, options) unless block || parameters[:html]
|
|
102
|
-
|
|
103
|
-
controller.instance_variable_set(:@record, record)
|
|
104
|
-
return controller.instance_exec(record, saved, &block) if block
|
|
105
|
-
|
|
106
|
-
if saved
|
|
107
|
-
controller.redirect_to(redirect_target(controller, record, options[:redirect]))
|
|
108
|
-
else
|
|
109
|
-
controller.render(options[:failure_template])
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def save_and_render(controller, record, parameters, options, persist, &block)
|
|
114
|
-
persist_and_render(controller, record, parameters,
|
|
115
|
-
options.merge(redirect: parameters[:redirect] || record), persist, &block)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def redirect_target(controller, record, target)
|
|
119
|
-
target.is_a?(Proc) ? controller.instance_exec(record, &target) : target
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def render_persisted(controller, record, saved, options)
|
|
123
|
-
return controller.render(json: record, status: options[:status]) if saved
|
|
124
|
-
|
|
125
|
-
controller.render json: { errors: record.errors.full_messages }, status: 422
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
require 'simple_crud/controller_helpers'
|
|
5
|
-
|
|
6
|
-
describe SimpleCrud::ControllerHelpers do
|
|
7
|
-
subject(:helpers) do
|
|
8
|
-
Class.new do
|
|
9
|
-
extend SimpleCrud::ControllerHelpers
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
describe '.call_scope' do
|
|
14
|
-
after { SimpleCrud::Config.user_method = :current_user }
|
|
15
|
-
|
|
16
|
-
it 'passes nil as the user when the controller has no current_user' do
|
|
17
|
-
controller = double(params: { status: 'active' })
|
|
18
|
-
scope = ->(user) { user ? :scoped_to_user : :unscoped }
|
|
19
|
-
|
|
20
|
-
expect(helpers.call_scope(scope, controller)).to eq(:unscoped)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
it 'still passes params as the second argument when the scope takes two' do
|
|
24
|
-
controller = double(params: { status: 'active' })
|
|
25
|
-
scope = ->(user, params) { [user, params[:status]] }
|
|
26
|
-
|
|
27
|
-
expect(helpers.call_scope(scope, controller)).to eq([nil, 'active'])
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'resolves the user via Config.user_method when overridden' do
|
|
31
|
-
SimpleCrud::Config.user_method = :current_admin
|
|
32
|
-
admin = double
|
|
33
|
-
controller = double(params: {}, current_admin: admin)
|
|
34
|
-
scope = ->(user) { user }
|
|
35
|
-
|
|
36
|
-
expect(helpers.call_scope(scope, controller)).to eq(admin)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
describe '.find_record' do
|
|
41
|
-
it 'raises when a custom finder returns something other than a record' do
|
|
42
|
-
controller = double(params: {})
|
|
43
|
-
parameters = { finder: ->(_params) { DummyModel.where(name: 'x') } }
|
|
44
|
-
|
|
45
|
-
expect { helpers.find_record(DummyModel, controller, parameters) }
|
|
46
|
-
.to raise_error(ActiveRecord::RecordNotFound, /must return a single record/)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|