model_driven_api 2.3.7 → 2.3.12
Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96a404941f09e02fd096c7b7f62cd9ba80ee3a61058652e5b74961167d7d123c
|
4
|
+
data.tar.gz: 821c7372263973d516017ce7704a51a851cbb87a2259b9540c548b18aa506f30
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9ce5471c94e2de11172bafcd047ce527840eb950eaef9281d9030542bcdc3ac7149edd81b366f38e6b8c7abe2214b4169f89c1412134d4f89e68a8f977db93c
|
7
|
+
data.tar.gz: 68756bc2801c58b653e12cbd6b02a0cb5379b5c6471246f371d56f8cae0c40eb41172b1c410819c2562507d80d2e8a76c03b56e7e8dc0f7d8f519bebd329fced
|
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
|
@@ -108,7 +113,8 @@ class Api::V2::ApplicationController < ActionController::API
|
|
108
113
|
resource = "custom_action_#{params[:do]}"
|
109
114
|
raise NoMethodError unless @model.respond_to?(resource)
|
110
115
|
# return true, MultiJson.dump(params[:id].blank? ? @model.send(resource, params) : @model.send(resource, params[:id].to_i, params))
|
111
|
-
|
116
|
+
puts json_attrs
|
117
|
+
return true, @model.send(resource, params).to_json(json_attrs)
|
112
118
|
end
|
113
119
|
# if it's here there is no custom action in the request querystring
|
114
120
|
return false
|
@@ -122,18 +128,20 @@ class Api::V2::ApplicationController < ActionController::API
|
|
122
128
|
end
|
123
129
|
|
124
130
|
def authenticate_request
|
125
|
-
|
131
|
+
Rails.logger.info request.headers.inspect
|
126
132
|
@current_user = nil
|
127
|
-
|
133
|
+
Rails.logger.info "Are there webhooks headers to check for? #{Settings.ns(:security).allowed_authorization_headers}"
|
128
134
|
Settings.ns(:security).allowed_authorization_headers.split(",").each do |header|
|
129
135
|
# puts "Found header #{header}: #{request.headers[header.underscore.dasherize]}"
|
130
136
|
check_authorization("Authorize#{header}".constantize.call(request.headers, request.raw_post)) if request.headers[header.underscore.dasherize]
|
131
137
|
end
|
132
138
|
|
133
|
-
|
139
|
+
Rails.logger.info "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"
|
134
140
|
check_authorization AuthorizeApiRequest.call(request.headers) unless @current_user
|
141
|
+
Rails.logger.info "Inspect @current_user: #{@current_user} if nil, then returns unauthenticated"
|
135
142
|
return unauthenticated!(OpenStruct.new({message: @auth_errors})) unless @current_user
|
136
143
|
|
144
|
+
Rails.logger.info "We are here, so the user authenticated"
|
137
145
|
current_user = @current_user
|
138
146
|
params[:current_user_id] = @current_user.id
|
139
147
|
# Now every time the user fires off a successful GET request,
|
@@ -143,12 +151,19 @@ class Api::V2::ApplicationController < ActionController::API
|
|
143
151
|
|
144
152
|
def find_record
|
145
153
|
record_id ||= (params[:path].split("/").second.to_i rescue nil)
|
146
|
-
|
154
|
+
# Keeping this automation can be too dangerous and lead to unpredicted results
|
155
|
+
# TODO: Remove it
|
156
|
+
# @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]))
|
157
|
+
@record = @model.find((@record_id.presence || params[:id]))
|
147
158
|
return not_found! if @record.blank?
|
148
159
|
end
|
149
160
|
|
150
161
|
def json_attrs
|
151
|
-
|
162
|
+
# In order of importance: if you send the configuration via querystring you are ok
|
163
|
+
# has precedence over if you have setup the json_attrs in the model concern
|
164
|
+
from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
|
165
|
+
from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
|
166
|
+
from_params.presence || @json_attrs.presence || @model.json_attrs.presence || {} rescue {}
|
152
167
|
end
|
153
168
|
|
154
169
|
def extract_model
|
@@ -10,6 +10,7 @@ module ApiExceptionManagement
|
|
10
10
|
rescue_from ActiveModel::ForbiddenAttributesError, with: :fivehundred!
|
11
11
|
rescue_from ActiveRecord::RecordInvalid, with: :invalid!
|
12
12
|
rescue_from ActiveRecord::RecordNotFound, with: :not_found!
|
13
|
+
rescue_from ActiveRecord::RecordNotUnique, with: :invalid!
|
13
14
|
end
|
14
15
|
|
15
16
|
def unauthenticated! exception = AuthenticateUser::AccessDenied.new
|
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.12
|
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-04-12 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
|