model_driven_api 2.3.5 → 2.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
         
     |