typical_situation 1.1.0 → 1.3.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/README.md +42 -22
- data/config/locales/typical_situation.en.yml +2 -0
- data/lib/typical_situation/actions.rb +20 -4
- data/lib/typical_situation/identity.rb +4 -4
- data/lib/typical_situation/operations.rb +22 -22
- data/lib/typical_situation/responses.rb +59 -52
- data/lib/typical_situation/serialization.rb +37 -0
- data/lib/typical_situation/version.rb +1 -1
- data/lib/typical_situation.rb +23 -11
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1784b34111a7c2de6a6f429d431d5fc36e00ec033f5e43c1618fc8a198ff534c
|
|
4
|
+
data.tar.gz: 4c40c7fab53aacffff4d30dbe5578fae9019e798384c086b36e4566bdb46baee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e88563fb73ebb702a0b673920e1e85b925799a9287b3336fec394dc8a52952234e296576d7c39a05e015e95611185a1607e7cb0feb005af6a1406caef15b488b
|
|
7
|
+
data.tar.gz: 1a663cdf260368f6ab83b7fadd99128a62cbae89ba67b2d50881232a0f388cc319ae4b5d3b6c4029e47145710be28c344e2608bae30cd2203fcaf5ccb6c95fe8
|
data/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Add to your **Gemfile**:
|
|
|
30
30
|
|
|
31
31
|
### Define your model and methods
|
|
32
32
|
|
|
33
|
-
Basic usage is to declare
|
|
33
|
+
Basic usage is to declare `typical_situation` and define the required `collection` helper. Member lookup uses the model's configured single-column primary key within the security-scoped collection. Missing identifiers return `nil` from the lookup and produce a 404 response.
|
|
34
34
|
|
|
35
35
|
```rb
|
|
36
36
|
class PostsController < ApplicationController
|
|
@@ -45,11 +45,6 @@ class PostsController < ApplicationController
|
|
|
45
45
|
def collection
|
|
46
46
|
current_user.posts
|
|
47
47
|
end
|
|
48
|
-
|
|
49
|
-
# Find a model instance by ID.
|
|
50
|
-
def find_in_collection(id)
|
|
51
|
-
collection.find_by_id(id)
|
|
52
|
-
end
|
|
53
48
|
end
|
|
54
49
|
```
|
|
55
50
|
|
|
@@ -108,6 +103,7 @@ The library is split into modules:
|
|
|
108
103
|
- [operations](https://github.com/mars/typical_situation/blob/master/lib/typical_situation/operations.rb) - loading, changing, & persisting the model
|
|
109
104
|
- [permissions](https://github.com/mars/typical_situation/blob/master/lib/typical_situation/permissions.rb) - handling authorization to records and actions
|
|
110
105
|
- [responses](https://github.com/mars/typical_situation/blob/master/lib/typical_situation/responses.rb) - HTTP responses & redirects
|
|
106
|
+
- [serialization](lib/typical_situation/serialization.rb) - resource representation and JSON root wrapping
|
|
111
107
|
|
|
112
108
|
#### Common Customization Hooks
|
|
113
109
|
|
|
@@ -117,11 +113,12 @@ For index actions, resources move through this pipeline:
|
|
|
117
113
|
|
|
118
114
|
1. `collection` - base relation from the host controller
|
|
119
115
|
2. `scoped_resource` - visibility/tenant/security scoping
|
|
120
|
-
3. `
|
|
116
|
+
3. `apply_filtering` - request-driven search and filter transforms
|
|
121
117
|
4. `apply_sorting` - default sorting from `default_sorting_attribute`
|
|
122
118
|
5. `paginate_resources` - pagination adapter hook
|
|
119
|
+
6. `prepare_resources` - post-process the loaded records
|
|
123
120
|
|
|
124
|
-
**Scoped Collections** - Restrict the base collection based on user permissions, tenancy, or other security boundaries:
|
|
121
|
+
**Scoped Collections** - Restrict the base collection based on user permissions, tenancy, or other security boundaries. This scope applies to index and member lookup for show, edit, update, and destroy. Filtering, sorting, and pagination remain index-only. New/create use `collection`, so read-visibility conditions do not become creation defaults:
|
|
125
122
|
|
|
126
123
|
```ruby
|
|
127
124
|
def scoped_resource
|
|
@@ -136,21 +133,31 @@ end
|
|
|
136
133
|
**Search and Filtering** - Apply request-driven filters after scoping and before sorting/pagination:
|
|
137
134
|
|
|
138
135
|
```ruby
|
|
139
|
-
def
|
|
136
|
+
def apply_filtering(resources)
|
|
140
137
|
resources = resources.where(status: params[:status]) if params[:status].present?
|
|
141
138
|
resources = resources.where("title ILIKE ?", "%#{params[:q]}%") if params[:q].present?
|
|
142
139
|
resources
|
|
143
140
|
end
|
|
144
141
|
```
|
|
145
142
|
|
|
143
|
+
**Post-processing** - Mutate or decorate the final paginated record set (e.g. compute a derived attribute on every record):
|
|
144
|
+
|
|
145
|
+
```ruby
|
|
146
|
+
def prepare_resources(resources)
|
|
147
|
+
resources.each { |post| post.current_user_liked = liked_post_ids.include?(post.id) }
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
146
151
|
**Custom Lookup** - Use different attributes for finding resources:
|
|
147
152
|
|
|
148
153
|
```ruby
|
|
149
|
-
def
|
|
150
|
-
|
|
154
|
+
def find_in_collection(resources, slug)
|
|
155
|
+
resources.find_by(slug: slug)
|
|
151
156
|
end
|
|
152
157
|
```
|
|
153
158
|
|
|
159
|
+
The lookup receives `scoped_resource`; query that relation rather than `collection` to preserve security boundaries. Overriding `find_resource` replaces the security-aware lookup entirely.
|
|
160
|
+
|
|
154
161
|
**Custom Redirects** - Control where users go after actions:
|
|
155
162
|
|
|
156
163
|
```ruby
|
|
@@ -323,7 +330,9 @@ end
|
|
|
323
330
|
|
|
324
331
|
#### Serialization
|
|
325
332
|
|
|
326
|
-
|
|
333
|
+
`TypicalSituation` calls `as_json(root: false)` by default. Override `serializable_resource` to return an unrooted Ruby `Hash` for a member or an `Array` of resource data for a collection. Rails encodes the response once; returning an encoded JSON string raises `TypeError`.
|
|
334
|
+
|
|
335
|
+
`include_root?` controls wrapping with `model_type` for members and `plural_model_type` for collections. Error responses merge model errors under `errors`, independently of the custom serializer.
|
|
327
336
|
|
|
328
337
|
##### Alba
|
|
329
338
|
|
|
@@ -343,16 +352,12 @@ class MockApplePiesController < ApplicationController
|
|
|
343
352
|
private
|
|
344
353
|
|
|
345
354
|
def serializable_resource(resource)
|
|
346
|
-
MockApplePieResource.new(resource).
|
|
355
|
+
MockApplePieResource.new(resource).to_h
|
|
347
356
|
end
|
|
348
357
|
|
|
349
358
|
def collection
|
|
350
359
|
current_user.mock_apple_pies
|
|
351
360
|
end
|
|
352
|
-
|
|
353
|
-
def find_in_collection(id)
|
|
354
|
-
collection.find_by_id(id)
|
|
355
|
-
end
|
|
356
361
|
end
|
|
357
362
|
```
|
|
358
363
|
|
|
@@ -364,15 +369,19 @@ class MockApplePieIndexSerializer < ActiveModel::Serializer
|
|
|
364
369
|
end
|
|
365
370
|
|
|
366
371
|
module TypicalSituation
|
|
367
|
-
module
|
|
372
|
+
module Serialization
|
|
368
373
|
def serializable_resource(resource)
|
|
369
374
|
if action_name == "index"
|
|
370
375
|
ActiveModelSerializers::SerializableResource.new(
|
|
371
376
|
resource,
|
|
372
|
-
each_serializer: MockApplePieIndexSerializer
|
|
373
|
-
|
|
377
|
+
each_serializer: MockApplePieIndexSerializer,
|
|
378
|
+
adapter: :attributes
|
|
379
|
+
).as_json
|
|
374
380
|
else
|
|
375
|
-
ActiveModelSerializers::SerializableResource.new(
|
|
381
|
+
ActiveModelSerializers::SerializableResource.new(
|
|
382
|
+
resource,
|
|
383
|
+
adapter: :attributes
|
|
384
|
+
).as_json
|
|
376
385
|
end
|
|
377
386
|
end
|
|
378
387
|
end
|
|
@@ -392,11 +401,22 @@ class MockApplePiesController < ApplicationController
|
|
|
392
401
|
include TypicalSituation
|
|
393
402
|
|
|
394
403
|
def serializable_resource(resource)
|
|
395
|
-
MockApplePieSerializer.new(resource).serializable_hash
|
|
404
|
+
MockApplePieSerializer.new(resource).serializable_hash.fetch(:data)
|
|
396
405
|
end
|
|
397
406
|
end
|
|
398
407
|
```
|
|
399
408
|
|
|
409
|
+
## Migrating Controller Hooks
|
|
410
|
+
|
|
411
|
+
These hook changes are breaking:
|
|
412
|
+
|
|
413
|
+
- Remove redundant `find_in_collection(id)` overrides, or change them to `find_in_collection(resources, id)` and query the supplied scoped relation.
|
|
414
|
+
- `update_resource(resource, attrs)` must return a success boolean. `create_resource` and `destroy_resource` still return resources; success is determined by `persisted?` and `destroyed?`, respectively. Callback aborts return 422 even when no validation errors were added.
|
|
415
|
+
- Overrides of `respond_as_created`, `respond_as_changed`, and `respond_as_gone` must accept the required `success:` keyword and pass it to `super` when delegating.
|
|
416
|
+
- HTML creation uses `created_so_redirect` and `after_resource_created_path`; updates continue using `changed_so_redirect` and `after_resource_updated_path`.
|
|
417
|
+
- Serialization methods now live in `TypicalSituation::Serialization`, not `Operations`. Return Ruby data from `serializable_resource`, not JSON strings or serializer objects. Overrides of `serialize_resource` should accept `errors: false` instead of an options hash.
|
|
418
|
+
- Failed operations preserve model errors or add `typical_situation.errors.operation_failed` when none exist. Failed HTML destruction sets the error flash; JSON failures do not set flash messages.
|
|
419
|
+
|
|
400
420
|
## Development
|
|
401
421
|
|
|
402
422
|
After checking out the repo, run `bin/setup` to install dependencies.
|
|
@@ -35,8 +35,12 @@ module TypicalSituation
|
|
|
35
35
|
get_resource
|
|
36
36
|
raise TypicalSituation::ActionForbidden unless authorized?(:update, @resource)
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
success = normalize_operation_result(
|
|
39
|
+
@resource,
|
|
40
|
+
success: update_resource(@resource, update_params)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
respond_as_changed(success: success)
|
|
40
44
|
end
|
|
41
45
|
|
|
42
46
|
def destroy
|
|
@@ -44,14 +48,26 @@ module TypicalSituation
|
|
|
44
48
|
raise TypicalSituation::ActionForbidden unless authorized?(:destroy, @resource)
|
|
45
49
|
|
|
46
50
|
destroy_resource(@resource)
|
|
47
|
-
|
|
51
|
+
|
|
52
|
+
success = normalize_operation_result(
|
|
53
|
+
@resource,
|
|
54
|
+
success: @resource.destroyed?
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
respond_as_gone(success: success)
|
|
48
58
|
end
|
|
49
59
|
|
|
50
60
|
def create
|
|
51
61
|
raise TypicalSituation::ActionForbidden unless authorized?(:create)
|
|
52
62
|
|
|
53
63
|
@resource = create_resource(create_params)
|
|
54
|
-
|
|
64
|
+
|
|
65
|
+
success = normalize_operation_result(
|
|
66
|
+
@resource,
|
|
67
|
+
success: @resource.persisted?
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
respond_as_created(success: success)
|
|
55
71
|
end
|
|
56
72
|
end
|
|
57
73
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module TypicalSituation
|
|
4
|
-
#
|
|
4
|
+
# Model identity, collection, and request configuration.
|
|
5
5
|
module Identity
|
|
6
6
|
protected
|
|
7
7
|
|
|
@@ -43,9 +43,9 @@ module TypicalSituation
|
|
|
43
43
|
raise(NotImplementedError, "#collection must be defined in the TypicalSituation implementation.")
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
# Find a model instance by
|
|
47
|
-
def find_in_collection(
|
|
48
|
-
|
|
46
|
+
# Find a model instance by its configured primary key.
|
|
47
|
+
def find_in_collection(resources, identifier)
|
|
48
|
+
resources.find_by(resources.primary_key => identifier)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def include_root?
|
|
@@ -11,7 +11,7 @@ module TypicalSituation
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def find_resource(param)
|
|
14
|
-
find_in_collection(param)
|
|
14
|
+
find_in_collection(scoped_resource, param)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def default_sorting_attribute
|
|
@@ -46,22 +46,28 @@ module TypicalSituation
|
|
|
46
46
|
# Collection pipeline lifecycle:
|
|
47
47
|
# collection - base relation (user-defined, required)
|
|
48
48
|
# scoped_resource - wraps/scopes the collection (visibility, tenancy, PHI)
|
|
49
|
-
#
|
|
49
|
+
# apply_filtering - applies search/filter params
|
|
50
50
|
# apply_sorting - applies ORDER BY
|
|
51
51
|
# paginate_resources - applies pagination
|
|
52
|
+
# prepare_resources - post-processes the loaded records (e.g. set a computed attribute)
|
|
52
53
|
#
|
|
53
|
-
# Override +
|
|
54
|
-
#
|
|
54
|
+
# Override +apply_filtering+ to add search/filter behavior.
|
|
55
|
+
# Override +prepare_resources+ to mutate or decorate the final record set.
|
|
55
56
|
def get_resources
|
|
56
57
|
resources = scoped_resource
|
|
57
|
-
resources =
|
|
58
|
+
resources = apply_filtering(resources)
|
|
58
59
|
resources = apply_sorting(resources)
|
|
59
60
|
resources = paginate_resources(resources)
|
|
61
|
+
resources = prepare_resources(resources)
|
|
60
62
|
@resources = resources
|
|
61
63
|
set_collection_instance
|
|
62
64
|
@resources
|
|
63
65
|
end
|
|
64
66
|
|
|
67
|
+
def apply_filtering(resources)
|
|
68
|
+
resources
|
|
69
|
+
end
|
|
70
|
+
|
|
65
71
|
def prepare_resources(resources)
|
|
66
72
|
resources
|
|
67
73
|
end
|
|
@@ -80,7 +86,6 @@ module TypicalSituation
|
|
|
80
86
|
|
|
81
87
|
def destroy_resource(resource)
|
|
82
88
|
resource.destroy
|
|
83
|
-
collection.reload if resource.errors.empty?
|
|
84
89
|
resource
|
|
85
90
|
end
|
|
86
91
|
|
|
@@ -96,28 +101,23 @@ module TypicalSituation
|
|
|
96
101
|
@model_class ||= model_type.to_s.camelize.constantize
|
|
97
102
|
end
|
|
98
103
|
|
|
99
|
-
def serialize_resource(resource, options = {})
|
|
100
|
-
serializable_resource(resource).to_json(options.merge(root: include_root?))
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def serialize_resources(resources)
|
|
104
|
-
if include_root?
|
|
105
|
-
return {plural_model_type => serializable_resource(resources)}
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
serializable_resource(resources).to_json(root: false)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def serializable_resource(resource)
|
|
112
|
-
resource
|
|
113
|
-
end
|
|
114
|
-
|
|
115
104
|
def id_param
|
|
116
105
|
params[:id]
|
|
117
106
|
end
|
|
118
107
|
|
|
119
108
|
private
|
|
120
109
|
|
|
110
|
+
def normalize_operation_result(resource, success:)
|
|
111
|
+
if !success && resource.errors.empty?
|
|
112
|
+
resource.errors.add(
|
|
113
|
+
:base,
|
|
114
|
+
I18n.t("typical_situation.errors.operation_failed")
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
success
|
|
119
|
+
end
|
|
120
|
+
|
|
121
121
|
def apply_sorting(resources)
|
|
122
122
|
return resources unless default_sorting_attribute
|
|
123
123
|
resources.order(default_sorting_attribute => default_sorting_direction)
|
|
@@ -39,42 +39,40 @@ module TypicalSituation
|
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def respond_as_changed
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
end
|
|
42
|
+
def respond_as_changed(success:)
|
|
43
|
+
return respond_as_failure(:update) unless success
|
|
44
|
+
|
|
45
|
+
respond_to do |format|
|
|
46
|
+
yield(format) if block_given?
|
|
47
|
+
|
|
48
|
+
format.html do
|
|
49
|
+
set_success_flash(:update)
|
|
50
|
+
set_single_instance
|
|
51
|
+
changed_so_redirect || render
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
format.json do
|
|
55
|
+
render json: serialize_resource(@resource)
|
|
57
56
|
end
|
|
58
57
|
end
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
def respond_as_created
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
60
|
+
def respond_as_created(success:)
|
|
61
|
+
return respond_as_failure(:create) unless success
|
|
62
|
+
|
|
63
|
+
respond_to do |format|
|
|
64
|
+
yield(format) if block_given?
|
|
65
|
+
|
|
66
|
+
format.html do
|
|
67
|
+
set_success_flash(:create)
|
|
68
|
+
set_single_instance
|
|
69
|
+
created_so_redirect || render
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
format.json do
|
|
73
|
+
render json: serialize_resource(@resource),
|
|
74
|
+
location: location_url,
|
|
75
|
+
status: :created
|
|
78
76
|
end
|
|
79
77
|
end
|
|
80
78
|
end
|
|
@@ -89,27 +87,25 @@ module TypicalSituation
|
|
|
89
87
|
status: :unprocessable_entity
|
|
90
88
|
end
|
|
91
89
|
format.json do
|
|
92
|
-
render json: serialize_resource(@resource,
|
|
90
|
+
render json: serialize_resource(@resource, errors: true),
|
|
93
91
|
status: :unprocessable_entity
|
|
94
92
|
end
|
|
95
93
|
end
|
|
96
94
|
end
|
|
97
95
|
|
|
98
|
-
def respond_as_gone
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
head :no_content
|
|
112
|
-
end
|
|
96
|
+
def respond_as_gone(success:)
|
|
97
|
+
return respond_as_failure(:destroy) unless success
|
|
98
|
+
|
|
99
|
+
respond_to do |format|
|
|
100
|
+
yield(format) if block_given?
|
|
101
|
+
|
|
102
|
+
format.html do
|
|
103
|
+
set_single_instance
|
|
104
|
+
gone_so_redirect || render
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
format.json do
|
|
108
|
+
head :no_content
|
|
113
109
|
end
|
|
114
110
|
end
|
|
115
111
|
end
|
|
@@ -135,17 +131,22 @@ module TypicalSituation
|
|
|
135
131
|
end
|
|
136
132
|
|
|
137
133
|
def after_resource_created_path(resource)
|
|
138
|
-
{action: :show, id: resource.
|
|
134
|
+
{action: :show, id: resource.to_param}
|
|
139
135
|
end
|
|
140
136
|
|
|
141
137
|
def after_resource_updated_path(resource)
|
|
142
|
-
{action: :show, id: resource.
|
|
138
|
+
{action: :show, id: resource.to_param}
|
|
143
139
|
end
|
|
144
140
|
|
|
145
141
|
def after_resource_destroyed_path(_resource)
|
|
146
142
|
{action: :index}
|
|
147
143
|
end
|
|
148
144
|
|
|
145
|
+
def created_so_redirect
|
|
146
|
+
redirect_to after_resource_created_path(@resource)
|
|
147
|
+
true
|
|
148
|
+
end
|
|
149
|
+
|
|
149
150
|
# HTML response when @resource saved or updated.
|
|
150
151
|
def changed_so_redirect
|
|
151
152
|
redirect_to after_resource_updated_path(@resource)
|
|
@@ -154,10 +155,16 @@ module TypicalSituation
|
|
|
154
155
|
|
|
155
156
|
# HTML response when @resource deleted.
|
|
156
157
|
def gone_so_redirect
|
|
157
|
-
|
|
158
|
-
set_success_flash(:destroy) unless has_errors?
|
|
158
|
+
set_success_flash(:destroy)
|
|
159
159
|
redirect_to after_resource_destroyed_path(@resource)
|
|
160
160
|
true
|
|
161
161
|
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def respond_as_failure(action)
|
|
166
|
+
set_error_flash if action == :destroy && request.format.html?
|
|
167
|
+
respond_as_error
|
|
168
|
+
end
|
|
162
169
|
end
|
|
163
170
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module TypicalSituation
|
|
4
|
+
module Serialization
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def serialize_resource(resource, errors: false)
|
|
8
|
+
data = serialization_data(resource, Hash)
|
|
9
|
+
data = data.merge("errors" => resource.errors.as_json) if errors
|
|
10
|
+
|
|
11
|
+
include_root? ? {model_type => data} : data
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def serialize_resources(resources)
|
|
15
|
+
data = serialization_data(resources, Array)
|
|
16
|
+
|
|
17
|
+
include_root? ? {plural_model_type => data} : data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def serializable_resource(resource)
|
|
21
|
+
resource.as_json(root: false)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def serialization_data(resource, expected_type)
|
|
27
|
+
data = serializable_resource(resource)
|
|
28
|
+
|
|
29
|
+
unless data.is_a?(expected_type)
|
|
30
|
+
raise TypeError,
|
|
31
|
+
"serializable_resource must return #{expected_type}, got #{data.class}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
data
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/typical_situation.rb
CHANGED
|
@@ -7,19 +7,28 @@ require "typical_situation/flash_messages"
|
|
|
7
7
|
require "typical_situation/actions"
|
|
8
8
|
require "typical_situation/operations"
|
|
9
9
|
require "typical_situation/responses"
|
|
10
|
+
require "typical_situation/serialization"
|
|
10
11
|
|
|
11
12
|
module TypicalSituation
|
|
12
13
|
class Error < StandardError; end
|
|
13
14
|
class ActionForbidden < Error; end
|
|
14
15
|
|
|
16
|
+
ACTION_NAMES = Actions.public_instance_methods(false).map(&:to_s).freeze
|
|
17
|
+
|
|
15
18
|
include Identity
|
|
16
19
|
include Permissions
|
|
17
20
|
include FlashMessages
|
|
18
21
|
include Operations
|
|
19
22
|
include Responses
|
|
23
|
+
include Serialization
|
|
20
24
|
|
|
21
25
|
def self.included(base)
|
|
22
26
|
add_rescues(base)
|
|
27
|
+
|
|
28
|
+
base.class_attribute :typical_action_names,
|
|
29
|
+
instance_accessor: false,
|
|
30
|
+
default: ACTION_NAMES
|
|
31
|
+
|
|
23
32
|
base.extend(ClassMethods)
|
|
24
33
|
end
|
|
25
34
|
|
|
@@ -37,20 +46,23 @@ module TypicalSituation
|
|
|
37
46
|
# :post
|
|
38
47
|
# end
|
|
39
48
|
def typical_situation(model_type_symbol, only: nil)
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
actions = only.nil? ? ACTION_NAMES : Array(only).map(&:to_s)
|
|
50
|
+
unknown = actions - ACTION_NAMES
|
|
51
|
+
|
|
52
|
+
if unknown.any?
|
|
53
|
+
raise ArgumentError, "Unknown actions: #{unknown.join(", ")}"
|
|
42
54
|
end
|
|
55
|
+
|
|
56
|
+
self.typical_action_names = actions.freeze
|
|
57
|
+
|
|
58
|
+
define_method(:model_type) { model_type_symbol }
|
|
43
59
|
protected :model_type
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
else
|
|
52
|
-
include TypicalSituation::Actions
|
|
53
|
-
end
|
|
61
|
+
include TypicalSituation::Actions
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def action_methods
|
|
65
|
+
super - (ACTION_NAMES - typical_action_names)
|
|
54
66
|
end
|
|
55
67
|
|
|
56
68
|
def typical_rest(model_type_symbol)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: typical_situation
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mars Hall
|
|
@@ -213,6 +213,7 @@ files:
|
|
|
213
213
|
- lib/typical_situation/operations.rb
|
|
214
214
|
- lib/typical_situation/permissions.rb
|
|
215
215
|
- lib/typical_situation/responses.rb
|
|
216
|
+
- lib/typical_situation/serialization.rb
|
|
216
217
|
- lib/typical_situation/version.rb
|
|
217
218
|
homepage: https://github.com/apsislabs/typical_situation
|
|
218
219
|
licenses: []
|