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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 315cd43dc90a24097cd08e0ee261ba26b94b8b2e1a9f640416b68b7366b0352e
4
- data.tar.gz: 47a36df93c41c8bcb0a32d832d3b7067e1b9894eda39c313fc0c18cdf17ced2d
3
+ metadata.gz: 190f87343976c6c393f9b2c80163ddc403b2575fd2c8fcceb125ef1996e7532a
4
+ data.tar.gz: e393facfcd2b11ac2af44df2f5602074e2da1ee48974dbb2fdec6d4f346c837e
5
5
  SHA512:
6
- metadata.gz: 8d121bf09e8f799dc08ba1b7a5cb5c1b17b6d810eaee6a4c32886733f1e1133a770618bcc2c31ebbf5876f377d017244ad68d63b9d76770dfdcd4e779e09c313
7
- data.tar.gz: c47b0e7fc8a63f96eb238cc8b6d8601b15e0d534b00151717ac333fff86f764998bb7857a471037c918bb25c24b89fe4ab7a63186683209c98e48eb4f18ffb4f
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
- ## Goal
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
- @q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(@query.presence|| params[:q])
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
- @record.user_id = current_user.id if @model.column_names.include? "user_id"
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
- @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]))
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
- ((@model.json_attrs.presence || @json_attrs.presence || {}) rescue {})
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: ModelDrivenApi::VERSION }.to_json, status: 200
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'
@@ -0,0 +1,8 @@
1
+ # Turns API calls to always UTC, leaving the APP the freedom to use local timezone
2
+ module ActiveSupport
3
+ class TimeWithZone
4
+ def as_json(options = nil)
5
+ utc
6
+ end
7
+ end
8
+ end
@@ -2,13 +2,16 @@ module ApiExceptionManagement
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- rescue_from NoMethodError, with: :not_found!
6
- rescue_from CanCan::AccessDenied, with: :unauthorized!
7
- rescue_from AuthenticateUser::AccessDenied, with: :unauthenticated!
8
- rescue_from ActionController::RoutingError, with: :not_found!
9
- rescue_from ActiveModel::ForbiddenAttributesError, with: :fivehundred!
10
- rescue_from ActiveRecord::RecordInvalid, with: :invalid!
11
- rescue_from ActiveRecord::RecordNotFound, with: :not_found!
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"
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "#{`git describe --tags $(git rev-list --tags --max-count=1)`}"
2
+ VERSION = "#{`git describe --tags $(git rev-list --tags --max-count=1)`.chomp}"
3
3
  end
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.5
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-02-16 00:00:00.000000000 Z
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