rest_framework 0.1.0 → 0.1.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.
- checksums.yaml +4 -4
- data/README.md +11 -6
- data/lib/rest_framework.rb +1 -0
- data/lib/rest_framework/VERSION_STAMP +1 -1
- data/lib/rest_framework/controller_mixins.rb +4 -0
- data/lib/rest_framework/controller_mixins/base.rb +148 -162
- data/lib/rest_framework/controller_mixins/models.rb +210 -181
- data/lib/rest_framework/filters.rb +53 -50
- data/lib/rest_framework/paginators.rb +76 -65
- data/lib/rest_framework/routers.rb +4 -4
- data/lib/rest_framework/serializers.rb +90 -92
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7cb7ba6d5e7861525dca18f908afa1f398f614af654223ac03de15f7014dafaa
|
4
|
+
data.tar.gz: 7938b5b0225e089f2cd3a168b80763ebcc63dd65655ae4c87b045e923477191a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cb1583fe183387972e1537bc2b90ebe9b224381e46515ffd202f3ad86cb4a545f906264644fff537b5937f8e4f985f39eb3123c6d47e2e8e5c1be31b425aad54
|
7
|
+
data.tar.gz: 3a8b3a6e63a4ca03250d8e6697eda885abd6940f8b580f073ffa0c237a59a535d4997aa4591651c01cda61fd3f021508ed69d298a19512f783a9604b6dbcdd7c
|
data/README.md
CHANGED
@@ -3,15 +3,20 @@
|
|
3
3
|
[](https://badge.fury.io/rb/rest_framework)
|
4
4
|
[](https://travis-ci.org/gregschmit/rails-rest-framework)
|
5
5
|
|
6
|
-
|
6
|
+
A framework for DRY RESTful APIs in Ruby on Rails.
|
7
7
|
|
8
8
|
**The Problem**: Building controllers for APIs usually involves writing a lot of redundant CRUD
|
9
|
-
logic, and routing them can be obnoxious.
|
9
|
+
logic, and routing them can be obnoxious. Building and maintaining features like ordering,
|
10
|
+
filtering, and pagination can be tedious.
|
10
11
|
|
11
|
-
**The Solution**: This
|
12
|
-
|
12
|
+
**The Solution**: This framework implements browsable API responses, CRUD actions for your models,
|
13
|
+
and features like ordering/filtering/pagination, so you can focus on building awesome APIs.
|
13
14
|
|
14
|
-
|
15
|
+
Website/Guide: [https://rails-rest-framework.com](https://rails-rest-framework.com)
|
16
|
+
|
17
|
+
Source: [https://github.com/gregschmit/rails-rest-framework](https://github.com/gregschmit/rails-rest-framework)
|
18
|
+
|
19
|
+
YARD Docs: [https://rubydoc.info/gems/rest_framework](https://rubydoc.info/gems/rest_framework)
|
15
20
|
|
16
21
|
## Installation
|
17
22
|
|
@@ -110,7 +115,7 @@ end
|
|
110
115
|
After you clone the repository, cd'ing into the directory should create a new gemset if you are
|
111
116
|
using RVM. Then run `bundle install` to install the appropriate gems.
|
112
117
|
|
113
|
-
To run the
|
118
|
+
To run the test suite:
|
114
119
|
|
115
120
|
```shell
|
116
121
|
$ rake test
|
data/lib/rest_framework.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
@@ -1,194 +1,180 @@
|
|
1
1
|
require_relative '../serializers'
|
2
2
|
|
3
|
-
module RESTFramework
|
4
|
-
|
5
|
-
# This module provides the common functionality for any controller mixins, a `root` action, and
|
6
|
-
# the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
|
7
|
-
# is defined.
|
8
|
-
module BaseControllerMixin
|
9
|
-
# Default action for API root.
|
10
|
-
def root
|
11
|
-
api_response({message: "This is the root of your awesome API!"})
|
12
|
-
end
|
13
3
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
4
|
+
# This module provides the common functionality for any controller mixins, a `root` action, and
|
5
|
+
# the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
|
6
|
+
# is defined.
|
7
|
+
module RESTFramework::BaseControllerMixin
|
8
|
+
# Default action for API root.
|
9
|
+
def root
|
10
|
+
api_response({message: "This is the root of your awesome API!"})
|
11
|
+
end
|
18
12
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
13
|
+
module ClassMethods
|
14
|
+
# Helper to get the actions that should be skipped.
|
15
|
+
def get_skip_actions(skip_undefined: true)
|
16
|
+
# First, skip explicitly skipped actions.
|
17
|
+
skip = self.skip_actions || []
|
25
18
|
|
26
|
-
|
19
|
+
# Now add methods which don't exist, since we don't want to route those.
|
20
|
+
if skip_undefined
|
21
|
+
[:index, :new, :create, :show, :edit, :update, :destroy].each do |a|
|
22
|
+
skip << a unless self.method_defined?(a)
|
23
|
+
end
|
27
24
|
end
|
28
|
-
end
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
# Add class attributes (with defaults) unless they already exist.
|
35
|
-
{
|
36
|
-
extra_actions: nil,
|
37
|
-
extra_member_actions: nil,
|
38
|
-
filter_backends: nil,
|
39
|
-
paginator_class: nil,
|
40
|
-
page_size: nil,
|
41
|
-
page_query_param: 'page',
|
42
|
-
page_size_query_param: 'page_size',
|
43
|
-
max_page_size: nil,
|
44
|
-
serializer_class: nil,
|
45
|
-
singleton_controller: nil,
|
46
|
-
skip_actions: nil,
|
47
|
-
}.each do |a, default|
|
48
|
-
unless base.respond_to?(a)
|
49
|
-
base.class_attribute(a)
|
50
|
-
|
51
|
-
# Set default manually so we can still support Rails 4. Maybe later we can use the
|
52
|
-
# default parameter on `class_attribute`.
|
53
|
-
base.send(:"#{a}=", default)
|
54
|
-
end
|
55
|
-
end
|
26
|
+
return skip
|
27
|
+
end
|
28
|
+
end
|
56
29
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
30
|
+
def self.included(base)
|
31
|
+
if base.is_a? Class
|
32
|
+
base.extend ClassMethods
|
33
|
+
|
34
|
+
# Add class attributes (with defaults) unless they already exist.
|
35
|
+
{
|
36
|
+
extra_actions: nil,
|
37
|
+
extra_member_actions: nil,
|
38
|
+
filter_backends: nil,
|
39
|
+
paginator_class: nil,
|
40
|
+
page_size: 20,
|
41
|
+
page_query_param: 'page',
|
42
|
+
page_size_query_param: 'page_size',
|
43
|
+
max_page_size: nil,
|
44
|
+
serializer_class: nil,
|
45
|
+
singleton_controller: nil,
|
46
|
+
skip_actions: nil,
|
47
|
+
}.each do |a, default|
|
48
|
+
unless base.respond_to?(a)
|
49
|
+
base.class_attribute(a)
|
50
|
+
|
51
|
+
# Set default manually so we can still support Rails 4. Maybe later we can use the
|
52
|
+
# default parameter on `class_attribute`.
|
53
|
+
base.send(:"#{a}=", default)
|
61
54
|
end
|
55
|
+
end
|
62
56
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
|
68
|
-
base.rescue_from(ActiveRecord::RecordInvalid, with: :record_invalid)
|
69
|
-
base.rescue_from(ActiveRecord::RecordNotSaved, with: :record_not_saved)
|
70
|
-
base.rescue_from(ActiveRecord::RecordNotDestroyed, with: :record_not_destroyed)
|
57
|
+
# Alias `extra_actions` to `extra_collection_actions`.
|
58
|
+
unless base.respond_to?(:extra_collection_actions)
|
59
|
+
base.alias_method(:extra_collection_actions, :extra_actions)
|
60
|
+
base.alias_method(:extra_collection_actions=, :extra_actions=)
|
71
61
|
end
|
72
|
-
end
|
73
62
|
|
74
|
-
|
63
|
+
# skip csrf since this is an API
|
64
|
+
base.skip_before_action(:verify_authenticity_token) rescue nil
|
75
65
|
|
76
|
-
|
77
|
-
|
78
|
-
|
66
|
+
# handle some common exceptions
|
67
|
+
base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
|
68
|
+
base.rescue_from(ActiveRecord::RecordInvalid, with: :record_invalid)
|
69
|
+
base.rescue_from(ActiveRecord::RecordNotSaved, with: :record_not_saved)
|
70
|
+
base.rescue_from(ActiveRecord::RecordNotDestroyed, with: :record_not_destroyed)
|
79
71
|
end
|
72
|
+
end
|
80
73
|
|
81
|
-
|
82
|
-
def get_filtered_data(data)
|
83
|
-
self.get_filter_backends.each do |filter_class|
|
84
|
-
filter = filter_class.new(controller: self)
|
85
|
-
data = filter.get_filtered_data(data)
|
86
|
-
end
|
74
|
+
protected
|
87
75
|
|
88
|
-
|
89
|
-
|
76
|
+
# Helper to get filtering backends with a sane default.
|
77
|
+
# @return [RESTFramework::BaseFilter]
|
78
|
+
def get_filter_backends
|
79
|
+
return self.class.filter_backends || []
|
80
|
+
end
|
90
81
|
|
91
|
-
|
92
|
-
|
93
|
-
|
82
|
+
# Helper to filter an arbitrary data set over all configured filter backends.
|
83
|
+
def get_filtered_data(data)
|
84
|
+
self.get_filter_backends.each do |filter_class|
|
85
|
+
filter = filter_class.new(controller: self)
|
86
|
+
data = filter.get_filtered_data(data)
|
94
87
|
end
|
95
88
|
|
96
|
-
|
97
|
-
|
98
|
-
action_serializer_config = self.class.native_serializer_action_config || {}
|
99
|
-
action = self.action_name.to_sym
|
100
|
-
|
101
|
-
# Handle case where :index action is not defined.
|
102
|
-
if action == :index && !action_serializer_config.key?(:index)
|
103
|
-
# Default is :show if `singleton_controller`, otherwise :list.
|
104
|
-
action = self.class.singleton_controller ? :show : :list
|
105
|
-
end
|
89
|
+
return data
|
90
|
+
end
|
106
91
|
|
107
|
-
|
108
|
-
|
92
|
+
# Helper to get the configured serializer class.
|
93
|
+
# @return [RESTFramework::BaseSerializer]
|
94
|
+
def get_serializer_class
|
95
|
+
return self.class.serializer_class
|
96
|
+
end
|
109
97
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
98
|
+
def record_invalid(e)
|
99
|
+
return api_response(
|
100
|
+
{message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
|
101
|
+
)
|
102
|
+
end
|
115
103
|
|
116
|
-
|
117
|
-
|
118
|
-
|
104
|
+
def record_not_found(e)
|
105
|
+
return api_response({message: "Record not found.", exception: e}, status: 404)
|
106
|
+
end
|
119
107
|
|
120
|
-
|
121
|
-
|
122
|
-
|
108
|
+
def record_not_saved(e)
|
109
|
+
return api_response({message: "Record not saved.", exception: e}, status: 406)
|
110
|
+
end
|
123
111
|
|
124
|
-
|
125
|
-
|
126
|
-
|
112
|
+
def record_not_destroyed(e)
|
113
|
+
return api_response({message: "Record not destroyed.", exception: e}, status: 406)
|
114
|
+
end
|
127
115
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
end
|
135
|
-
return ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes).format(
|
136
|
-
formatter.new
|
137
|
-
).lines.drop(1).map { |r| r.split.last(3) }.map { |r|
|
138
|
-
{verb: r[0], path: r[1], action: r[2]}
|
139
|
-
}.select { |r| r[:path].start_with?(request.path) }
|
116
|
+
# Helper for showing routes under a controller action, used for the browsable API.
|
117
|
+
def _get_routes
|
118
|
+
begin
|
119
|
+
formatter = ActionDispatch::Routing::ConsoleFormatter::Sheet
|
120
|
+
rescue NameError
|
121
|
+
formatter = ActionDispatch::Routing::ConsoleFormatter
|
140
122
|
end
|
123
|
+
return ActionDispatch::Routing::RoutesInspector.new(Rails.application.routes.routes).format(
|
124
|
+
formatter.new
|
125
|
+
).lines.drop(1).map { |r| r.split.last(3) }.map { |r|
|
126
|
+
{verb: r[0], path: r[1], action: r[2]}
|
127
|
+
}.select { |r| r[:path].start_with?(request.path) }
|
128
|
+
end
|
141
129
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
end
|
130
|
+
# Helper to render a browsable API for `html` format, along with basic `json`/`xml` formats, and
|
131
|
+
# with support or passing custom `kwargs` to the underlying `render` calls.
|
132
|
+
def api_response(payload, html_kwargs: nil, json_kwargs: nil, xml_kwargs: nil, **kwargs)
|
133
|
+
html_kwargs ||= {}
|
134
|
+
json_kwargs ||= {}
|
135
|
+
xml_kwargs ||= {}
|
136
|
+
|
137
|
+
# allow blank (no-content) responses
|
138
|
+
@blank = kwargs[:blank]
|
139
|
+
|
140
|
+
respond_to do |format|
|
141
|
+
if @blank
|
142
|
+
format.json {head :no_content}
|
143
|
+
format.xml {head :no_content}
|
144
|
+
else
|
145
|
+
if payload.respond_to?(:to_json)
|
146
|
+
format.json {
|
147
|
+
jkwargs = kwargs.merge(json_kwargs)
|
148
|
+
render(json: payload, layout: false, **jkwargs)
|
149
|
+
}
|
150
|
+
end
|
151
|
+
if payload.respond_to?(:to_xml)
|
152
|
+
format.xml {
|
153
|
+
xkwargs = kwargs.merge(xml_kwargs)
|
154
|
+
render(xml: payload, layout: false, **xkwargs)
|
155
|
+
}
|
169
156
|
end
|
170
|
-
format.html {
|
171
|
-
@payload = payload
|
172
|
-
@json_payload = ''
|
173
|
-
@xml_payload = ''
|
174
|
-
unless @blank
|
175
|
-
@json_payload = payload.to_json if payload.respond_to?(:to_json)
|
176
|
-
@xml_payload = payload.to_xml if payload.respond_to?(:to_xml)
|
177
|
-
end
|
178
|
-
@template_logo_text ||= "Rails REST Framework"
|
179
|
-
@title ||= self.controller_name.camelize
|
180
|
-
@routes ||= self._get_routes
|
181
|
-
kwargs = kwargs.merge(html_kwargs)
|
182
|
-
begin
|
183
|
-
render(**kwargs)
|
184
|
-
rescue ActionView::MissingTemplate # fallback to rest_framework layout/view
|
185
|
-
kwargs[:layout] = "rest_framework"
|
186
|
-
kwargs[:template] = "rest_framework/default"
|
187
|
-
render(**kwargs)
|
188
|
-
end
|
189
|
-
}
|
190
157
|
end
|
158
|
+
format.html {
|
159
|
+
@payload = payload
|
160
|
+
@json_payload = ''
|
161
|
+
@xml_payload = ''
|
162
|
+
unless @blank
|
163
|
+
@json_payload = payload.to_json if payload.respond_to?(:to_json)
|
164
|
+
@xml_payload = payload.to_xml if payload.respond_to?(:to_xml)
|
165
|
+
end
|
166
|
+
@template_logo_text ||= "Rails REST Framework"
|
167
|
+
@title ||= self.controller_name.camelize
|
168
|
+
@routes ||= self._get_routes
|
169
|
+
hkwargs = kwargs.merge(html_kwargs)
|
170
|
+
begin
|
171
|
+
render(**hkwargs)
|
172
|
+
rescue ActionView::MissingTemplate # fallback to rest_framework layout/view
|
173
|
+
hkwargs[:layout] = "rest_framework"
|
174
|
+
hkwargs[:template] = "rest_framework/default"
|
175
|
+
render(**hkwargs)
|
176
|
+
end
|
177
|
+
}
|
191
178
|
end
|
192
179
|
end
|
193
|
-
|
194
180
|
end
|
@@ -1,228 +1,257 @@
|
|
1
1
|
require_relative 'base'
|
2
2
|
require_relative '../filters'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
4
|
+
|
5
|
+
# This module provides the core functionality for controllers based on models.
|
6
|
+
module RESTFramework::BaseModelControllerMixin
|
7
|
+
include RESTFramework::BaseControllerMixin
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
if base.is_a? Class
|
11
|
+
RESTFramework::BaseControllerMixin.included(base)
|
12
|
+
|
13
|
+
# Add class attributes (with defaults) unless they already exist.
|
14
|
+
{
|
15
|
+
# Core attributes related to models.
|
16
|
+
model: nil,
|
17
|
+
recordset: nil,
|
18
|
+
|
19
|
+
# Attributes for create/update parameters.
|
20
|
+
allowed_parameters: nil,
|
21
|
+
allowed_action_parameters: nil,
|
22
|
+
|
23
|
+
# Attributes for configuring record fields.
|
24
|
+
fields: nil,
|
25
|
+
action_fields: nil,
|
26
|
+
|
27
|
+
# Attributes for the default native serializer.
|
28
|
+
native_serializer_config: nil,
|
29
|
+
native_serializer_action_config: nil,
|
30
|
+
|
31
|
+
# Attributes for default model filtering (and ordering).
|
32
|
+
filterset_fields: nil,
|
33
|
+
ordering_fields: nil,
|
34
|
+
ordering_query_param: 'ordering',
|
35
|
+
|
36
|
+
# Other misc attributes.
|
37
|
+
disable_creation_from_recordset: nil, # Option to disable `recordset.create` behavior.
|
38
|
+
}.each do |a, default|
|
39
|
+
unless base.respond_to?(a)
|
40
|
+
base.class_attribute(a)
|
41
|
+
|
42
|
+
# Set default manually so we can still support Rails 4. Maybe later we can use the
|
43
|
+
# default parameter on `class_attribute`.
|
44
|
+
base.send(:"#{a}=", default)
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
+
protected
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
# Get a native serializer config for the current action.
|
53
|
+
# @return [RESTFramework::NativeModelSerializer]
|
54
|
+
def get_native_serializer_config
|
55
|
+
action_serializer_config = self.class.native_serializer_action_config || {}
|
56
|
+
action = self.action_name.to_sym
|
57
|
+
|
58
|
+
# Handle case where :index action is not defined.
|
59
|
+
if action == :index && !action_serializer_config.key?(:index)
|
60
|
+
# Default is :show if `singleton_controller`, otherwise :list.
|
61
|
+
action = self.class.singleton_controller ? :show : :list
|
54
62
|
end
|
55
63
|
|
56
|
-
|
57
|
-
|
58
|
-
allowed_action_parameters = self.class.allowed_action_parameters || {}
|
59
|
-
action = self.action_name.to_sym
|
64
|
+
return (action_serializer_config[action] if action) || self.class.native_serializer_config
|
65
|
+
end
|
60
66
|
|
61
|
-
|
62
|
-
|
67
|
+
# Helper to get the configured serializer class, or `NativeModelSerializer` as a default.
|
68
|
+
# @return [RESTFramework::BaseSerializer]
|
69
|
+
def get_serializer_class
|
70
|
+
return self.class.serializer_class || RESTFramework::NativeModelSerializer
|
71
|
+
end
|
63
72
|
|
64
|
-
|
65
|
-
|
73
|
+
# Get a list of parameters allowed for the current action.
|
74
|
+
def get_allowed_parameters
|
75
|
+
allowed_action_parameters = self.class.allowed_action_parameters || {}
|
76
|
+
action = self.action_name.to_sym
|
66
77
|
|
67
|
-
#
|
68
|
-
|
69
|
-
return self.class.filter_backends || [
|
70
|
-
RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
|
71
|
-
]
|
72
|
-
end
|
78
|
+
# index action should use :list allowed parameters if :index is not provided
|
79
|
+
action = :list if action == :index && !allowed_action_parameters.key?(:index)
|
73
80
|
|
74
|
-
|
75
|
-
|
76
|
-
action_fields = self.class.action_fields || {}
|
77
|
-
action = self.action_name.to_sym
|
81
|
+
return (allowed_action_parameters[action] if action) || self.class.allowed_parameters
|
82
|
+
end
|
78
83
|
|
79
|
-
|
80
|
-
|
84
|
+
# Get the list of filtering backends to use.
|
85
|
+
# @return [RESTFramework::BaseFilter]
|
86
|
+
def get_filter_backends
|
87
|
+
return self.class.filter_backends || [
|
88
|
+
RESTFramework::ModelFilter, RESTFramework::ModelOrderingFilter
|
89
|
+
]
|
90
|
+
end
|
81
91
|
|
82
|
-
|
83
|
-
|
92
|
+
# Get a list of fields for the current action.
|
93
|
+
def get_fields
|
94
|
+
action_fields = self.class.action_fields || {}
|
95
|
+
action = self.action_name.to_sym
|
84
96
|
|
85
|
-
#
|
86
|
-
|
87
|
-
fields = self.get_allowed_parameters || self.get_fields
|
88
|
-
return @get_body_params ||= (request.request_parameters.select { |p|
|
89
|
-
fields.include?(p.to_sym) || fields.include?(p.to_s)
|
90
|
-
})
|
91
|
-
end
|
92
|
-
alias :get_create_params :get_body_params
|
93
|
-
alias :get_update_params :get_body_params
|
94
|
-
|
95
|
-
# Get a record by `id` or return a single record if recordset is filtered down to a single
|
96
|
-
# record.
|
97
|
-
def get_record
|
98
|
-
records = self.get_filtered_data(self.get_recordset)
|
99
|
-
if params['id'] # direct lookup
|
100
|
-
return records.find(params['id'])
|
101
|
-
elsif records.length == 1
|
102
|
-
return records[0]
|
103
|
-
end
|
104
|
-
return nil
|
105
|
-
end
|
97
|
+
# index action should use :list fields if :index is not provided
|
98
|
+
action = :list if action == :index && !action_fields.key?(:index)
|
106
99
|
|
107
|
-
|
108
|
-
|
109
|
-
return @model if instance_variable_defined?(:@model) && @model
|
110
|
-
return self.class.model if self.class.model
|
111
|
-
unless from_internal_get_recordset # prevent infinite recursion
|
112
|
-
recordset = self._get_recordset(from_internal_get_model: true)
|
113
|
-
return (@model = recordset.klass) if recordset
|
114
|
-
end
|
115
|
-
begin
|
116
|
-
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
117
|
-
rescue NameError
|
118
|
-
end
|
119
|
-
return nil
|
120
|
-
end
|
100
|
+
return (action_fields[action] if action) || self.class.fields || []
|
101
|
+
end
|
121
102
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
103
|
+
# Filter the request body for keys in current action's allowed_parameters/fields config.
|
104
|
+
def get_body_params
|
105
|
+
fields = self.get_allowed_parameters || self.get_fields
|
106
|
+
return @get_body_params ||= (request.request_parameters.select { |p|
|
107
|
+
fields.include?(p.to_sym) || fields.include?(p.to_s)
|
108
|
+
})
|
109
|
+
end
|
110
|
+
alias :get_create_params :get_body_params
|
111
|
+
alias :get_update_params :get_body_params
|
112
|
+
|
113
|
+
# Get a record by the primary key from the filtered recordset.
|
114
|
+
def get_record
|
115
|
+
records = self.get_filtered_data(self.get_recordset)
|
116
|
+
if pk = params[self.model.primary_key]
|
117
|
+
return records.find(pk)
|
131
118
|
end
|
119
|
+
return nil
|
120
|
+
end
|
132
121
|
|
133
|
-
|
134
|
-
|
135
|
-
|
122
|
+
# Internal interface for get_model, protecting against infinite recursion with get_recordset.
|
123
|
+
def _get_model(from_internal_get_recordset: false)
|
124
|
+
return @model if instance_variable_defined?(:@model) && @model
|
125
|
+
return self.class.model if self.class.model
|
126
|
+
unless from_internal_get_recordset # prevent infinite recursion
|
127
|
+
recordset = self._get_recordset(from_internal_get_model: true)
|
128
|
+
return (@model = recordset.klass) if recordset
|
136
129
|
end
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
return _get_recordset
|
130
|
+
begin
|
131
|
+
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
132
|
+
rescue NameError
|
141
133
|
end
|
134
|
+
return nil
|
142
135
|
end
|
143
136
|
|
144
|
-
|
145
|
-
|
146
|
-
|
137
|
+
# Internal interface for get_recordset, protecting against infinite recursion with get_model.
|
138
|
+
def _get_recordset(from_internal_get_model: false)
|
139
|
+
return @recordset if instance_variable_defined?(:@recordset) && @recordset
|
140
|
+
return self.class.recordset if self.class.recordset
|
141
|
+
unless from_internal_get_model # prevent infinite recursion
|
142
|
+
model = self._get_model(from_internal_get_recordset: true)
|
143
|
+
return (@recordset = model.all) if model
|
144
|
+
end
|
145
|
+
return nil
|
146
|
+
end
|
147
147
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
serialized_page = self.get_serializer_class.new(object: page, controller: self).serialize
|
153
|
-
data = paginator.get_paginated_response(serialized_page)
|
154
|
-
else
|
155
|
-
data = self.get_serializer_class.new(object: @records, controller: self).serialize
|
156
|
-
end
|
148
|
+
# Get the model for this controller.
|
149
|
+
def get_model
|
150
|
+
return _get_model
|
151
|
+
end
|
157
152
|
|
158
|
-
|
159
|
-
|
153
|
+
# Get the set of records this controller has access to.
|
154
|
+
def get_recordset
|
155
|
+
return _get_recordset
|
160
156
|
end
|
157
|
+
end
|
158
|
+
|
161
159
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
160
|
+
# Mixin for listing records.
|
161
|
+
module RESTFramework::ListModelMixin
|
162
|
+
def index
|
163
|
+
@records = self.get_filtered_data(self.get_recordset)
|
164
|
+
|
165
|
+
# Handle pagination, if enabled.
|
166
|
+
if self.class.paginator_class
|
167
|
+
paginator = self.class.paginator_class.new(data: @records, controller: self)
|
168
|
+
page = paginator.get_page
|
169
|
+
serialized_page = self.get_serializer_class.new(object: page, controller: self).serialize
|
170
|
+
data = paginator.get_paginated_response(serialized_page)
|
171
|
+
else
|
172
|
+
data = self.get_serializer_class.new(object: @records, controller: self).serialize
|
167
173
|
end
|
174
|
+
|
175
|
+
return api_response(data)
|
168
176
|
end
|
177
|
+
end
|
169
178
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
@record = self.get_model.create!(self.get_create_params)
|
178
|
-
end
|
179
|
-
serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
|
180
|
-
return api_response(serialized_record)
|
181
|
-
end
|
179
|
+
|
180
|
+
# Mixin for showing records.
|
181
|
+
module RESTFramework::ShowModelMixin
|
182
|
+
def show
|
183
|
+
@record = self.get_record
|
184
|
+
serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
|
185
|
+
return api_response(serialized_record)
|
182
186
|
end
|
187
|
+
end
|
188
|
+
|
183
189
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
+
# Mixin for creating records.
|
191
|
+
module RESTFramework::CreateModelMixin
|
192
|
+
def create
|
193
|
+
if self.get_recordset.respond_to?(:create!) && !self.disable_creation_from_recordset
|
194
|
+
# Create with any properties inherited from the recordset (like associations).
|
195
|
+
@record = self.get_recordset.create!(self.get_create_params)
|
196
|
+
else
|
197
|
+
# Otherwise, perform a "bare" create.
|
198
|
+
@record = self.get_model.create!(self.get_create_params)
|
190
199
|
end
|
200
|
+
serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
|
201
|
+
return api_response(serialized_record)
|
191
202
|
end
|
203
|
+
end
|
192
204
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
205
|
+
|
206
|
+
# Mixin for updating records.
|
207
|
+
module RESTFramework::UpdateModelMixin
|
208
|
+
def update
|
209
|
+
@record = self.get_record
|
210
|
+
@record.update!(self.get_update_params)
|
211
|
+
serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
|
212
|
+
return api_response(serialized_record)
|
199
213
|
end
|
214
|
+
end
|
200
215
|
|
201
|
-
module ReadOnlyModelControllerMixin
|
202
|
-
include BaseModelControllerMixin
|
203
|
-
def self.included(base)
|
204
|
-
if base.is_a? Class
|
205
|
-
BaseModelControllerMixin.included(base)
|
206
|
-
end
|
207
|
-
end
|
208
216
|
|
209
|
-
|
210
|
-
|
217
|
+
# Mixin for destroying records.
|
218
|
+
module RESTFramework::DestroyModelMixin
|
219
|
+
def destroy
|
220
|
+
@record = self.get_record
|
221
|
+
@record.destroy!
|
222
|
+
api_response(nil)
|
211
223
|
end
|
224
|
+
end
|
212
225
|
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
226
|
+
|
227
|
+
# Mixin that includes show/list mixins.
|
228
|
+
module RESTFramework::ReadOnlyModelControllerMixin
|
229
|
+
include RESTFramework::BaseModelControllerMixin
|
230
|
+
|
231
|
+
def self.included(base)
|
232
|
+
if base.is_a? Class
|
233
|
+
RESTFramework::BaseModelControllerMixin.included(base)
|
219
234
|
end
|
235
|
+
end
|
236
|
+
|
237
|
+
include RESTFramework::ListModelMixin
|
238
|
+
include RESTFramework::ShowModelMixin
|
239
|
+
end
|
220
240
|
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
241
|
+
|
242
|
+
# Mixin that includes all the CRUD mixins.
|
243
|
+
module RESTFramework::ModelControllerMixin
|
244
|
+
include RESTFramework::BaseModelControllerMixin
|
245
|
+
|
246
|
+
def self.included(base)
|
247
|
+
if base.is_a? Class
|
248
|
+
RESTFramework::BaseModelControllerMixin.included(base)
|
249
|
+
end
|
226
250
|
end
|
227
251
|
|
252
|
+
include RESTFramework::ListModelMixin
|
253
|
+
include RESTFramework::ShowModelMixin
|
254
|
+
include RESTFramework::CreateModelMixin
|
255
|
+
include RESTFramework::UpdateModelMixin
|
256
|
+
include RESTFramework::DestroyModelMixin
|
228
257
|
end
|