rest_framework 0.0.2 → 0.0.8
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 +34 -22
- data/app/views/layouts/rest_framework.html.erb +38 -16
- data/app/views/rest_framework/_head.html.erb +24 -0
- data/lib/rest_framework.rb +1 -1
- data/lib/rest_framework/VERSION_STAMP +1 -1
- data/lib/rest_framework/controller_mixins.rb +2 -0
- data/lib/rest_framework/{controllers → controller_mixins}/base.rb +47 -58
- data/lib/rest_framework/controller_mixins/models.rb +217 -0
- data/lib/rest_framework/routers.rb +25 -5
- data/lib/rest_framework/serializers.rb +40 -0
- data/lib/rest_framework/version.rb +3 -3
- metadata +7 -8
- data/app/assets/images/rest_framework_favicon.ico +0 -0
- data/app/assets/javascripts/rest_framework.js +0 -0
- data/app/assets/stylesheets/rest_framework.css +0 -3
- data/lib/rest_framework/controllers.rb +0 -2
- data/lib/rest_framework/controllers/models.rb +0 -225
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f0a9ae17435876f34d868d88ea187ae8844593a498f6d2d560c3ab2cfe1fa37a
|
4
|
+
data.tar.gz: ea04580ad618ed1e15f28cb29ed079e64beead85d77479a52896c10aed226bc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3b596fb77e6bf656ce0fd7613ed2498019670b8576b022f6bda349d4f945d1502a2704bffb3d82a8cf2910ee0779f8fbcf2abae69bbd90e7734d59af1f2a8323
|
7
|
+
data.tar.gz: 9c11d18a0805d17adb55b15d2e5b75e563c7e065bf0734e330c2584991ba0301a542466d9ee5d87387ecd97848451656c0740fa5488d77cd3f187b1f8988aacb
|
data/README.md
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
# REST Framework
|
1
|
+
# Rails REST Framework
|
2
2
|
|
3
3
|
[](https://badge.fury.io/rb/rest_framework)
|
4
4
|
[](https://travis-ci.org/gregschmit/rails-rest-framework)
|
5
5
|
|
6
|
-
REST Framework helps you build awesome APIs in Ruby on Rails.
|
6
|
+
Rails REST Framework helps you build awesome Web APIs in Ruby on Rails.
|
7
7
|
|
8
8
|
**The Problem**: Building controllers for APIs usually involves writing a lot of redundant CRUD
|
9
9
|
logic, and routing them can be obnoxious.
|
@@ -11,6 +11,8 @@ logic, and routing them can be obnoxious.
|
|
11
11
|
**The Solution**: This gem handles the common logic so you can focus on the parts of your API which
|
12
12
|
make it unique.
|
13
13
|
|
14
|
+
To see detailed documentation, visit https://rails-rest-framework.com.
|
15
|
+
|
14
16
|
## Installation
|
15
17
|
|
16
18
|
Add this line to your application's Gemfile:
|
@@ -21,13 +23,17 @@ gem 'rest_framework'
|
|
21
23
|
|
22
24
|
And then execute:
|
23
25
|
|
24
|
-
|
26
|
+
```shell
|
27
|
+
$ bundle install
|
28
|
+
```
|
25
29
|
|
26
30
|
Or install it yourself with:
|
27
31
|
|
28
|
-
|
32
|
+
```shell
|
33
|
+
$ gem install rest_framework
|
34
|
+
```
|
29
35
|
|
30
|
-
## Usage
|
36
|
+
## Quick Usage Tutorial
|
31
37
|
|
32
38
|
### Controller Mixins
|
33
39
|
|
@@ -35,10 +41,10 @@ To transform a controller into a RESTful controller, you can either include `Bas
|
|
35
41
|
`ReadOnlyModelControllerMixin`, or `ModelControllerMixin`. `BaseControllerMixin` provides a `root`
|
36
42
|
action and a simple interface for routing arbitrary additional actions:
|
37
43
|
|
38
|
-
```
|
44
|
+
```ruby
|
39
45
|
class ApiController < ApplicationController
|
40
46
|
include RESTFramework::BaseControllerMixin
|
41
|
-
|
47
|
+
self.extra_actions = {test: [:get]}
|
42
48
|
|
43
49
|
def test
|
44
50
|
render inline: "Test successful!"
|
@@ -46,24 +52,24 @@ class ApiController < ApplicationController
|
|
46
52
|
end
|
47
53
|
```
|
48
54
|
|
49
|
-
`ModelControllerMixin` assists with providing the standard CRUD for your controller.
|
55
|
+
`ModelControllerMixin` assists with providing the standard model CRUD for your controller.
|
50
56
|
|
51
|
-
```
|
57
|
+
```ruby
|
52
58
|
class Api::MoviesController < ApiController
|
53
59
|
include RESTFramework::ModelControllerMixin
|
54
60
|
|
55
|
-
|
61
|
+
self.recordset = Movie.where(enabled: true)
|
56
62
|
end
|
57
63
|
```
|
58
64
|
|
59
65
|
`ReadOnlyModelControllerMixin` only enables list/show actions, but since we're naming this
|
60
66
|
controller in a way that doesn't make the model obvious, we can set that explicitly:
|
61
67
|
|
62
|
-
```
|
68
|
+
```ruby
|
63
69
|
class Api::ReadOnlyMoviesController < ApiController
|
64
70
|
include RESTFramework::ReadOnlyModelControllerMixin
|
65
71
|
|
66
|
-
|
72
|
+
self.model = Movie
|
67
73
|
end
|
68
74
|
```
|
69
75
|
|
@@ -73,28 +79,28 @@ behavior dynamically per-request.
|
|
73
79
|
### Routing
|
74
80
|
|
75
81
|
You can use Rails' `resource`/`resources` routers to route your API, however if you want
|
76
|
-
|
77
|
-
`rest_resource` / `rest_resources` routers
|
78
|
-
the root of your API:
|
82
|
+
`extra_actions` / `extra_member_actions` to be routed automatically, then you can use `rest_route`
|
83
|
+
for non-resourceful controllers, or `rest_resource` / `rest_resources` resourceful routers. You can
|
84
|
+
also use `rest_root` to route the root of your API:
|
79
85
|
|
80
|
-
```
|
86
|
+
```ruby
|
81
87
|
Rails.application.routes.draw do
|
82
88
|
rest_root :api # will find `api_controller` and route the `root` action to '/api'
|
83
89
|
namespace :api do
|
84
90
|
rest_resources :movies
|
85
|
-
rest_resources :
|
91
|
+
rest_resources :users
|
86
92
|
end
|
87
93
|
end
|
88
94
|
```
|
89
95
|
|
90
96
|
Or if you want the API root to be routed to `Api::RootController#root`:
|
91
97
|
|
92
|
-
```
|
98
|
+
```ruby
|
93
99
|
Rails.application.routes.draw do
|
94
100
|
namespace :api do
|
95
101
|
rest_root # will route `Api::RootController#root` to '/' in this namespace ('/api')
|
96
102
|
rest_resources :movies
|
97
|
-
rest_resources :
|
103
|
+
rest_resources :users
|
98
104
|
end
|
99
105
|
end
|
100
106
|
```
|
@@ -106,15 +112,21 @@ using RVM. Then run `bundle install` to install the appropriate gems.
|
|
106
112
|
|
107
113
|
To run the full test suite:
|
108
114
|
|
109
|
-
|
115
|
+
```shell
|
116
|
+
$ rake test
|
117
|
+
```
|
110
118
|
|
111
119
|
To run unit tests:
|
112
120
|
|
113
|
-
|
121
|
+
```shell
|
122
|
+
$ rake test:unit
|
123
|
+
```
|
114
124
|
|
115
125
|
To run integration tests:
|
116
126
|
|
117
|
-
|
127
|
+
```shell
|
128
|
+
$ rake test:integration
|
129
|
+
```
|
118
130
|
|
119
131
|
To interact with the integration app, you can `cd test/integration` and operate it via the normal
|
120
132
|
Rails interfaces. Ensure you run `rake db:schema:load` before running `rails server` or
|
@@ -2,17 +2,7 @@
|
|
2
2
|
<html>
|
3
3
|
<head>
|
4
4
|
<title><%= @title %></title>
|
5
|
-
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
7
|
-
<%= csrf_meta_tags %>
|
8
|
-
<%= csp_meta_tag %>
|
9
|
-
|
10
|
-
<%= favicon_link_tag 'rest_framework_favicon.ico' %>
|
11
|
-
|
12
|
-
<%= stylesheet_link_tag 'application', media: 'all' %>
|
13
|
-
<%= javascript_include_tag 'application' %>
|
14
|
-
<%= stylesheet_link_tag 'rest_framework', media: 'all' %>
|
15
|
-
<%= javascript_include_tag 'rest_framework' %>
|
5
|
+
<%= render partial: 'rest_framework/head' %>
|
16
6
|
</head>
|
17
7
|
|
18
8
|
<body>
|
@@ -21,7 +11,7 @@
|
|
21
11
|
<nav class="navbar navbar-dark bg-dark">
|
22
12
|
<div class="container">
|
23
13
|
<span class="navbar-brand p-0">
|
24
|
-
<h1 class="text-light font-weight-light m-0 p-0" style="font-size: 1em"><%= @template_logo_text %></h1>
|
14
|
+
<h1 class="text-light font-weight-light m-0 p-0" style="font-size: 1em"><%= @template_logo_text || 'Rails REST Framework' %></h1>
|
25
15
|
</span>
|
26
16
|
</div>
|
27
17
|
</nav>
|
@@ -31,10 +21,42 @@
|
|
31
21
|
<div class="row">
|
32
22
|
<h1><%= @title %></h1>
|
33
23
|
</div>
|
34
|
-
<
|
35
|
-
|
36
|
-
<div
|
37
|
-
|
24
|
+
<hr/>
|
25
|
+
<% if @json_payload || @xml_payload %>
|
26
|
+
<div class="row">
|
27
|
+
<h2>Payload</h2>
|
28
|
+
<div class="w-100">
|
29
|
+
<ul class="nav nav-tabs">
|
30
|
+
<% if @json_payload %>
|
31
|
+
<li class="nav-item">
|
32
|
+
<a class="nav-link active" href="#tab-json" data-toggle="tab" role="tab">
|
33
|
+
JSON
|
34
|
+
</a>
|
35
|
+
</li>
|
36
|
+
<% end %>
|
37
|
+
<% if @xml_payload %>
|
38
|
+
<li class="nav-item">
|
39
|
+
<a class="nav-link" href="#tab-xml" data-toggle="tab" role="tab">
|
40
|
+
XML
|
41
|
+
</a>
|
42
|
+
</li>
|
43
|
+
<% end %>
|
44
|
+
</ul>
|
45
|
+
</div>
|
46
|
+
<div class="tab-content w-100 pt-3">
|
47
|
+
<div class="tab-pane fade show active" id="tab-json" role="tab">
|
48
|
+
<% if @json_payload %>
|
49
|
+
<div><pre><code class="language-json"><%= JSON.pretty_generate(JSON.parse(@json_payload)) %></code></pre></div>
|
50
|
+
<% end %>
|
51
|
+
</div>
|
52
|
+
<div class="tab-pane fade" id="tab-xml" role="tab">
|
53
|
+
<% if @xml_payload %>
|
54
|
+
<div><pre><code class="language-xml"><%= @xml_payload %></code></pre></div>
|
55
|
+
<% end %>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
<% end %>
|
38
60
|
<% unless @routes.blank? %>
|
39
61
|
<div class="row">
|
40
62
|
<h2>Routes</h2>
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<meta charset="utf-8">
|
2
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
3
|
+
<%= csrf_meta_tags %>
|
4
|
+
<%= csp_meta_tag rescue nil %>
|
5
|
+
|
6
|
+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
|
7
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/styles/vs.min.css" integrity="sha512-aWjgJTbdG4imzxTxistV5TVNffcYGtIQQm2NBNahV6LmX14Xq9WwZTL1wPjaSglUuVzYgwrq+0EuI4+vKvQHHw==" crossorigin="anonymous" />
|
8
|
+
<style>
|
9
|
+
h1,h2,h3,h4,h5,h6 { width: 100%; font-weight: normal; }
|
10
|
+
h1 { font-size: 2rem; }
|
11
|
+
h2 { font-size: 1.7rem; }
|
12
|
+
h3 { font-size: 1.5rem; }
|
13
|
+
h4 { font-size: 1.3rem; }
|
14
|
+
h5 { font-size: 1.1rem; }
|
15
|
+
h6 { font-size: 1rem; }
|
16
|
+
</style>
|
17
|
+
|
18
|
+
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
|
20
|
+
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
|
21
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/highlight.min.js" integrity="sha512-TDKKr+IvoqZnPzc3l35hdjpHD0m+b2EC2SrLEgKDRWpxf2rFCxemkgvJ5kfU48ip+Y+m2XVKyOCD85ybtlZDmw==" crossorigin="anonymous"></script>
|
22
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/languages/json.min.js" integrity="sha512-FoN8JE+WWCdIGXAIT8KQXwpiavz0Mvjtfk7Rku3MDUNO0BDCiRMXAsSX+e+COFyZTcDb9HDgP+pM2RX12d4j+A==" crossorigin="anonymous"></script>
|
23
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.2.0/languages/xml.min.js" integrity="sha512-dICltIgnUP+QSJrnYGCV8943p3qSDgvcg2NU4W8IcOZP4tdrvxlXjbhIznhtVQEcXow0mOjLM0Q6/NvZsmUH4g==" crossorigin="anonymous"></script>
|
24
|
+
<script>hljs.initHighlightingOnLoad();</script>
|
data/lib/rest_framework.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.8
|
@@ -1,63 +1,18 @@
|
|
1
1
|
module RESTFramework
|
2
2
|
|
3
|
-
# This module provides helpers for mixin `ClassMethods` submodules.
|
4
|
-
module ClassMethodHelpers
|
5
|
-
|
6
|
-
# This helper assists in providing reader interfaces for mixin properties.
|
7
|
-
def _restframework_attr_reader(property, default: nil)
|
8
|
-
method = <<~RUBY
|
9
|
-
def #{property}
|
10
|
-
return _restframework_try_class_level_variable_get(
|
11
|
-
#{property.inspect},
|
12
|
-
default: #{default.inspect},
|
13
|
-
)
|
14
|
-
end
|
15
|
-
RUBY
|
16
|
-
self.module_eval(method)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
3
|
# This module provides the common functionality for any controller mixins, a `root` action, and
|
21
|
-
# the ability to route arbitrary actions with
|
4
|
+
# the ability to route arbitrary actions with `extra_actions`. This is also where `api_response`
|
22
5
|
# is defined.
|
23
6
|
module BaseControllerMixin
|
24
7
|
# Default action for API root.
|
25
|
-
# TODO: use api_response and show sub-routes.
|
26
8
|
def root
|
27
|
-
|
9
|
+
api_response({message: "This is the root of your awesome API!"})
|
28
10
|
end
|
29
11
|
|
30
|
-
protected
|
31
|
-
|
32
12
|
module ClassMethods
|
33
|
-
|
34
|
-
|
35
|
-
# Interface for getting class-level instance/class variables.
|
36
|
-
private def _restframework_try_class_level_variable_get(name, default: nil)
|
37
|
-
begin
|
38
|
-
v = instance_variable_get("@#{name}")
|
39
|
-
return v unless v.nil?
|
40
|
-
rescue NameError
|
41
|
-
end
|
42
|
-
begin
|
43
|
-
v = class_variable_get("@@#{name}")
|
44
|
-
return v unless v.nil?
|
45
|
-
rescue NameError
|
46
|
-
end
|
47
|
-
return default
|
48
|
-
end
|
49
|
-
|
50
|
-
# Interface for registering exceptions handlers.
|
51
|
-
# private def _restframework_register_exception_handlers
|
52
|
-
# rescue_from
|
53
|
-
# end
|
54
|
-
|
55
|
-
_restframework_attr_reader(:extra_actions, default: {})
|
56
|
-
_restframework_attr_reader(:template_logo_text, default: 'Rails REST Framework')
|
57
|
-
|
58
|
-
def skip_actions(skip_undefined: true)
|
13
|
+
def get_skip_actions(skip_undefined: true)
|
59
14
|
# first, skip explicitly skipped actions
|
60
|
-
skip =
|
15
|
+
skip = self.skip_actions || []
|
61
16
|
|
62
17
|
# now add methods which don't exist, since we don't want to route those
|
63
18
|
if skip_undefined
|
@@ -71,7 +26,29 @@ module RESTFramework
|
|
71
26
|
end
|
72
27
|
|
73
28
|
def self.included(base)
|
74
|
-
base.
|
29
|
+
if base.is_a? Class
|
30
|
+
base.extend ClassMethods
|
31
|
+
base.class_attribute(*[
|
32
|
+
:singleton_controller,
|
33
|
+
:extra_actions,
|
34
|
+
:skip_actions,
|
35
|
+
:paginator_class,
|
36
|
+
])
|
37
|
+
base.rescue_from(ActiveRecord::RecordNotFound, with: :record_not_found)
|
38
|
+
base.rescue_from(ActiveRecord::RecordInvalid, with: :record_invalid)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def record_invalid(e)
|
45
|
+
return api_response(
|
46
|
+
{message: "Record invalid.", exception: e, errors: e.record.errors}, status: 400
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def record_not_found(e)
|
51
|
+
return api_response({message: "Record not found.", exception: e}, status: 404)
|
75
52
|
end
|
76
53
|
|
77
54
|
def _get_routes
|
@@ -87,18 +64,34 @@ module RESTFramework
|
|
87
64
|
}.select { |r| r[:path].start_with?(request.path) }
|
88
65
|
end
|
89
66
|
|
90
|
-
# Helper alias for `respond_to`/`render`, and replace nil responses with blank ones.
|
91
|
-
|
67
|
+
# Helper alias for `respond_to`/`render`, and replace nil responses with blank ones. `payload`
|
68
|
+
# must be already serialized to Ruby primitives.
|
69
|
+
def api_response(payload, html_kwargs: nil, json_kwargs: nil, xml_kwargs: nil, **kwargs)
|
92
70
|
html_kwargs ||= {}
|
93
71
|
json_kwargs ||= {}
|
72
|
+
xml_kwargs ||= {}
|
94
73
|
|
95
74
|
respond_to do |format|
|
75
|
+
if payload.respond_to?(:to_json)
|
76
|
+
format.json {
|
77
|
+
kwargs = kwargs.merge(json_kwargs)
|
78
|
+
render(json: payload || '', **kwargs)
|
79
|
+
}
|
80
|
+
end
|
81
|
+
if payload.respond_to?(:to_xml)
|
82
|
+
format.xml {
|
83
|
+
kwargs = kwargs.merge(xml_kwargs)
|
84
|
+
render(xml: payload || '', **kwargs)
|
85
|
+
}
|
86
|
+
end
|
96
87
|
format.html {
|
97
|
-
|
88
|
+
@payload = payload
|
89
|
+
@json_payload = payload.to_json
|
90
|
+
@xml_payload = payload.to_xml
|
98
91
|
@template_logo_text ||= "Rails REST Framework"
|
99
92
|
@title ||= self.controller_name.camelize
|
100
93
|
@routes ||= self._get_routes
|
101
|
-
|
94
|
+
kwargs = kwargs.merge(html_kwargs)
|
102
95
|
begin
|
103
96
|
render(**kwargs)
|
104
97
|
rescue ActionView::MissingTemplate # fallback to rest_framework default view
|
@@ -106,10 +99,6 @@ module RESTFramework
|
|
106
99
|
end
|
107
100
|
render(**kwargs)
|
108
101
|
}
|
109
|
-
format.json {
|
110
|
-
kwargs = kwargs.merge(json_kwargs)
|
111
|
-
render(json: payload || '', **kwargs)
|
112
|
-
}
|
113
102
|
end
|
114
103
|
end
|
115
104
|
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
require_relative '../serializers'
|
3
|
+
|
4
|
+
module RESTFramework
|
5
|
+
|
6
|
+
module BaseModelControllerMixin
|
7
|
+
include BaseControllerMixin
|
8
|
+
def self.included(base)
|
9
|
+
if base.is_a? Class
|
10
|
+
BaseControllerMixin.included(base)
|
11
|
+
base.class_attribute(*[
|
12
|
+
:model,
|
13
|
+
:recordset,
|
14
|
+
:fields,
|
15
|
+
:action_fields,
|
16
|
+
:filterset_fields,
|
17
|
+
:allowed_parameters,
|
18
|
+
:allowed_action_parameters,
|
19
|
+
:serializer_class,
|
20
|
+
:extra_member_actions,
|
21
|
+
])
|
22
|
+
base.alias_method(:extra_collection_actions=, :extra_actions=)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
|
28
|
+
def get_serializer_class
|
29
|
+
return self.class.serializer_class || NativeModelSerializer
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get a list of fields for an action (or the current action if none given).
|
33
|
+
def get_fields(action: nil)
|
34
|
+
action_fields = self.class.action_fields || {}
|
35
|
+
|
36
|
+
# action will, by default, be the current action name
|
37
|
+
action = action_name.to_sym unless action
|
38
|
+
|
39
|
+
# index action should use :list fields if :index is not provided
|
40
|
+
action = :list if action == :index && !action_fields.key?(:index)
|
41
|
+
|
42
|
+
return action_fields[action] || self.class.fields || []
|
43
|
+
end
|
44
|
+
|
45
|
+
# Get a list of parameters allowed for an action (or the current action if none given).
|
46
|
+
def get_allowed_parameters(action: nil)
|
47
|
+
allowed_action_parameters = self.class.allowed_action_parameters || {}
|
48
|
+
|
49
|
+
# action will, by default, be the current action name
|
50
|
+
action = action_name.to_sym unless action
|
51
|
+
|
52
|
+
# index action should use :list allowed parameters if :index is not provided
|
53
|
+
action = :list if action == :index && !allowed_action_parameters.key?(:index)
|
54
|
+
|
55
|
+
return allowed_action_parameters[action] || self.class.allowed_parameters
|
56
|
+
end
|
57
|
+
|
58
|
+
# Filter the request body for keys in current action's allowed_parameters/fields config.
|
59
|
+
def _get_parameter_values_from_request_body
|
60
|
+
fields = self.get_allowed_parameters || self.get_fields
|
61
|
+
return @_get_field_values_from_request_body ||= (request.request_parameters.select { |p|
|
62
|
+
fields.include?(p.to_sym) || fields.include?(p.to_s)
|
63
|
+
})
|
64
|
+
end
|
65
|
+
alias :get_create_params :_get_parameter_values_from_request_body
|
66
|
+
alias :get_update_params :_get_parameter_values_from_request_body
|
67
|
+
|
68
|
+
# Filter params for keys allowed by the current action's filterset_fields/fields config.
|
69
|
+
def _get_filterset_values_from_params
|
70
|
+
fields = self.filterset_fields || self.get_fields
|
71
|
+
return @_get_field_values_from_params ||= request.query_parameters.select { |p|
|
72
|
+
fields.include?(p.to_sym) || fields.include?(p.to_s)
|
73
|
+
}
|
74
|
+
end
|
75
|
+
alias :get_lookup_params :_get_filterset_values_from_params
|
76
|
+
alias :get_filter_params :_get_filterset_values_from_params
|
77
|
+
|
78
|
+
# Get the recordset, filtered by the filter params.
|
79
|
+
def get_filtered_recordset
|
80
|
+
filter_params = self.get_filter_params
|
81
|
+
unless filter_params.blank?
|
82
|
+
return self.get_recordset.where(**self.get_filter_params.to_hash.symbolize_keys)
|
83
|
+
end
|
84
|
+
return self.get_recordset
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get a record by `id` or return a single record if recordset is filtered down to a single
|
88
|
+
# record.
|
89
|
+
def get_record
|
90
|
+
records = self.get_filtered_recordset
|
91
|
+
if params['id'] # direct lookup
|
92
|
+
return records.find(params['id'])
|
93
|
+
elsif records.length == 1
|
94
|
+
return records[0]
|
95
|
+
end
|
96
|
+
return nil
|
97
|
+
end
|
98
|
+
|
99
|
+
# Internal interface for get_model, protecting against infinite recursion with get_recordset.
|
100
|
+
def _get_model(from_internal_get_recordset: false)
|
101
|
+
return @model if instance_variable_defined?(:@model) && @model
|
102
|
+
return self.class.model if self.class.model
|
103
|
+
unless from_internal_get_recordset # prevent infinite recursion
|
104
|
+
recordset = self._get_recordset(from_internal_get_model: true)
|
105
|
+
return (@model = recordset.klass) if recordset
|
106
|
+
end
|
107
|
+
begin
|
108
|
+
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
109
|
+
rescue NameError
|
110
|
+
end
|
111
|
+
return nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Internal interface for get_recordset, protecting against infinite recursion with get_model.
|
115
|
+
def _get_recordset(from_internal_get_model: false)
|
116
|
+
return @recordset if instance_variable_defined?(:@recordset) && @recordset
|
117
|
+
return self.class.recordset if self.class.recordset
|
118
|
+
unless from_internal_get_model # prevent infinite recursion
|
119
|
+
model = self._get_model(from_internal_get_recordset: true)
|
120
|
+
return (@recordset = model.all) if model
|
121
|
+
end
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get the model for this controller.
|
126
|
+
def get_model
|
127
|
+
return _get_model
|
128
|
+
end
|
129
|
+
|
130
|
+
# Get the set of records this controller has access to.
|
131
|
+
def get_recordset
|
132
|
+
return _get_recordset
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module ListModelMixin
|
137
|
+
# TODO: pagination classes like Django
|
138
|
+
def index
|
139
|
+
@records = self.get_filtered_recordset
|
140
|
+
@serialized_records = self.get_serializer_class.new(
|
141
|
+
object: @records, controller: self
|
142
|
+
).serialize
|
143
|
+
return api_response(@serialized_records)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module ShowModelMixin
|
148
|
+
def show
|
149
|
+
@record = self.get_record
|
150
|
+
@serialized_record = self.get_serializer_class.new(
|
151
|
+
object: @record, controller: self
|
152
|
+
).serialize
|
153
|
+
return api_response(@serialized_record)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
module CreateModelMixin
|
158
|
+
def create
|
159
|
+
@record = self.get_model.create!(self.get_create_params)
|
160
|
+
@serialized_record = self.get_serializer_class.new(
|
161
|
+
object: @record, controller: self
|
162
|
+
).serialize
|
163
|
+
return api_response(@serialized_record)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
module UpdateModelMixin
|
168
|
+
def update
|
169
|
+
@record = self.get_record
|
170
|
+
@record.update!(self.get_update_params)
|
171
|
+
@serialized_record = self.get_serializer_class.new(
|
172
|
+
object: @record, controller: self
|
173
|
+
).serialize
|
174
|
+
return api_response(@serialized_record)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
module DestroyModelMixin
|
179
|
+
def destroy
|
180
|
+
@record = self.get_record
|
181
|
+
if @record
|
182
|
+
@record.destroy!
|
183
|
+
api_response('')
|
184
|
+
else
|
185
|
+
api_response({detail: "Method 'DELETE' not allowed."}, status: 405)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
module ReadOnlyModelControllerMixin
|
191
|
+
include BaseModelControllerMixin
|
192
|
+
def self.included(base)
|
193
|
+
if base.is_a? Class
|
194
|
+
BaseModelControllerMixin.included(base)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
include ListModelMixin
|
199
|
+
include ShowModelMixin
|
200
|
+
end
|
201
|
+
|
202
|
+
module ModelControllerMixin
|
203
|
+
include BaseModelControllerMixin
|
204
|
+
def self.included(base)
|
205
|
+
if base.is_a? Class
|
206
|
+
BaseModelControllerMixin.included(base)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
include ListModelMixin
|
211
|
+
include ShowModelMixin
|
212
|
+
include CreateModelMixin
|
213
|
+
include UpdateModelMixin
|
214
|
+
include DestroyModelMixin
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'rails'
|
2
1
|
require 'action_dispatch/routing/mapper'
|
3
2
|
|
4
3
|
module ActionDispatch::Routing
|
@@ -67,11 +66,11 @@ module ActionDispatch::Routing
|
|
67
66
|
|
68
67
|
# call either `resource` or `resources`, passing appropriate modifiers
|
69
68
|
skip_undefined = kwargs.delete(:skip_undefined) || true
|
70
|
-
skip = controller_class.
|
69
|
+
skip = controller_class.get_skip_actions(skip_undefined: skip_undefined)
|
71
70
|
public_send(resource_method, name, except: skip, **kwargs) do
|
72
71
|
if controller_class.respond_to?(:extra_member_actions)
|
73
72
|
member do
|
74
|
-
actions = controller_class.extra_member_actions
|
73
|
+
actions = controller_class.extra_member_actions || {}
|
75
74
|
actions = actions.select { |k,v| controller_class.method_defined?(k) } if skip_undefined
|
76
75
|
actions.each do |action, methods|
|
77
76
|
methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
|
@@ -83,8 +82,9 @@ module ActionDispatch::Routing
|
|
83
82
|
end
|
84
83
|
|
85
84
|
collection do
|
86
|
-
actions = controller_class.extra_actions
|
85
|
+
actions = controller_class.extra_actions || {}
|
87
86
|
actions = actions.select { |k,v| controller_class.method_defined?(k) } if skip_undefined
|
87
|
+
actions.reject! { |k,v| skip.include? k }
|
88
88
|
actions.each do |action, methods|
|
89
89
|
methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
|
90
90
|
methods.each do |m|
|
@@ -111,6 +111,26 @@ module ActionDispatch::Routing
|
|
111
111
|
end
|
112
112
|
end
|
113
113
|
|
114
|
+
# Route a controller without the default resourceful paths.
|
115
|
+
def rest_route(path=nil, skip_undefined: true, **kwargs, &block)
|
116
|
+
controller = kwargs.delete(:controller) || path
|
117
|
+
path = path.to_s
|
118
|
+
|
119
|
+
# route actions
|
120
|
+
controller_class = self._get_controller_class(controller, pluralize: false)
|
121
|
+
skip = controller_class.get_skip_actions(skip_undefined: skip_undefined)
|
122
|
+
actions = controller_class.extra_actions || {}
|
123
|
+
actions = actions.select { |k,v| controller_class.method_defined?(k) } if skip_undefined
|
124
|
+
actions.reject! { |k,v| skip.include? k }
|
125
|
+
actions.each do |action, methods|
|
126
|
+
methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
|
127
|
+
methods.each do |m|
|
128
|
+
public_send(m, File.join(path, action.to_s), controller: controller, action: action)
|
129
|
+
end
|
130
|
+
yield if block_given?
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
114
134
|
# Route a controller's `#root` to '/' in the current scope/namespace, along with other actions.
|
115
135
|
# @param label [Symbol] the snake_case name of the controller
|
116
136
|
def rest_root(path=nil, **kwargs, &block)
|
@@ -124,7 +144,7 @@ module ActionDispatch::Routing
|
|
124
144
|
|
125
145
|
# route any additional actions
|
126
146
|
controller_class = self._get_controller_class(controller, pluralize: false)
|
127
|
-
controller_class.extra_actions.each do |action, methods|
|
147
|
+
(controller_class.extra_actions || {}).each do |action, methods|
|
128
148
|
methods = [methods] if methods.is_a?(Symbol) || methods.is_a?(String)
|
129
149
|
methods.each do |m|
|
130
150
|
public_send(m, File.join(path, action.to_s), controller: controller, action: action)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RESTFramework
|
2
|
+
class BaseSerializer
|
3
|
+
attr_reader :errors
|
4
|
+
|
5
|
+
def initialize(object: nil, data: nil, controller: nil, **kwargs)
|
6
|
+
@object = object
|
7
|
+
@data = data
|
8
|
+
@controller = controller
|
9
|
+
end
|
10
|
+
|
11
|
+
def is_valid
|
12
|
+
return true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# This serializer uses `.as_json` to serialize objects. Despite the name, `.as_json` is a Rails
|
17
|
+
# method which converts objects to Ruby primitives (with the top-level being either an array or a
|
18
|
+
# hash).
|
19
|
+
class NativeModelSerializer < BaseSerializer
|
20
|
+
def initialize(model: nil, **kwargs)
|
21
|
+
super(**kwargs)
|
22
|
+
@model = model || @controller.send(:get_model)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get a configuration passable to `as_json` for the model.
|
26
|
+
def get_native_serializer_config
|
27
|
+
fields = @controller.send(:get_fields)
|
28
|
+
unless fields.blank?
|
29
|
+
columns, methods = fields.partition { |f| f.to_s.in?(@model.column_names) }
|
30
|
+
return {only: columns, methods: methods}
|
31
|
+
end
|
32
|
+
return {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Convert the object(s) to Ruby primitives.
|
36
|
+
def serialize
|
37
|
+
return @object.as_json(self.get_native_serializer_config)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -8,10 +8,10 @@ module RESTFramework
|
|
8
8
|
|
9
9
|
# First, attempt to get the version from git.
|
10
10
|
begin
|
11
|
-
version = `git describe`.strip
|
11
|
+
version = `git describe 2>/dev/null`.strip
|
12
12
|
raise "blank version" if version.nil? || version.match(/^\w*$/)
|
13
13
|
# Check for local changes.
|
14
|
-
changes = `git status --porcelain`
|
14
|
+
changes = `git status --porcelain 2>/dev/null`
|
15
15
|
version << '.localchanges' if changes.strip.length > 0
|
16
16
|
return version
|
17
17
|
rescue
|
@@ -21,7 +21,7 @@ module RESTFramework
|
|
21
21
|
begin
|
22
22
|
version = File.read(File.expand_path("VERSION_STAMP", __dir__))
|
23
23
|
unless version.nil? || version.match(/^\w*$/)
|
24
|
-
return (@_version = version) # cache VERSION_STAMP content
|
24
|
+
return (@_version = version) # cache VERSION_STAMP content
|
25
25
|
end
|
26
26
|
rescue
|
27
27
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rest_framework
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregory N. Schmit
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -33,19 +33,18 @@ extra_rdoc_files: []
|
|
33
33
|
files:
|
34
34
|
- LICENSE
|
35
35
|
- README.md
|
36
|
-
- app/assets/images/rest_framework_favicon.ico
|
37
|
-
- app/assets/javascripts/rest_framework.js
|
38
|
-
- app/assets/stylesheets/rest_framework.css
|
39
36
|
- app/views/layouts/rest_framework.html.erb
|
37
|
+
- app/views/rest_framework/_head.html.erb
|
40
38
|
- app/views/rest_framework/_routes.html.erb
|
41
39
|
- app/views/rest_framework/default.html.erb
|
42
40
|
- lib/rest_framework.rb
|
43
41
|
- lib/rest_framework/VERSION_STAMP
|
44
|
-
- lib/rest_framework/
|
45
|
-
- lib/rest_framework/
|
46
|
-
- lib/rest_framework/
|
42
|
+
- lib/rest_framework/controller_mixins.rb
|
43
|
+
- lib/rest_framework/controller_mixins/base.rb
|
44
|
+
- lib/rest_framework/controller_mixins/models.rb
|
47
45
|
- lib/rest_framework/engine.rb
|
48
46
|
- lib/rest_framework/routers.rb
|
47
|
+
- lib/rest_framework/serializers.rb
|
49
48
|
- lib/rest_framework/version.rb
|
50
49
|
homepage: https://github.com/gregschmit/rails-rest-framework
|
51
50
|
licenses:
|
Binary file
|
File without changes
|
@@ -1,225 +0,0 @@
|
|
1
|
-
require_relative 'base'
|
2
|
-
|
3
|
-
module RESTFramework
|
4
|
-
|
5
|
-
module BaseModelControllerMixin
|
6
|
-
include BaseControllerMixin
|
7
|
-
|
8
|
-
# By default (and for now), we will just use `as_json`, but we should consider supporting:
|
9
|
-
# active_model_serializers (problem:
|
10
|
-
# https://github.com/rails-api/active_model_serializers#whats-happening-to-ams)
|
11
|
-
# fast_jsonapi (really good and fast serializers)
|
12
|
-
#@serializer
|
13
|
-
#@list_serializer
|
14
|
-
#@show_serializer
|
15
|
-
#@create_serializer
|
16
|
-
#@update_serializer
|
17
|
-
|
18
|
-
module ClassMethods
|
19
|
-
extend ClassMethodHelpers
|
20
|
-
include BaseControllerMixin::ClassMethods
|
21
|
-
|
22
|
-
_restframework_attr_reader(:model)
|
23
|
-
_restframework_attr_reader(:recordset)
|
24
|
-
_restframework_attr_reader(:singleton_controller)
|
25
|
-
|
26
|
-
_restframework_attr_reader(:fields)
|
27
|
-
_restframework_attr_reader(:list_fields)
|
28
|
-
_restframework_attr_reader(:show_fields)
|
29
|
-
_restframework_attr_reader(:create_fields)
|
30
|
-
_restframework_attr_reader(:update_fields)
|
31
|
-
|
32
|
-
_restframework_attr_reader(:extra_member_actions, default: {})
|
33
|
-
|
34
|
-
# For model-based mixins, `@extra_collection_actions` is synonymous with `@extra_actions`.
|
35
|
-
# @param skip_undefined [Boolean] whether we should skip routing undefined actions
|
36
|
-
def extra_actions(skip_undefined: true)
|
37
|
-
actions = (
|
38
|
-
_restframework_try_class_level_variable_get(:extra_collection_actions) ||
|
39
|
-
_restframework_try_class_level_variable_get(:extra_actions, default: {})
|
40
|
-
)
|
41
|
-
actions = actions.select { |a| self.method_defined?(a) } if skip_undefined
|
42
|
-
return actions
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def self.included(base)
|
47
|
-
base.extend ClassMethods
|
48
|
-
end
|
49
|
-
|
50
|
-
protected
|
51
|
-
|
52
|
-
# Get a list of fields for the current action.
|
53
|
-
def get_fields
|
54
|
-
return @fields if @fields
|
55
|
-
|
56
|
-
# index action should use list_fields
|
57
|
-
name = (action_name == 'index') ? 'list' : action_name
|
58
|
-
|
59
|
-
begin
|
60
|
-
@fields = self.class.send("#{name}_fields")
|
61
|
-
rescue NameError
|
62
|
-
end
|
63
|
-
@fields ||= self.class.fields || []
|
64
|
-
|
65
|
-
return @fields
|
66
|
-
end
|
67
|
-
|
68
|
-
# Get a configuration passable to `as_json` for the model.
|
69
|
-
def get_model_serializer_config
|
70
|
-
fields = self.get_fields
|
71
|
-
unless fields.blank?
|
72
|
-
columns, methods = fields.partition { |f| f.to_s.in?(self.get_model.column_names) }
|
73
|
-
return {only: columns, methods: methods}
|
74
|
-
end
|
75
|
-
return {}
|
76
|
-
end
|
77
|
-
|
78
|
-
# Filter the request body for keys allowed by the current action's field config.
|
79
|
-
def _get_field_values_from_request_body
|
80
|
-
return @_get_field_values_from_request_body ||= (request.request_parameters.select { |p|
|
81
|
-
self.get_fields.include?(p.to_sym) || self.get_fields.include?(p.to_s)
|
82
|
-
})
|
83
|
-
end
|
84
|
-
alias :get_create_params :_get_field_values_from_request_body
|
85
|
-
alias :get_update_params :_get_field_values_from_request_body
|
86
|
-
|
87
|
-
# Filter params for keys allowed by the current action's field config.
|
88
|
-
def _get_field_values_from_params
|
89
|
-
return @_get_field_values_from_params ||= params.permit(*self.get_fields)
|
90
|
-
end
|
91
|
-
alias :get_lookup_params :_get_field_values_from_params
|
92
|
-
alias :get_filter_params :_get_field_values_from_params
|
93
|
-
|
94
|
-
# Get the recordset, filtered by the filter params.
|
95
|
-
def get_filtered_recordset
|
96
|
-
filter_params = self.get_filter_params
|
97
|
-
unless filter_params.blank?
|
98
|
-
return self.get_recordset.where(**self.get_filter_params)
|
99
|
-
end
|
100
|
-
return self.get_recordset
|
101
|
-
end
|
102
|
-
|
103
|
-
# Get a record by `id` or return a single record if recordset is filtered down to a single record.
|
104
|
-
def get_record
|
105
|
-
records = self.get_filtered_recordset
|
106
|
-
if params['id'] # direct lookup
|
107
|
-
return records.find(params['id'])
|
108
|
-
elsif records.length == 1
|
109
|
-
return records[0]
|
110
|
-
end
|
111
|
-
return nil
|
112
|
-
end
|
113
|
-
|
114
|
-
# Internal interface for get_model, protecting against infinite recursion with get_recordset.
|
115
|
-
def _get_model(from_internal_get_recordset: false)
|
116
|
-
return @model if @model
|
117
|
-
return self.class.model if self.class.model
|
118
|
-
unless from_internal_get_recordset # prevent infinite recursion
|
119
|
-
recordset = self._get_recordset(from_internal_get_model: true)
|
120
|
-
return (@model = recordset.klass) if recordset
|
121
|
-
end
|
122
|
-
begin
|
123
|
-
return (@model = self.class.name.demodulize.match(/(.*)Controller/)[1].singularize.constantize)
|
124
|
-
rescue NameError
|
125
|
-
end
|
126
|
-
return nil
|
127
|
-
end
|
128
|
-
|
129
|
-
# Internal interface for get_recordset, protecting against infinite recursion with get_model.
|
130
|
-
def _get_recordset(from_internal_get_model: false)
|
131
|
-
return @recordset if @recordset
|
132
|
-
return self.class.recordset if self.class.recordset
|
133
|
-
unless from_internal_get_model # prevent infinite recursion
|
134
|
-
model = self._get_model(from_internal_get_recordset: true)
|
135
|
-
return (@recordset = model.all) if model
|
136
|
-
end
|
137
|
-
return nil
|
138
|
-
end
|
139
|
-
|
140
|
-
# Get the model for this controller.
|
141
|
-
def get_model
|
142
|
-
return _get_model
|
143
|
-
end
|
144
|
-
|
145
|
-
# Get the base set of records this controller has access to.
|
146
|
-
def get_recordset
|
147
|
-
return _get_recordset
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
module ListModelMixin
|
152
|
-
# TODO: pagination classes like Django
|
153
|
-
def index
|
154
|
-
@records = self.get_filtered_recordset
|
155
|
-
api_response(@records, **self.get_model_serializer_config)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
module ShowModelMixin
|
160
|
-
def show
|
161
|
-
@record = self.get_record
|
162
|
-
api_response(@record, **self.get_model_serializer_config)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
module CreateModelMixin
|
167
|
-
def create
|
168
|
-
begin
|
169
|
-
@record = self.get_model.create!(self.get_create_params)
|
170
|
-
rescue ActiveRecord::RecordInvalid => e
|
171
|
-
api_response(e.record.messages, status: 400)
|
172
|
-
end
|
173
|
-
api_response(@record, **self.get_model_serializer_config)
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
module UpdateModelMixin
|
178
|
-
def update
|
179
|
-
@record = self.get_record
|
180
|
-
if @record
|
181
|
-
@record.attributes(self.get_update_params)
|
182
|
-
@record.save!
|
183
|
-
api_response(@record, **self.get_model_serializer_config)
|
184
|
-
else
|
185
|
-
api_response({detail: "Record not found."}, status: 404)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
module DestroyModelMixin
|
191
|
-
def destroy
|
192
|
-
@record = self.get_record
|
193
|
-
if @record
|
194
|
-
@record.destroy!
|
195
|
-
api_response('')
|
196
|
-
else
|
197
|
-
api_response({detail: "Method 'DELETE' not allowed."}, status: 405)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
module ReadOnlyModelControllerMixin
|
203
|
-
include BaseModelControllerMixin
|
204
|
-
def self.included(base)
|
205
|
-
base.extend BaseModelControllerMixin::ClassMethods
|
206
|
-
end
|
207
|
-
|
208
|
-
include ListModelMixin
|
209
|
-
include ShowModelMixin
|
210
|
-
end
|
211
|
-
|
212
|
-
module ModelControllerMixin
|
213
|
-
include BaseModelControllerMixin
|
214
|
-
def self.included(base)
|
215
|
-
base.extend BaseModelControllerMixin::ClassMethods
|
216
|
-
end
|
217
|
-
|
218
|
-
include ListModelMixin
|
219
|
-
include ShowModelMixin
|
220
|
-
include CreateModelMixin
|
221
|
-
include UpdateModelMixin
|
222
|
-
include DestroyModelMixin
|
223
|
-
end
|
224
|
-
|
225
|
-
end
|