apicasso 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 918cf0c81232e0e147d5bf174b7759e189cc82933d8d7341b64e6765bcd5af57
4
+ data.tar.gz: 929566ab3bfde9f9980263a2304072be2a6d21e5e82198e89e43ecfecfc7c588
5
+ SHA512:
6
+ metadata.gz: bdfae676999589bce579a57c07adb0d58ecf2e1a18c94a4f754cd8428b3708d92d745a521ebc419c739ae1e08e30c36c93c6013c84afac4056252d770ebc0671
7
+ data.tar.gz: 6a7fb2af0119e03ae6ba657c713d7dc03b0e4331475b582d826f28486f40c33f4a22d17584ce7a5ccfef37e90fbabdd6db0c9f42cb4e42e631765cb53de5b911
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ <img src="https://raw.githubusercontent.com/ErvalhouS/APIcasso/master/APIcasso.png" width="300" />
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 ADDED
@@ -0,0 +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
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/apicasso .js
2
+ //= link_directory ../stylesheets/apicasso .css
@@ -0,0 +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 .
@@ -0,0 +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
+ */
@@ -0,0 +1,472 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apicasso
4
+ # Controller used to generate an application Swagger JSON, used by
5
+ # SwaggerUI to generate beautiful API documentation
6
+ class ApidocsController < ActionController::API
7
+ include Swagger::Blocks
8
+
9
+ swagger_root do
10
+ key :swagger, '2.0'
11
+ info do
12
+ key :version, ENV.fetch('VERSION', I18n.t('application.version'))
13
+ key :title, ENV.fetch('APP_NAME', I18n.t('application.name'))
14
+ key :description, ENV.fetch('APP_DESCRIPTION', I18n.t('application.description'))
15
+ key :termsOfService, I18n.t('application.terms_of_service')
16
+ contact do
17
+ key :name, I18n.t('application.contact_name')
18
+ end
19
+ license do
20
+ key :name, I18n.t('application.license')
21
+ end
22
+ end
23
+ ActiveRecord::Base.descendants.each do |model|
24
+ tag do
25
+ key :name, I18n.t("activerecord.models.#{model.name.underscore}", default: model.name.underscore)
26
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.description", default: model.name)
27
+ end
28
+ end
29
+ key :host, I18n.t('application.apicasso_host', default: ENV.fetch('ROOT', 'localhost:3000'))
30
+ key :basePath, I18n.t('application.apicasso_path', default: '/')
31
+ key :consumes, ['application/json']
32
+ key :produces, ['application/json']
33
+ end
34
+
35
+ # Eager load application to be able to list all models
36
+ Rails.application.eager_load! unless Rails.configuration.cache_classes
37
+ # A list of all classes that have swagger_* declarations, which gets injected
38
+ # by this gem in all `ActiveRecord::Base` classes
39
+ SWAGGERED_CLASSES = [
40
+ *ActiveRecord::Base.descendants,
41
+ self
42
+ ].freeze
43
+
44
+ swagger_schema :ErrorModel do
45
+ key :required, [:code, :message]
46
+ property :code do
47
+ key :type, :integer
48
+ key :format, :int32
49
+ end
50
+ property :message do
51
+ key :type, :string
52
+ end
53
+ end
54
+
55
+ ActiveRecord::Base.descendants do |model|
56
+ swagger_path "/#{model.name.underscore}" do
57
+ operation :get do
58
+ key :summary, I18n.t("activerecord.models.#{model.name.underscore}.index.summary", default: model.name)
59
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.index.description", default: model.name)
60
+ key :operationId, "find#{model.name.pluralize}"
61
+ key :produces, ['application/json']
62
+ key :tags, [model.name.underscore]
63
+ parameter do
64
+ key :name, :sort
65
+ key :in, :query
66
+ key :description, I18n.t('apicasso.sort.description',
67
+ default: 'Parameters sorting splitted by `,` preffixed by `+` or `-` which translates into ascending or descending order')
68
+ key :required, false
69
+ key :type, :string
70
+ key :collectionFormat, :json
71
+ end
72
+ parameter do
73
+ key :name, :q
74
+ key :in, :query
75
+ key :description, I18n.t('apicasso.q.description',
76
+ default: 'Records filtering by attribute and search query as affix. Usage: `?q[{attribute}{search_affix}]={matcher}`. All available search affixes are listed on: https://github.com/activerecord-hackery/ransack#search-matchers')
77
+ key :required, false
78
+ key :type, :json
79
+ end
80
+ parameter do
81
+ key :name, :page
82
+ key :in, :query
83
+ key :description, I18n.t('apicasso.page.description',
84
+ default: 'Records pagination paging, which offsets collection based on `params[:per_page]`')
85
+ key :required, false
86
+ key :type, :integer
87
+ end
88
+ parameter do
89
+ key :name, :per_page
90
+ key :in, :query
91
+ key :description, I18n.t('apicasso.per_page.description',
92
+ default: 'Records pagination size, which sets how many records will be rendered per request')
93
+ key :required, false
94
+ key :type, :integer
95
+ end
96
+ response 200 do
97
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.index.response",
98
+ default: "#{model.name} response, which include records matching current query and pagination metadata")
99
+ parameter do
100
+ key :name, :total
101
+ key :description, I18n.t('apicasso.total.description',
102
+ default: 'Total records contained in current collection, as if there was no pagination.')
103
+ key :required, true
104
+ key :type, :integer
105
+ end
106
+ parameter do
107
+ key :name, :total_pages
108
+ key :description, I18n.t('apicasso.total_pages.description',
109
+ default: 'How many pages of data the current collection has.')
110
+ key :required, true
111
+ key :type, :integer
112
+ end
113
+ parameter do
114
+ key :name, :last_page
115
+ key :description, I18n.t('apicasso.last_page.description',
116
+ default: 'An indication if current request is the last to paginate in the current collection')
117
+ key :required, true
118
+ key :type, :boolean
119
+ end
120
+ parameter do
121
+ key :name, :previous_page
122
+ key :description, I18n.t('apicasso.previous_page.description',
123
+ default: "The link of the previous page for the current collection. It can be null if there isn't any")
124
+ key :required, false
125
+ key :type, :string
126
+ end
127
+ parameter do
128
+ key :name, :next_page
129
+ key :description, I18n.t('apicasso.next_page.description',
130
+ default: "The link of the next page for the current collection. It can be null if there isn't any")
131
+ key :required, false
132
+ key :type, :string
133
+ end
134
+ parameter do
135
+ key :name, :out_of_bounds
136
+ key :description, I18n.t('apicasso.out_of_bounds.description',
137
+ default: 'An indication if current request is out of pagination bounds for the current collection')
138
+ key :required, true
139
+ key :type, :boolean
140
+ end
141
+ parameter do
142
+ key :name, :offset
143
+ key :description, I18n.t('apicasso.offset.description',
144
+ default: 'How many records were offsetted from the collection to render the current page')
145
+ key :required, true
146
+ key :type, :integer
147
+ end
148
+ parameter do
149
+ key :name, :entries
150
+ key :description, I18n.t('apicasso.entries.description',
151
+ default: 'The records collection in the current pagination scope.')
152
+ key :required, true
153
+ key :type, :array
154
+ items do
155
+ key :'$ref', model.name.to_sym
156
+ end
157
+ end
158
+ end
159
+ response :default do
160
+ key :description, I18n.t("activerecord.errors.models.#{model.name.underscore}",
161
+ default: "Unexpected error in #{model.name}")
162
+ schema do
163
+ key :'$ref', :ErrorModel
164
+ end
165
+ end
166
+ end
167
+ operation :options do
168
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.schema.description",
169
+ default: "#{model.name} metadata information.")
170
+ key :operationId, "schema#{model.name.pluralize}"
171
+ key :produces, ['application/json']
172
+ key :tags, [model.name.underscore]
173
+ response 200 do
174
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.schema.response",
175
+ default: "#{model.name} metadata as a json with field names as keys and field types as values.")
176
+ schema do
177
+ key :'$ref', "#{model.name}Metadata".to_sym
178
+ end
179
+ end
180
+ response :default do
181
+ key :description, I18n.t("activerecord.errors.models.#{model.name.underscore}",
182
+ default: "Unexpected error in #{model.name}")
183
+ schema do
184
+ key :'$ref', :ErrorModel
185
+ end
186
+ end
187
+ end
188
+ operation :post do
189
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.create.response",
190
+ default: "Creates a #{model.name}")
191
+ key :operationId, "add#{model.name}"
192
+ key :produces, ['application/json']
193
+ key :tags, [model.name.underscore]
194
+ parameter do
195
+ key :name, model.name.underscore.to_sym
196
+ key :in, :body
197
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.create.description",
198
+ default: "#{model.name} to add into application")
199
+ key :required, true
200
+ schema do
201
+ key :'$ref', "#{model.name}Input".to_sym
202
+ end
203
+ end
204
+ response 201 do
205
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.show.description",
206
+ default: "#{model.name} response")
207
+ schema do
208
+ key :'$ref', model.name.to_sym
209
+ end
210
+ end
211
+ response :default do
212
+ key :description, I18n.t("activerecord.errors.models.#{model.name.underscore}",
213
+ default: "Unexpected error in #{model.name}")
214
+ schema do
215
+ key :'$ref', :ErrorModel
216
+ end
217
+ end
218
+ end
219
+ end
220
+ swagger_path "/#{model.name.underscore}/{id}" do
221
+ operation :patch do
222
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.update.response",
223
+ default: "Updates a #{model.name}")
224
+ key :operationId, "edit#{model.name}"
225
+ key :produces, ['application/json']
226
+ key :tags, [model.name.underscore]
227
+ parameter do
228
+ key :name, :id
229
+ key :in, :path
230
+ key :description, I18n.t("activerecord.models.attributes.#{model.name.underscore}.id",
231
+ default: "ID of #{model.name} to update on the application")
232
+ key :required, true
233
+ schema do
234
+ key :'$ref', "#{model.name}Input".to_sym
235
+ end
236
+ end
237
+ parameter do
238
+ key :name, model.name.underscore.to_sym
239
+ key :in, :body
240
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.update.description",
241
+ default: "Existing #{model.name} to update on the application")
242
+ key :required, true
243
+ schema do
244
+ key :'$ref', "#{model.name}Input".to_sym
245
+ end
246
+ end
247
+ response 200 do
248
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.show.description",
249
+ default: "#{model.name} response")
250
+ schema do
251
+ key :'$ref', model.name.to_sym
252
+ end
253
+ end
254
+ end
255
+ operation :get do
256
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.show.response",
257
+ default: "Creates a #{model.name}")
258
+ key :operationId, "show#{model.name}"
259
+ key :produces, ['application/json']
260
+ key :tags, [model.name.underscore]
261
+ parameter do
262
+ key :name, :id
263
+ key :in, :path
264
+ key :description, I18n.t("activerecord.models.attributes.#{model.name.underscore}.id",
265
+ default: "ID of #{model.name} to fetch on the application")
266
+ key :required, true
267
+ schema do
268
+ key :'$ref', "#{model.name}Input".to_sym
269
+ end
270
+ end
271
+ response 200 do
272
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.show.description",
273
+ default: "#{model.name} response")
274
+ schema do
275
+ key :'$ref', model.name.to_sym
276
+ end
277
+ end
278
+ response :default do
279
+ key :description, I18n.t("activerecord.errors.models.#{model.name.underscore}",
280
+ default: "Unexpected error in #{model.name}")
281
+ schema do
282
+ key :'$ref', :ErrorModel
283
+ end
284
+ end
285
+ end
286
+ operation :delete do
287
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.destroy.response",
288
+ default: "Deletes a #{model.name}")
289
+ key :operationId, "destroy#{model.name}"
290
+ key :produces, ['application/json']
291
+ key :tags, [model.name.underscore]
292
+ parameter do
293
+ key :name, :id
294
+ key :in, :path
295
+ key :description, I18n.t("activerecord.models.attributes.#{model.name.underscore}.id",
296
+ default: "ID of #{model.name} to delete on the application")
297
+ key :required, true
298
+ schema do
299
+ key :'$ref', "#{model.name}Input".to_sym
300
+ end
301
+ end
302
+ response 200 do
303
+ key :description, I18n.t("activerecord.models.#{model.name.underscore}.destroy.description",
304
+ default: "#{model.name} response")
305
+ end
306
+ response :default do
307
+ key :description, I18n.t("activerecord.errors.models.#{model.name.underscore}",
308
+ default: "Unexpected error in #{model.name}")
309
+ schema do
310
+ key :'$ref', :ErrorModel
311
+ end
312
+ end
313
+ end
314
+ end
315
+
316
+ model.reflect_on_all_associations.map(&:name) do |association|
317
+ inner_name = extract_klass_name(model: model, association: association)
318
+ inner_klass = extract_klass(model: model, association: association)
319
+ swagger_path "/#{model.name.underscore}/{id}/#{association}" do
320
+ operation :get do
321
+ key :summary, I18n.t("activerecord.models.#{inner_name.underscore}.index.summary", default: inner_name)
322
+ key :description, I18n.t("activerecord.models.#{inner_name.underscore}.index.description", default: inner_name)
323
+ key :operationId, "find#{inner_name.pluralize}"
324
+ key :produces, ['application/json']
325
+ key :tags, [inner_name.underscore]
326
+ parameter do
327
+ key :name, :sort
328
+ key :in, :query
329
+ key :description, I18n.t('apicasso.sort.description',
330
+ default: 'Parameters sorting splitted by `,` preffixed by `+` or `-` which translates into ascending or descending order')
331
+ key :required, false
332
+ key :type, :string
333
+ key :collectionFormat, :json
334
+ end
335
+ parameter do
336
+ key :name, :q
337
+ key :in, :query
338
+ key :description, I18n.t('apicasso.q.description',
339
+ default: 'Records filtering by attribute and search query as affix. Usage: `?q[{attribute}{search_affix}]={matcher}`. All available search affixes are listed on: https://github.com/activerecord-hackery/ransack#search-matchers')
340
+ key :required, false
341
+ key :type, :json
342
+ end
343
+ parameter do
344
+ key :name, :page
345
+ key :in, :query
346
+ key :description, I18n.t('apicasso.page.description',
347
+ default: 'Records pagination paging, which offsets collection based on `params[:per_page]`')
348
+ key :required, false
349
+ key :type, :integer
350
+ end
351
+ parameter do
352
+ key :name, :per_page
353
+ key :in, :query
354
+ key :description, I18n.t('apicasso.per_page.description',
355
+ default: 'Records pagination size, which sets how many records will be rendered per request')
356
+ key :required, false
357
+ key :type, :integer
358
+ end
359
+ response 200 do
360
+ key :description, I18n.t("activerecord.models.#{inner_name.underscore}.index.response",
361
+ default: "#{inner_name} response, which include records matching current query and pagination metadata")
362
+ parameter do
363
+ key :name, :total
364
+ key :description, I18n.t('apicasso.total.description',
365
+ default: 'Total records contained in current collection, as if there was no pagination.')
366
+ key :required, true
367
+ key :type, :integer
368
+ end
369
+ parameter do
370
+ key :name, :total_pages
371
+ key :description, I18n.t('apicasso.total_pages.description',
372
+ default: 'How many pages of data the current collection has.')
373
+ key :required, true
374
+ key :type, :integer
375
+ end
376
+ parameter do
377
+ key :name, :last_page
378
+ key :description, I18n.t('apicasso.last_page.description',
379
+ default: 'An indication if current request is the last to paginate in the current collection')
380
+ key :required, true
381
+ key :type, :boolean
382
+ end
383
+ parameter do
384
+ key :name, :previous_page
385
+ key :description, I18n.t('apicasso.previous_page.description',
386
+ default: "The link of the previous page for the current collection. It can be null if there isn't any")
387
+ key :required, false
388
+ key :type, :string
389
+ end
390
+ parameter do
391
+ key :name, :next_page
392
+ key :description, I18n.t('apicasso.next_page.description',
393
+ default: "The link of the next page for the current collection. It can be null if there isn't any")
394
+ key :required, false
395
+ key :type, :string
396
+ end
397
+ parameter do
398
+ key :name, :out_of_bounds
399
+ key :description, I18n.t('apicasso.out_of_bounds.description',
400
+ default: 'An indication if current request is out of pagination bounds for the current collection')
401
+ key :required, true
402
+ key :type, :boolean
403
+ end
404
+ parameter do
405
+ key :name, :offset
406
+ key :description, I18n.t('apicasso.offset.description',
407
+ default: 'How many records were offsetted from the collection to render the current page')
408
+ key :required, true
409
+ key :type, :integer
410
+ end
411
+ parameter do
412
+ key :name, :entries
413
+ key :description, I18n.t('apicasso.entries.description',
414
+ default: 'The records collection in the current pagination scope.')
415
+ key :required, true
416
+ key :type, :array
417
+ items do
418
+ key :'$ref', inner_name.to_sym
419
+ end
420
+ end
421
+ end
422
+ response :default do
423
+ key :description, I18n.t("activerecord.errors.models.#{inner_name.underscore}",
424
+ default: "Unexpected error in #{inner_name}")
425
+ schema do
426
+ key :'$ref', :ErrorModel
427
+ end
428
+ end
429
+ end
430
+ operation :options do
431
+ key :description, I18n.t("activerecord.models.#{inner_name.underscore}.schema.description",
432
+ default: "#{inner_name} metadata information.")
433
+ key :operationId, "schema#{inner_name.pluralize}"
434
+ key :produces, ['application/json']
435
+ key :tags, [inner_name.underscore]
436
+ response 200 do
437
+ key :description, I18n.t("activerecord.models.#{inner_name.underscore}.schema.response",
438
+ default: "#{inner_name} metadata as a json with field names as keys and field types as values.")
439
+ schema do
440
+ key :'$ref', "#{inner_name}Metadata".to_sym
441
+ end
442
+ end
443
+ response :default do
444
+ key :description, I18n.t("activerecord.errors.models.#{inner_name.underscore}",
445
+ default: "Unexpected error in #{inner_name}")
446
+ schema do
447
+ key :'$ref', :ErrorModel
448
+ end
449
+ end
450
+ end
451
+ end
452
+ end
453
+ end
454
+
455
+ def index
456
+ render json: Swagger::Blocks.build_root_json(SWAGGERED_CLASSES)
457
+ end
458
+
459
+ private
460
+
461
+ def extract_klass_name(opts = {})
462
+ association = opts[:model].reflect_on_all_associations.select{ |ass| ass.name == opts[:association] }.first
463
+ association.class_name
464
+ end
465
+
466
+ def extract_klass(opts = {})
467
+ extract_klass_name(opts).constantize
468
+ rescue NameError
469
+ return false
470
+ end
471
+ end
472
+ end