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: ec88dcaf27ab5abca89f8db87d1293237950dc1879565048b4fbb3d2901b7ac3
4
- data.tar.gz: 643513f277c72318ac7b3ffb9ef2e165258113f093611e29442e24a0aa5d553d
3
+ metadata.gz: 96a404941f09e02fd096c7b7f62cd9ba80ee3a61058652e5b74961167d7d123c
4
+ data.tar.gz: 821c7372263973d516017ce7704a51a851cbb87a2259b9540c548b18aa506f30
5
5
  SHA512:
6
- metadata.gz: 76eccc737ca72f10b10b7b0ae265560f41c53950c423faa8c78fece2097e502c1fe477809720e17acff7bdf796d1a5d186d8710d7fe099d3da6e12f987a2fb88
7
- data.tar.gz: 4a9052111ab66b72e8639fa2d567bb4cfd99b565fa304fc17e2e80d8eb1d3627a526f3a2f7fcc8cc5229f4816bb578085cc06a6eef85028f603fb10538e51699
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
- ## 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,18 +128,20 @@ class Api::V2::ApplicationController < ActionController::API
122
128
  end
123
129
 
124
130
  def authenticate_request
125
- # puts request.headers.inspect
131
+ Rails.logger.info request.headers.inspect
126
132
  @current_user = nil
127
- # puts "Are there wbehooks headers to check for? #{Settings.ns(:security).allowed_authorization_headers}"
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
- # 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
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
- @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
+ # 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
- ((@model.json_attrs.presence || @json_attrs.presence || {}) rescue {})
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
@@ -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
@@ -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.7
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-02-26 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
@@ -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