apicasso 0.1.2 → 0.1.3

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: 75481eb1df595fc8cda2ad65bd8db05bd19d6fad66a46b6126b1a8549cafeeca
4
- data.tar.gz: 51233d723df45b0c763a8632bbf913e119e476481e1fd4c6c92e041512f8b556
3
+ metadata.gz: ed1753c335c48bf87951c03895be8817219ae73fb171980b0819fd8d24333dc2
4
+ data.tar.gz: 19e4f9adb0f182af037966167deea8c92ab7a0bf99319b0ed608b56cbb68097a
5
5
  SHA512:
6
- metadata.gz: 55f03cdd94bb13026d4f8042375d38195b45bdbddb5cb0a0d45e49b14c45ac71c8f6a252e37f57e383d9c1bfc530830f24a5922c5e33dee854c8f7e060135bf7
7
- data.tar.gz: 380ef85943825041441e2122f46c3dcbfe31e9eac8222df89118033bf4f1133ec153e3395a7be450ee0d6feb7dabe6810a2c713fcf2d625769e2677cf1a7206e
6
+ metadata.gz: 03ec13eb0696da794383f0c0a3b62c7913ed0b012d20d36bc99f218fa4d2939bd736b96928dd6f843fc1bbf07e2d0142a142a0049240ca51228a5bc6ff33621f
7
+ data.tar.gz: 20929c52bc0cd0bfbc06cd6f0110ed3949efc50a34b7978b5c0433514fc7feea99b04a2ec404d58cce6037420748d83f0cc8b5f2eb73415750d46659a56a7c30
data/README.md CHANGED
@@ -1,84 +1,84 @@
1
- <img src="https://raw.githubusercontent.com/ErvalhouS/APIcasso/master/APIcasso.png" width="300" /> [![Gem Version](https://badge.fury.io/rb/apicasso.svg)](https://badge.fury.io/rb/apicasso) [![Docs Coverage](https://inch-ci.org/github/autoforce/APIcasso.svg?branch=master)](https://inch-ci.org/github/autoforce/APIcasso.svg?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/b58bbd6b9a0376f7cfc8/maintainability)](https://codeclimate.com/github/autoforce/APIcasso/maintainability) [![codecov](https://codecov.io/gh/autoforce/APIcasso/branch/master/graph/badge.svg)](https://codecov.io/gh/autoforce/APIcasso) [![Build Status](https://travis-ci.org/autoforce/APIcasso.svg?branch=master)](https://travis-ci.org/autoforce/APIcasso)
2
-
3
- JSON API development can get boring and time consuming. If you think it through, every time you make one you use almost the same route structure, pointing to the same controller actions, with the same ordering, filtering and pagination features.
4
-
5
- **APIcasso** is intended to be used as a full-fledged CRUD JSON API or as a base controller to speed-up development.
6
- It is a route-based resource abstraction using API key scoping. This makes it possible to make CRUD-only applications just by creating functional Rails' models. It is a perfect candidate for legacy Rails projects that do not have an API. Access to your application's resources is managed by a `.scope` JSON object per API key. It uses that permission scope to restrict and extend access.
7
-
8
- ## Installation
9
- Add this line to your application's `Gemfile`:
10
-
11
- ```ruby
12
- gem 'apicasso'
13
- ```
14
-
15
- And then execute this to generate the required migrations:
16
- ```bash
17
- $ rails g apicasso:install
18
- ```
19
- You will need to use a database with JSON fields support to use this gem.
20
-
21
- ## Usage
22
- After installing APIcasso into your application you can mount a full-fledged CRUD JSON API just by attaching into some route. Usually you will have it under a scoped route like `/api/v1` or a subdomain. You can do that by adding this into your `config/routes.rb`:
23
- ```ruby
24
- # To mount your APIcasso routes under the path scope `/api/v1`
25
- mount Apicasso::Engine, at: "/api/v1"
26
- # or, if you prefer subdomain scope isolation
27
- constraints subdomain: 'apiv1' do
28
- mount Apicasso::Engine, at: "/"
29
- end
30
- ```
31
- Your API will reflect very similarly a `resources :resource` statement with the following routes:
32
- ```ruby
33
- get '/:resource/' # Index action, listing a `:resource` collection from your application
34
- post '/:resource/' # Create action for one `:resource` from your application
35
- get '/:resource/:id' # Show action for one `:resource` from your application
36
- patch '/:resource/:id' # Update action for one `:resource` from your application
37
- delete '/:resource/:id' # Destroy action for one `:resource` from your application
38
- get '/:resource/:id/:nested/' # Index action, listing a collection of a `:nested` relation from one of your application's `:resource`
39
- options '/:resource/' # A schema dump for the required `:resource`
40
- options '/:resource/:id/:nested/' # A schema dump for the required `:nested` relation from one of your application's `:resource`
41
- ```
42
- This means all your application's models will be exposed as `:resource` and it's relations will be exposed as `:nested`. It will enable you to CRUD and get schema metadata from your records.
43
-
44
- > But this is permissive as hell! I do not want to expose my entire application like this, haven't you thought about security?
45
-
46
- *Sure!* The API is being exposed using authentication through `Authorization: Token` [HTTP header authentication](http://tools.ietf.org/html/draft-hammer-http-token-auth-01). The API key objects are manageable through the `Apicasso::Key` model, which gets setup at install. When a new key is created a `.token` is generated using an [Universally Unique Identifier(RFC 4122)](https://tools.ietf.org/html/rfc4122).
47
-
48
- Your API is then exposed based on each `Apicasso::Key.scope` definition
49
- ```ruby
50
- Apicasso::Key.create(scope:
51
- { manage:
52
- [{ order: true }, { user: { account_id: 1 } }],
53
- read:
54
- [{ account: { id: 1 } }]
55
- })
56
- ```
57
- This translates directly into which parts of your application is exposed to each APIcasso keys.
58
-
59
- The key from this example will have full access to all orders and to users with `account_id == 1`. It will have also read-only access to accounts with `id == 1`.
60
-
61
- This saves you the trouble of having to setup each and every controller for each model. And even if your application really need it, just make your controllers inherit from `Apicasso::CrudController` and extend it's functionalities. This authorization feature is why one of the dependencies for this gem is [CanCanCan](https://github.com/CanCanCommunity/cancancan), that abstracts the scope field into authorization for your application's resources.
62
-
63
- The `crud#index` and `crud#nested_index` actions are already equipped with pagination, ordering and filtering.
64
-
65
- - You can pass `params[:sort]` with field names preffixed with `+` or `-` to configure custom ordering per request. I.E.: `?sort=+updated_at,-name`
66
- - You can pass `params[:q]` using [ransack's search matchers](https://github.com/activerecord-hackery/ransack#search-matchers) to build a search query. I.E.: `?q[full_name_start]=Picasso`
67
- - You can pass `params[:page]` and `params[:per_page]` to build pagination options. I.E.: `?page=2&per_page=12`
68
-
69
- ## Contributing
70
- Bug reports and pull requests are welcome on GitHub at https://github.com/ErvalhouS/APIcasso. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant code of conduct](http://contributor-covenant.org/).
71
-
72
- ## License
73
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
74
-
75
- ## Code of conduct
76
- Everyone interacting in the APIcasso project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ErvalhouS/APIcasso/blob/master/CODE_OF_CONDUCT.md).
77
-
78
- ## TODO
79
-
80
- - Add gem options like: Token rotation, Alternative authentication methods
81
- - Response fields selecting
82
- - Rate limiting
83
- - Testing suite
84
- - Travis CI
1
+ <img src="https://raw.githubusercontent.com/ErvalhouS/APIcasso/master/APIcasso.png" width="300" /> [![Gem Version](https://badge.fury.io/rb/apicasso.svg)](https://badge.fury.io/rb/apicasso) [![Docs Coverage](https://inch-ci.org/github/autoforce/APIcasso.svg?branch=master)](https://inch-ci.org/github/autoforce/APIcasso.svg?branch=master) [![Maintainability](https://api.codeclimate.com/v1/badges/b58bbd6b9a0376f7cfc8/maintainability)](https://codeclimate.com/github/autoforce/APIcasso/maintainability) [![codecov](https://codecov.io/gh/autoforce/APIcasso/branch/master/graph/badge.svg)](https://codecov.io/gh/autoforce/APIcasso) [![Build Status](https://travis-ci.org/autoforce/APIcasso.svg?branch=master)](https://travis-ci.org/autoforce/APIcasso)
2
+
3
+ JSON API development can get boring and time consuming. If you think it through, every time you make one you use almost the same route structure, pointing to the same controller actions, with the same ordering, filtering and pagination features.
4
+
5
+ **APIcasso** is intended to be used as a full-fledged CRUD JSON API or as a base controller to speed-up development.
6
+ It is a route-based resource abstraction using API key scoping. This makes it possible to make CRUD-only applications just by creating functional Rails' models. It is a perfect candidate for legacy Rails projects that do not have an API. Access to your application's resources is managed by a `.scope` JSON object per API key. It uses that permission scope to restrict and extend access.
7
+
8
+ ## Installation
9
+ Add this line to your application's `Gemfile`:
10
+
11
+ ```ruby
12
+ gem 'apicasso'
13
+ ```
14
+
15
+ And then execute this to generate the required migrations:
16
+ ```bash
17
+ $ rails g apicasso:install
18
+ ```
19
+ You will need to use a database with JSON fields support to use this gem.
20
+
21
+ ## Usage
22
+ After installing APIcasso into your application you can mount a full-fledged CRUD JSON API just by attaching into some route. Usually you will have it under a scoped route like `/api/v1` or a subdomain. You can do that by adding this into your `config/routes.rb`:
23
+ ```ruby
24
+ # To mount your APIcasso routes under the path scope `/api/v1`
25
+ mount Apicasso::Engine, at: "/api/v1"
26
+ # or, if you prefer subdomain scope isolation
27
+ constraints subdomain: 'apiv1' do
28
+ mount Apicasso::Engine, at: "/"
29
+ end
30
+ ```
31
+ Your API will reflect very similarly a `resources :resource` statement with the following routes:
32
+ ```ruby
33
+ get '/:resource/' # Index action, listing a `:resource` collection from your application
34
+ post '/:resource/' # Create action for one `:resource` from your application
35
+ get '/:resource/:id' # Show action for one `:resource` from your application
36
+ patch '/:resource/:id' # Update action for one `:resource` from your application
37
+ delete '/:resource/:id' # Destroy action for one `:resource` from your application
38
+ get '/:resource/:id/:nested/' # Index action, listing a collection of a `:nested` relation from one of your application's `:resource`
39
+ options '/:resource/' # A schema dump for the required `:resource`
40
+ options '/:resource/:id/:nested/' # A schema dump for the required `:nested` relation from one of your application's `:resource`
41
+ ```
42
+ This means all your application's models will be exposed as `:resource` and it's relations will be exposed as `:nested`. It will enable you to CRUD and get schema metadata from your records.
43
+
44
+ > But this is permissive as hell! I do not want to expose my entire application like this, haven't you thought about security?
45
+
46
+ *Sure!* The API is being exposed using authentication through `Authorization: Token` [HTTP header authentication](http://tools.ietf.org/html/draft-hammer-http-token-auth-01). The API key objects are manageable through the `Apicasso::Key` model, which gets setup at install. When a new key is created a `.token` is generated using an [Universally Unique Identifier(RFC 4122)](https://tools.ietf.org/html/rfc4122).
47
+
48
+ Your API is then exposed based on each `Apicasso::Key.scope` definition
49
+ ```ruby
50
+ Apicasso::Key.create(scope:
51
+ { manage:
52
+ [{ order: true }, { user: { account_id: 1 } }],
53
+ read:
54
+ [{ account: { id: 1 } }]
55
+ })
56
+ ```
57
+ This translates directly into which parts of your application is exposed to each APIcasso keys.
58
+
59
+ The key from this example will have full access to all orders and to users with `account_id == 1`. It will have also read-only access to accounts with `id == 1`.
60
+
61
+ This saves you the trouble of having to setup each and every controller for each model. And even if your application really need it, just make your controllers inherit from `Apicasso::CrudController` and extend it's functionalities. This authorization feature is why one of the dependencies for this gem is [CanCanCan](https://github.com/CanCanCommunity/cancancan), that abstracts the scope field into authorization for your application's resources.
62
+
63
+ The `crud#index` and `crud#nested_index` actions are already equipped with pagination, ordering and filtering.
64
+
65
+ - You can pass `params[:sort]` with field names preffixed with `+` or `-` to configure custom ordering per request. I.E.: `?sort=+updated_at,-name`
66
+ - You can pass `params[:q]` using [ransack's search matchers](https://github.com/activerecord-hackery/ransack#search-matchers) to build a search query. I.E.: `?q[full_name_start]=Picasso`
67
+ - You can pass `params[:page]` and `params[:per_page]` to build pagination options. I.E.: `?page=2&per_page=12`
68
+
69
+ ## Contributing
70
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ErvalhouS/APIcasso. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant code of conduct](http://contributor-covenant.org/).
71
+
72
+ ## License
73
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
74
+
75
+ ## Code of conduct
76
+ Everyone interacting in the APIcasso project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ErvalhouS/APIcasso/blob/master/CODE_OF_CONDUCT.md).
77
+
78
+ ## TODO
79
+
80
+ - Add gem options like: Token rotation, Alternative authentication methods
81
+ - Response fields selecting
82
+ - Rate limiting
83
+ - Testing suite
84
+ - Travis CI
data/Rakefile CHANGED
@@ -1,32 +1,32 @@
1
- begin
2
- require 'bundler/setup'
3
- rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
- end
6
-
7
- require 'rdoc/task'
8
-
9
- RDoc::Task.new(:rdoc) do |rdoc|
10
- rdoc.rdoc_dir = 'rdoc'
11
- rdoc.title = 'Apicasso'
12
- rdoc.options << '--line-numbers'
13
- rdoc.rdoc_files.include('README.md')
14
- rdoc.rdoc_files.include('lib/**/*.rb')
15
- end
16
-
17
- APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
- load 'rails/tasks/engine.rake'
19
-
20
- load 'rails/tasks/statistics.rake'
21
-
22
- require 'bundler/gem_tasks'
23
-
24
- require 'rake/testtask'
25
-
26
- Rake::TestTask.new(:test) do |t|
27
- t.libs << 'test'
28
- t.pattern = 'test/**/*_test.rb'
29
- t.verbose = false
30
- end
31
-
32
- task default: :test
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Apicasso'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -1,2 +1,2 @@
1
- //= link_directory ../javascripts/apicasso .js
2
- //= link_directory ../stylesheets/apicasso .css
1
+ //= link_directory ../javascripts/apicasso .js
2
+ //= link_directory ../stylesheets/apicasso .css
@@ -1,15 +1,15 @@
1
- // This is a manifest file that'll be compiled into application.js, which will include all the files
2
- // listed below.
3
- //
4
- // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
- // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
- //
7
- // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
- // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
- //
10
- // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
- // about supported directives.
12
- //
13
- //= require rails-ujs
14
- //= require activestorage
15
- //= require_tree .
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require rails-ujs
14
+ //= require activestorage
15
+ //= require_tree .
@@ -1,15 +1,15 @@
1
- /*
2
- * This is a manifest file that'll be compiled into application.css, which will include all the files
3
- * listed below.
4
- *
5
- * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
- * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
- *
8
- * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
- * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
- * files in this directory. Styles in this file should be added after the last require_* statement.
11
- * It is generally better to create a new file per style scope.
12
- *
13
- *= require_tree .
14
- *= require_self
15
- */
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
File without changes
@@ -1,104 +1,104 @@
1
- # frozen_string_literal: true
2
-
3
- module Apicasso
4
- # Controller to extract common API features,
5
- # such as authentication and authorization
6
- class ApplicationController < ActionController::API
7
- include ActionController::HttpAuthentication::Token::ControllerMethods
8
- prepend_before_action :restrict_access
9
- after_action :register_api_request
10
-
11
- # Sets the authorization scope for the current API key
12
- def current_ability
13
- @current_ability ||= Apicasso::Ability.new(@api_key)
14
- end
15
-
16
- private
17
-
18
- # Identifies API key used in the request, avoiding unauthenticated access
19
- def restrict_access
20
- authenticate_or_request_with_http_token do |token, _options|
21
- @api_key = Apicasso::Key.find_by!(token: token)
22
- end
23
- end
24
-
25
- # Creates a request object in databse, registering the API key and
26
- # a hash of the request and the response
27
- def register_api_request
28
- Apicasso::Request.delay.create(api_key_id: @api_key.id,
29
- object: { request: request_hash,
30
- response: response_hash })
31
- end
32
-
33
- # Request data built as a hash.
34
- # Returns UUID, URL, HTTP Headers and origin IP
35
- def request_hash
36
- {
37
- uuid: request.uuid,
38
- url: request.original_url,
39
- headers: request.env.select { |key, _v| key =~ /^HTTP_/ },
40
- ip: request.remote_ip
41
- }
42
- end
43
-
44
- # Resonse data built as a hash.
45
- # Returns HTTP Status and request body
46
- def response_hash
47
- {
48
- status: response.status,
49
- body: JSON.parse(response.body)
50
- }
51
- end
52
-
53
- # Used to avoid errors parsing the search query,
54
- # which can be passed as a JSON or as a key-value param
55
- def parsed_query
56
- JSON.parse(params[:q])
57
- rescue JSON::ParserError, TypeError
58
- params[:q]
59
- end
60
-
61
- # Used to avoid errors in included associations parsing
62
- def parsed_include
63
- params[:include].split(',')
64
- rescue NoMethodError
65
- []
66
- end
67
-
68
- # Receives a `.paginate`d collection and returns the pagination
69
- # metadata to be merged into response
70
- def pagination_metadata_for(records)
71
- { total: records.total_entries,
72
- total_pages: records.total_pages,
73
- last_page: records.next_page.blank?,
74
- previous_page: previous_link_for(records),
75
- next_page: next_link_for(records),
76
- out_of_bounds: records.out_of_bounds?,
77
- offset: records.offset }
78
- end
79
-
80
- # Generates a contextualized URL of the next page for this request
81
- def next_link_for(records)
82
- uri = URI.parse(request.original_url)
83
- query = Rack::Utils.parse_query(uri.query)
84
- query['page'] = records.next_page
85
- uri.query = Rack::Utils.build_query(query)
86
- uri.to_s
87
- end
88
-
89
- # Generates a contextualized URL of the previous page for this request
90
- def previous_link_for(records)
91
- uri = URI.parse(request.original_url)
92
- query = Rack::Utils.parse_query(uri.query)
93
- query['page'] = records.previous_page
94
- uri.query = Rack::Utils.build_query(query)
95
- uri.to_s
96
- end
97
-
98
- # Receives a `:action, :resource, :object` hash to validate authorization
99
- def authorize_for(opts = {})
100
- authorize! opts[:action], opts[:resource] if opts[:resource].present?
101
- authorize! opts[:action], opts[:object] if opts[:object].present?
102
- end
103
- end
104
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Apicasso
4
+ # Controller to extract common API features,
5
+ # such as authentication and authorization
6
+ class ApplicationController < ActionController::API
7
+ include ActionController::HttpAuthentication::Token::ControllerMethods
8
+ prepend_before_action :restrict_access
9
+ after_action :register_api_request
10
+
11
+ # Sets the authorization scope for the current API key
12
+ def current_ability
13
+ @current_ability ||= Apicasso::Ability.new(@api_key)
14
+ end
15
+
16
+ private
17
+
18
+ # Identifies API key used in the request, avoiding unauthenticated access
19
+ def restrict_access
20
+ authenticate_or_request_with_http_token do |token, _options|
21
+ @api_key = Apicasso::Key.find_by!(token: token)
22
+ end
23
+ end
24
+
25
+ # Creates a request object in databse, registering the API key and
26
+ # a hash of the request and the response
27
+ def register_api_request
28
+ Apicasso::Request.delay.create(api_key_id: @api_key.id,
29
+ object: { request: request_hash,
30
+ response: response_hash })
31
+ end
32
+
33
+ # Request data built as a hash.
34
+ # Returns UUID, URL, HTTP Headers and origin IP
35
+ def request_hash
36
+ {
37
+ uuid: request.uuid,
38
+ url: request.original_url,
39
+ headers: request.env.select { |key, _v| key =~ /^HTTP_/ },
40
+ ip: request.remote_ip
41
+ }
42
+ end
43
+
44
+ # Resonse data built as a hash.
45
+ # Returns HTTP Status and request body
46
+ def response_hash
47
+ {
48
+ status: response.status,
49
+ body: JSON.parse(response.body)
50
+ }
51
+ end
52
+
53
+ # Used to avoid errors parsing the search query,
54
+ # which can be passed as a JSON or as a key-value param
55
+ def parsed_query
56
+ JSON.parse(params[:q])
57
+ rescue JSON::ParserError, TypeError
58
+ params[:q]
59
+ end
60
+
61
+ # Used to avoid errors in included associations parsing
62
+ def parsed_include
63
+ params[:include].split(',')
64
+ rescue NoMethodError
65
+ []
66
+ end
67
+
68
+ # Receives a `.paginate`d collection and returns the pagination
69
+ # metadata to be merged into response
70
+ def pagination_metadata_for(records)
71
+ { total: records.total_entries,
72
+ total_pages: records.total_pages,
73
+ last_page: records.next_page.blank?,
74
+ previous_page: previous_link_for(records),
75
+ next_page: next_link_for(records),
76
+ out_of_bounds: records.out_of_bounds?,
77
+ offset: records.offset }
78
+ end
79
+
80
+ # Generates a contextualized URL of the next page for this request
81
+ def next_link_for(records)
82
+ uri = URI.parse(request.original_url)
83
+ query = Rack::Utils.parse_query(uri.query)
84
+ query['page'] = records.next_page
85
+ uri.query = Rack::Utils.build_query(query)
86
+ uri.to_s
87
+ end
88
+
89
+ # Generates a contextualized URL of the previous page for this request
90
+ def previous_link_for(records)
91
+ uri = URI.parse(request.original_url)
92
+ query = Rack::Utils.parse_query(uri.query)
93
+ query['page'] = records.previous_page
94
+ uri.query = Rack::Utils.build_query(query)
95
+ uri.to_s
96
+ end
97
+
98
+ # Receives a `:action, :resource, :object` hash to validate authorization
99
+ def authorize_for(opts = {})
100
+ authorize! opts[:action], opts[:resource] if opts[:resource].present?
101
+ authorize! opts[:action], opts[:object] if opts[:object].present?
102
+ end
103
+ end
104
+ end