model_driven_api 2.3.5 → 2.3.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +52 -2
- data/app/controllers/api/v2/application_controller.rb +16 -5
- data/app/controllers/api/v2/info_controller.rb +1 -1
- data/config/initializers/time_with_zone.rb +8 -0
- data/lib/concerns/api_exception_management.rb +10 -7
- data/lib/model_driven_api/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 190f87343976c6c393f9b2c80163ddc403b2575fd2c8fcceb125ef1996e7532a
|
4
|
+
data.tar.gz: e393facfcd2b11ac2af44df2f5602074e2da1ee48974dbb2fdec6d4f346c837e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4f9083c41e709a64d99278f84594d28ac94175983678c01b2b08124facf2ccf3264808034a1c30eac3d2801a763807ffc480bbc4ab17afe929216b3ce096ece
|
7
|
+
data.tar.gz: 25e8013e5421b51b5fdce2d444fabdb5f66bc9e4805df7c2761646b43a0058dabf70ed7e11d2e36494740c0d5966ea300a4351a4f16754837caf0f23826a7b7e
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Model Driven Api
|
2
2
|
|
3
|
-
##
|
3
|
+
## Goals
|
4
4
|
|
5
|
-
To have a comprehensive and meaningful Model driven API right out of the box by just creating migrations in your rails app or engine. With all the CRUD operations in place out of the box and easily expandable with custom actions if needed.
|
5
|
+
* To have a comprehensive and meaningful Model driven API right out of the box by just creating migrations in your rails app or engine. With all the CRUD operations in place out of the box and easily expandable with custom actions if needed.
|
6
|
+
* To have a plain REST implementation which adapts the returned JSON to the specific needs of the client, **without the need to change backend code**, this may overcome the biggest disadvantage of REST vs GraphQL = client driver presentation.
|
6
7
|
|
7
8
|
## TL;DR 5-10 minutes adoption
|
8
9
|
|
@@ -27,6 +28,55 @@ I've always been interested in effortless, no-fuss, conventions' based developme
|
|
27
28
|
|
28
29
|
Doing this means also narrowing a bit the scope of the tools, taking decisions, at least for the first implementations and versions of this engine, so, this works well if the data is relational, this is a prerequisite (postgres, mysql, mssql, etc.).
|
29
30
|
|
31
|
+
## REST Enhanced
|
32
|
+
|
33
|
+
Thanks to the inclusion of [Ransack](https://github.com/activerecord-hackery/ransack/wiki) and [ActiveModel::Serializer](https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html), by just adding the querystring keys **q** and **a**, you can create complex queries (q) to obtain just the records you need, which present in the returnd JSON just the attributes (a) needed.
|
34
|
+
By combining the two keys, you can obtain just the data you want.
|
35
|
+
|
36
|
+
The *json_attrs* or *a* query string passed accepts these keys (Please see [ActiveModel::Serializer](https://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html) for reference):
|
37
|
+
- only: list [] of model fields to be shown in JSON serialization
|
38
|
+
- except: exclude these fields from the JSON serialization, this is a list []
|
39
|
+
- methods: include the result of some methods defined in the model, this is a list []
|
40
|
+
- include: include associated models, it's an object {} which also accepts the keys described in this document (only, except, methods, include)
|
41
|
+
|
42
|
+
### Example
|
43
|
+
|
44
|
+
```
|
45
|
+
{{ base_url }}/{{ controller }}?a[only][]=locked&a[only][]=username&a[methods][]=jwe_data
|
46
|
+
```
|
47
|
+
|
48
|
+
Is translated to:
|
49
|
+
|
50
|
+
```
|
51
|
+
{a: {only: ["locked", "username"], methods: ["jwe_data"]}}
|
52
|
+
```
|
53
|
+
|
54
|
+
Which tells the API controller just to return this optimized serialization:
|
55
|
+
|
56
|
+
```
|
57
|
+
[
|
58
|
+
{
|
59
|
+
"username": "Administrator",
|
60
|
+
"locked": false,
|
61
|
+
"jwe_data": "eyJhbGciOiJkaXIiLCJlbmMiOiJSMTI4R0NNIn0..yz0tnC6y3BzgoOsO.BjHb9CRIb0vrv7nnEx54Ac8-cATPJ9sTlQSSxRbTmtcPHc5KhvtyE_hyBRnIcK92bzUBRwdy6ASB2XJVy1VfWxAmO8E.4tOzJlfuXi-shaRhDSkOyg"
|
62
|
+
}
|
63
|
+
]
|
64
|
+
```
|
65
|
+
|
66
|
+
By combinig with Ransack's **q** query string key (please read [Ransack](https://github.com/activerecord-hackery/ransack/wiki) documentation to discover all the possible and complex searches you can make), you can obtain right what you want:
|
67
|
+
|
68
|
+
```
|
69
|
+
{{ base_url }}/{{ controller }}?a[only][]=locked&a[only][]=username&a[methods][]=jwe_data&q[email_cont][]=adm
|
70
|
+
```
|
71
|
+
|
72
|
+
Which translates to:
|
73
|
+
|
74
|
+
```
|
75
|
+
{a: {only: ["locked", "username"], methods: ["jwe_data"]}, q: { email_cont: ["adm"]}}
|
76
|
+
```
|
77
|
+
|
78
|
+
For bigger searches, which may over crowd the querystring length, you can always use the default [Search](#Search) POST endpoint.
|
79
|
+
|
30
80
|
## v2?
|
31
81
|
|
32
82
|
Yes, this is the second version of such an effort and you can note it from the api calls, which are all under the ```/api/v2``` namespace the [/api/v1](https://github.com/gabrieletassoni/thecore_api) one, was were it all started, many ideas are ported from there, such as the generation of the automatic model based crud actions, as well as custom actions definitions and all the things that make also this gem useful for my daily job were already in place, but it was too coupled with [thecore](https://github.com/gabrieletassoni/thecore)'s [rails_admin](https://github.com/sferik/rails_admin) UI, making it impossible to create a complete UI-less, API only application, out of the box and directly based of the DB schema, with all the bells and whistles I needed (mainly self adapting, data and schema driven API functionalities).
|
@@ -20,7 +20,10 @@ class Api::V2::ApplicationController < ActionController::API
|
|
20
20
|
return render json: result, status: 200 if status == true
|
21
21
|
|
22
22
|
# Normal Index Action with Ransack querying
|
23
|
-
|
23
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
24
|
+
# TODO: Remove it
|
25
|
+
# @q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(@query.presence|| params[:q])
|
26
|
+
@q = @model.ransack(@query.presence|| params[:q])
|
24
27
|
@records_all = @q.result # (distinct: true) Removing, but I'm not sure, with it I cannot sort in postgres for associated records (throws an exception on misuse of sort with distinct)
|
25
28
|
page = (@page.presence || params[:page])
|
26
29
|
per = (@per.presence || params[:per])
|
@@ -66,7 +69,9 @@ class Api::V2::ApplicationController < ActionController::API
|
|
66
69
|
return render json: result, status: 200 if status == true
|
67
70
|
|
68
71
|
# Normal Create Action
|
69
|
-
|
72
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
73
|
+
# TODO: Remove it
|
74
|
+
# @record.user_id = current_user.id if @model.column_names.include? "user_id"
|
70
75
|
@record.save!
|
71
76
|
render json: @record.to_json(json_attrs), status: 201
|
72
77
|
end
|
@@ -129,7 +134,6 @@ class Api::V2::ApplicationController < ActionController::API
|
|
129
134
|
# puts "Found header #{header}: #{request.headers[header.underscore.dasherize]}"
|
130
135
|
check_authorization("Authorize#{header}".constantize.call(request.headers, request.raw_post)) if request.headers[header.underscore.dasherize]
|
131
136
|
end
|
132
|
-
return unauthenticated!(OpenStruct.new({message: @auth_errors})) unless @current_user
|
133
137
|
|
134
138
|
# This is the default one, if the header doesn't have a valid form for one of the other Auth methods, then use this Auth Class
|
135
139
|
check_authorization AuthorizeApiRequest.call(request.headers) unless @current_user
|
@@ -144,12 +148,19 @@ class Api::V2::ApplicationController < ActionController::API
|
|
144
148
|
|
145
149
|
def find_record
|
146
150
|
record_id ||= (params[:path].split("/").second.to_i rescue nil)
|
147
|
-
|
151
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
152
|
+
# TODO: Remove it
|
153
|
+
# @record = @model.column_names.include?("user_id") ? @model.where(id: (record_id.presence || @record_id.presence || params[:id]), user_id: current_user.id).first : @model.find((@record_id.presence || params[:id]))
|
154
|
+
@record = @model.find((@record_id.presence || params[:id]))
|
148
155
|
return not_found! if @record.blank?
|
149
156
|
end
|
150
157
|
|
151
158
|
def json_attrs
|
152
|
-
|
159
|
+
# In order of importance: if you send the configuration via querystring you are ok
|
160
|
+
# has precedence over if you have setup the json_attrs in the model concern
|
161
|
+
from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
|
162
|
+
from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
|
163
|
+
from_params.presence || @model.json_attrs.presence || @json_attrs.presence || {} rescue {}
|
153
164
|
end
|
154
165
|
|
155
166
|
def extract_model
|
@@ -6,7 +6,7 @@ class Api::V2::InfoController < Api::V2::ApplicationController
|
|
6
6
|
|
7
7
|
# api :GET, '/api/v2/info/version', "Just prints the APPVERSION."
|
8
8
|
def version
|
9
|
-
render json: { version:
|
9
|
+
render json: { version: "TODO: Find a Way to Dynamically Obtain It" }.to_json, status: 200
|
10
10
|
end
|
11
11
|
|
12
12
|
# api :GET, '/api/v2/info/roles'
|
@@ -2,13 +2,16 @@ module ApiExceptionManagement
|
|
2
2
|
extend ActiveSupport::Concern
|
3
3
|
|
4
4
|
included do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
if Rails.env.production?
|
6
|
+
rescue_from NoMethodError, with: :not_found!
|
7
|
+
rescue_from CanCan::AccessDenied, with: :unauthorized!
|
8
|
+
rescue_from AuthenticateUser::AccessDenied, with: :unauthenticated!
|
9
|
+
rescue_from ActionController::RoutingError, with: :not_found!
|
10
|
+
rescue_from ActiveModel::ForbiddenAttributesError, with: :fivehundred!
|
11
|
+
rescue_from ActiveRecord::RecordInvalid, with: :invalid!
|
12
|
+
rescue_from ActiveRecord::RecordNotFound, with: :not_found!
|
13
|
+
rescue_from ActiveRecord::RecordNotUnique, with: :invalid!
|
14
|
+
end
|
12
15
|
|
13
16
|
def unauthenticated! exception = AuthenticateUser::AccessDenied.new
|
14
17
|
response.headers['WWW-Authenticate'] = "Token realm=Application"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: model_driven_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.3.
|
4
|
+
version: 2.3.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gabriele Tassoni
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thecore_backend_commons
|
@@ -142,6 +142,7 @@ files:
|
|
142
142
|
- config/initializers/after_initialize_for_model_driven_api.rb
|
143
143
|
- config/initializers/cors_api_thecore.rb
|
144
144
|
- config/initializers/knock.rb
|
145
|
+
- config/initializers/time_with_zone.rb
|
145
146
|
- config/initializers/wrap_parameters.rb
|
146
147
|
- config/routes.rb
|
147
148
|
- lib/concerns/api_exception_management.rb
|