rest_framework 0.0.13 → 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 +14 -21
- data/app/views/layouts/rest_framework.html.erb +2 -2
- data/lib/rest_framework.rb +4 -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 +158 -107
- data/lib/rest_framework/controller_mixins/models.rb +211 -176
- data/lib/rest_framework/filters.rb +68 -0
- data/lib/rest_framework/paginators.rb +95 -0
- data/lib/rest_framework/routers.rb +87 -44
- data/lib/rest_framework/serializers.rb +89 -83
- metadata +7 -5
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,24 +115,12 @@ 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
|
|
117
122
|
```
|
|
118
123
|
|
|
119
|
-
To
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
$ rake test:unit
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
To run integration tests:
|
|
126
|
-
|
|
127
|
-
```shell
|
|
128
|
-
$ rake test:integration
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
To interact with the integration app, you can `cd test/integration` and operate it via the normal
|
|
132
|
-
Rails interfaces. Ensure you run `rake db:schema:load` before running `rails server` or
|
|
133
|
-
`rails console`.
|
|
124
|
+
To interact with the test app, `cd test` and operate it via the normal Rails interfaces. Ensure you
|
|
125
|
+
run `rake db:schema:load` before running `rails server` or `rails console`. You can also load the
|
|
126
|
+
test fixtures with `rake db:fixtures:load`.
|
|
@@ -30,14 +30,14 @@
|
|
|
30
30
|
<% if @json_payload %>
|
|
31
31
|
<li class="nav-item">
|
|
32
32
|
<a class="nav-link active" href="#tab-json" data-toggle="tab" role="tab">
|
|
33
|
-
|
|
33
|
+
.json
|
|
34
34
|
</a>
|
|
35
35
|
</li>
|
|
36
36
|
<% end %>
|
|
37
37
|
<% if @xml_payload %>
|
|
38
38
|
<li class="nav-item">
|
|
39
39
|
<a class="nav-link" href="#tab-xml" data-toggle="tab" role="tab">
|
|
40
|
-
|
|
40
|
+
.xml
|
|
41
41
|
</a>
|
|
42
42
|
</li>
|
|
43
43
|
<% end %>
|
data/lib/rest_framework.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
module RESTFramework
|
|
2
2
|
end
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
require_relative "rest_framework/controller_mixins"
|
|
5
6
|
require_relative "rest_framework/engine"
|
|
7
|
+
require_relative "rest_framework/filters"
|
|
8
|
+
require_relative "rest_framework/paginators"
|
|
6
9
|
require_relative "rest_framework/routers"
|
|
10
|
+
require_relative "rest_framework/serializers"
|
|
7
11
|
require_relative "rest_framework/version"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.1.1
|
|
@@ -1,129 +1,180 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
# This module provides the common functionality for any controller mixins, a `root` action, and
|
|
4
|
-
# the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
|
|
5
|
-
# is defined.
|
|
6
|
-
module BaseControllerMixin
|
|
7
|
-
# Default action for API root.
|
|
8
|
-
def root
|
|
9
|
-
api_response({message: "This is the root of your awesome API!"})
|
|
10
|
-
end
|
|
1
|
+
require_relative '../serializers'
|
|
11
2
|
|
|
12
|
-
module ClassMethods
|
|
13
|
-
def get_skip_actions(skip_undefined: true)
|
|
14
|
-
# first, skip explicitly skipped actions
|
|
15
|
-
skip = self.skip_actions || []
|
|
16
3
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
23
12
|
|
|
24
|
-
|
|
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 || []
|
|
18
|
+
|
|
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
|
|
25
24
|
end
|
|
25
|
+
|
|
26
|
+
return skip
|
|
26
27
|
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.included(base)
|
|
31
|
+
if base.is_a? Class
|
|
32
|
+
base.extend ClassMethods
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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)
|
|
54
|
+
end
|
|
41
55
|
end
|
|
42
|
-
end
|
|
43
56
|
|
|
44
|
-
|
|
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=)
|
|
61
|
+
end
|
|
45
62
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
|
|
49
|
-
)
|
|
50
|
-
end
|
|
63
|
+
# skip csrf since this is an API
|
|
64
|
+
base.skip_before_action(:verify_authenticity_token) rescue nil
|
|
51
65
|
|
|
52
|
-
|
|
53
|
-
|
|
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)
|
|
54
71
|
end
|
|
72
|
+
end
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
return api_response({message: "Record not saved.", exception: e}, status: 406)
|
|
58
|
-
end
|
|
74
|
+
protected
|
|
59
75
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
81
|
+
|
|
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)
|
|
62
87
|
end
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
return data
|
|
90
|
+
end
|
|
91
|
+
|
|
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
|
|
97
|
+
|
|
98
|
+
def record_invalid(e)
|
|
99
|
+
return api_response(
|
|
100
|
+
{message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
|
|
101
|
+
)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def record_not_found(e)
|
|
105
|
+
return api_response({message: "Record not found.", exception: e}, status: 404)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def record_not_saved(e)
|
|
109
|
+
return api_response({message: "Record not saved.", exception: e}, status: 406)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def record_not_destroyed(e)
|
|
113
|
+
return api_response({message: "Record not destroyed.", exception: e}, status: 406)
|
|
114
|
+
end
|
|
115
|
+
|
|
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
|
|
75
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
|
|
129
|
+
|
|
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]
|
|
76
139
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if
|
|
89
|
-
format.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
format.json {
|
|
94
|
-
kwargs = kwargs.merge(json_kwargs)
|
|
95
|
-
render(json: payload, layout: false, **kwargs)
|
|
96
|
-
}
|
|
97
|
-
end
|
|
98
|
-
if payload.respond_to?(:to_xml)
|
|
99
|
-
format.xml {
|
|
100
|
-
kwargs = kwargs.merge(xml_kwargs)
|
|
101
|
-
render(xml: payload, layout: false, **kwargs)
|
|
102
|
-
}
|
|
103
|
-
end
|
|
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
|
+
}
|
|
104
156
|
end
|
|
105
|
-
format.html {
|
|
106
|
-
@payload = payload
|
|
107
|
-
@json_payload = ''
|
|
108
|
-
@xml_payload = ''
|
|
109
|
-
unless @blank
|
|
110
|
-
@json_payload = payload.to_json if payload.respond_to?(:to_json)
|
|
111
|
-
@xml_payload = payload.to_xml if payload.respond_to?(:to_xml)
|
|
112
|
-
end
|
|
113
|
-
@template_logo_text ||= "Rails REST Framework"
|
|
114
|
-
@title ||= self.controller_name.camelize
|
|
115
|
-
@routes ||= self._get_routes
|
|
116
|
-
kwargs = kwargs.merge(html_kwargs)
|
|
117
|
-
begin
|
|
118
|
-
render(**kwargs)
|
|
119
|
-
rescue ActionView::MissingTemplate # fallback to rest_framework layout/view
|
|
120
|
-
kwargs[:layout] = "rest_framework"
|
|
121
|
-
kwargs[:template] = "rest_framework/default"
|
|
122
|
-
end
|
|
123
|
-
render(**kwargs)
|
|
124
|
-
}
|
|
125
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
|
+
}
|
|
126
178
|
end
|
|
127
179
|
end
|
|
128
|
-
|
|
129
180
|
end
|
|
@@ -1,222 +1,257 @@
|
|
|
1
1
|
require_relative 'base'
|
|
2
|
-
require_relative '../
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
2
|
+
require_relative '../filters'
|
|
3
|
+
|
|
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
|
+
end
|
|
25
46
|
end
|
|
26
47
|
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
protected
|
|
27
51
|
|
|
28
|
-
|
|
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
|
|
29
57
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
|
32
62
|
end
|
|
33
63
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
action_fields = self.class.action_fields || {}
|
|
37
|
-
action = self.action_name.to_sym
|
|
64
|
+
return (action_serializer_config[action] if action) || self.class.native_serializer_config
|
|
65
|
+
end
|
|
38
66
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
41
72
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
44
77
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
action_serializer_config = self.class.native_serializer_action_config || {}
|
|
48
|
-
action = self.action_name.to_sym
|
|
78
|
+
# index action should use :list allowed parameters if :index is not provided
|
|
79
|
+
action = :list if action == :index && !allowed_action_parameters.key?(:index)
|
|
49
80
|
|
|
50
|
-
|
|
51
|
-
|
|
81
|
+
return (allowed_action_parameters[action] if action) || self.class.allowed_parameters
|
|
82
|
+
end
|
|
52
83
|
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
55
91
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
# index action should use :list fields if :index is not provided
|
|
98
|
+
action = :list if action == :index && !action_fields.key?(:index)
|
|
63
99
|
|
|
64
|
-
|
|
65
|
-
|
|
100
|
+
return (action_fields[action] if action) || self.class.fields || []
|
|
101
|
+
end
|
|
66
102
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# Filter params for keys allowed by the current action's filterset_fields/fields config.
|
|
78
|
-
def _get_filterset_values_from_params
|
|
79
|
-
fields = self.filterset_fields || self.get_fields
|
|
80
|
-
return @_get_filterset_values_from_params ||= request.query_parameters.select { |p|
|
|
81
|
-
fields.include?(p.to_sym) || fields.include?(p.to_s)
|
|
82
|
-
}
|
|
83
|
-
end
|
|
84
|
-
alias :get_lookup_params :_get_filterset_values_from_params
|
|
85
|
-
alias :get_filter_params :_get_filterset_values_from_params
|
|
86
|
-
|
|
87
|
-
# Get the recordset, filtered by the filter params.
|
|
88
|
-
def get_filtered_recordset
|
|
89
|
-
filter_params = self.get_filter_params
|
|
90
|
-
unless filter_params.blank?
|
|
91
|
-
return self.get_recordset.where(**self.get_filter_params.to_hash.symbolize_keys)
|
|
92
|
-
end
|
|
93
|
-
return self.get_recordset
|
|
94
|
-
end
|
|
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
|
|
95
112
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return records.find(params['id'])
|
|
102
|
-
elsif records.length == 1
|
|
103
|
-
return records[0]
|
|
104
|
-
end
|
|
105
|
-
return nil
|
|
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)
|
|
106
118
|
end
|
|
119
|
+
return nil
|
|
120
|
+
end
|
|
107
121
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
end
|
|
116
|
-
begin
|
|
117
|
-
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
|
118
|
-
rescue NameError
|
|
119
|
-
end
|
|
120
|
-
return nil
|
|
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
|
|
121
129
|
end
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return @recordset if instance_variable_defined?(:@recordset) && @recordset
|
|
126
|
-
return self.class.recordset if self.class.recordset
|
|
127
|
-
unless from_internal_get_model # prevent infinite recursion
|
|
128
|
-
model = self._get_model(from_internal_get_recordset: true)
|
|
129
|
-
return (@recordset = model.all) if model
|
|
130
|
-
end
|
|
131
|
-
return nil
|
|
130
|
+
begin
|
|
131
|
+
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
|
132
|
+
rescue NameError
|
|
132
133
|
end
|
|
134
|
+
return nil
|
|
135
|
+
end
|
|
133
136
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
144
|
end
|
|
145
|
+
return nil
|
|
146
|
+
end
|
|
138
147
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
end
|
|
148
|
+
# Get the model for this controller.
|
|
149
|
+
def get_model
|
|
150
|
+
return _get_model
|
|
143
151
|
end
|
|
144
152
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
@records = self.get_filtered_recordset
|
|
149
|
-
@serialized_records = self.get_serializer_class.new(
|
|
150
|
-
object: @records, controller: self
|
|
151
|
-
).serialize
|
|
152
|
-
return api_response(@serialized_records)
|
|
153
|
-
end
|
|
153
|
+
# Get the set of records this controller has access to.
|
|
154
|
+
def get_recordset
|
|
155
|
+
return _get_recordset
|
|
154
156
|
end
|
|
157
|
+
end
|
|
158
|
+
|
|
155
159
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
163
173
|
end
|
|
174
|
+
|
|
175
|
+
return api_response(data)
|
|
164
176
|
end
|
|
177
|
+
end
|
|
165
178
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
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)
|
|
174
186
|
end
|
|
187
|
+
end
|
|
188
|
+
|
|
175
189
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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)
|
|
184
199
|
end
|
|
200
|
+
serialized_record = self.get_serializer_class.new(object: @record, controller: self).serialize
|
|
201
|
+
return api_response(serialized_record)
|
|
185
202
|
end
|
|
203
|
+
end
|
|
186
204
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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)
|
|
193
213
|
end
|
|
214
|
+
end
|
|
194
215
|
|
|
195
|
-
module ReadOnlyModelControllerMixin
|
|
196
|
-
include BaseModelControllerMixin
|
|
197
|
-
def self.included(base)
|
|
198
|
-
if base.is_a? Class
|
|
199
|
-
BaseModelControllerMixin.included(base)
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
216
|
|
|
203
|
-
|
|
204
|
-
|
|
217
|
+
# Mixin for destroying records.
|
|
218
|
+
module RESTFramework::DestroyModelMixin
|
|
219
|
+
def destroy
|
|
220
|
+
@record = self.get_record
|
|
221
|
+
@record.destroy!
|
|
222
|
+
api_response(nil)
|
|
205
223
|
end
|
|
224
|
+
end
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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)
|
|
213
234
|
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
include RESTFramework::ListModelMixin
|
|
238
|
+
include RESTFramework::ShowModelMixin
|
|
239
|
+
end
|
|
214
240
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
220
250
|
end
|
|
221
251
|
|
|
252
|
+
include RESTFramework::ListModelMixin
|
|
253
|
+
include RESTFramework::ShowModelMixin
|
|
254
|
+
include RESTFramework::CreateModelMixin
|
|
255
|
+
include RESTFramework::UpdateModelMixin
|
|
256
|
+
include RESTFramework::DestroyModelMixin
|
|
222
257
|
end
|