model_driven_api 2.3.8 → 2.3.13

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: b8eeb8407dddfe99afa98093d2c16b193572843694616f7b27249c3661390b16
4
- data.tar.gz: 31287c2fb6cb36353e76652e470e6d3c58ad4d4140c08a90e3b633ab98324a91
3
+ metadata.gz: a5497bdb2727e103fa3727595ca62bbaf065b9135dd9bfbc8674f21506929dd0
4
+ data.tar.gz: ad09cdae9730b0f5c7983eb478c660ef8cc1c13d1d2c1cb13cd9a143b67f4029
5
5
  SHA512:
6
- metadata.gz: 519231f4c1073ffe54c3800ade34ee21cbcee27bfa8e568b66521808458a9def469679a01ca82222056ea69cbd314eb5a8a5bac979ba8801ea7ee7b926e2835c
7
- data.tar.gz: 4ed7f93269ce8bf37104a36cb3b71c5de52f45d7ce6623eee6ffdcb2b95fd86c53b043ff5bf2e2692a784a9d079dfe0b787e9bb228486a4d4854faf4c7a03529
6
+ metadata.gz: 719fcf3175dced8eaea5e238551d7d143edc185e9131c44ae9bf5358509de252da07491c27c16f485644c782f73c8f351379744c60f20bc803b074f024ecdbb8
7
+ data.tar.gz: ad157de69fbd575e5a9197594901b811a0cb1e74f25eb49146c011fbec9d32ebd6874e8712f2ef8edff4799d9e46cd04e2040c5cbe7c48b3a04e7f428917ab5a
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
@@ -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
- return true, MultiJson.dump(@model.send(resource, params))
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,15 +128,12 @@ class Api::V2::ApplicationController < ActionController::API
122
128
  end
123
129
 
124
130
  def authenticate_request
125
- # puts request.headers.inspect
126
131
  @current_user = nil
127
- # puts "Are there wbehooks headers to check for? #{Settings.ns(:security).allowed_authorization_headers}"
128
132
  Settings.ns(:security).allowed_authorization_headers.split(",").each do |header|
129
133
  # puts "Found header #{header}: #{request.headers[header.underscore.dasherize]}"
130
134
  check_authorization("Authorize#{header}".constantize.call(request.headers, request.raw_post)) if request.headers[header.underscore.dasherize]
131
135
  end
132
136
 
133
- # 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
137
  check_authorization AuthorizeApiRequest.call(request.headers) unless @current_user
135
138
  return unauthenticated!(OpenStruct.new({message: @auth_errors})) unless @current_user
136
139
 
@@ -143,12 +146,19 @@ class Api::V2::ApplicationController < ActionController::API
143
146
 
144
147
  def find_record
145
148
  record_id ||= (params[:path].split("/").second.to_i rescue nil)
146
- @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]))
149
+ # Keeping this automation can be too dangerous and lead to unpredicted results
150
+ # TODO: Remove it
151
+ # @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]))
152
+ @record = @model.find((@record_id.presence || params[:id]))
147
153
  return not_found! if @record.blank?
148
154
  end
149
155
 
150
156
  def json_attrs
151
- ((@model.json_attrs.presence || @json_attrs.presence || {}) rescue {})
157
+ # In order of importance: if you send the configuration via querystring you are ok
158
+ # has precedence over if you have setup the json_attrs in the model concern
159
+ from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
160
+ from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
161
+ from_params.presence || @json_attrs.presence || @model.json_attrs.presence || {} rescue {}
152
162
  end
153
163
 
154
164
  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
@@ -1,11 +1,11 @@
1
1
  class JsonWebToken
2
2
  class << self
3
3
  def encode(payload, expiry = 15.minutes.from_now.to_i)
4
- ::JWT.encode(payload.merge(exp: expiry), ::Rails.application.credentials.dig(:secret_key_base))
4
+ ::JWT.encode(payload.merge(exp: expiry), ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"])
5
5
  end
6
6
 
7
7
  def decode(token)
8
- body = ::JWT.decode(token, ::Rails.application.credentials.dig(:secret_key_base))[0]
8
+ body = ::JWT.decode(token, ::Rails.application.credentials.dig(:secret_key_base).presence||ENV["SECRET_KEY_BASE"])[0]
9
9
  ::HashWithIndifferentAccess.new body
10
10
  rescue
11
11
  nil
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.8
4
+ version: 2.3.13
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-03-08 00:00:00.000000000 Z
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