api_helper 0.0.1
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 +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/README.md +94 -0
- data/Rakefile +6 -0
- data/api_helper.gemspec +26 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/api_helper/fieldsettable.rb +171 -0
- data/lib/api_helper/filterable.rb +128 -0
- data/lib/api_helper/includable.rb +207 -0
- data/lib/api_helper/multigettable.rb +82 -0
- data/lib/api_helper/paginatable.rb +140 -0
- data/lib/api_helper/sortable.rb +89 -0
- data/lib/api_helper/version.rb +3 -0
- data/lib/api_helper.rb +10 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 991d12decd2615a223eebef7da4c5f132c47dfe6
|
4
|
+
data.tar.gz: 9bafcbeafb95c36abf2f5b091cec2b995b47a43e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78f5748409ee50cb19af1c70f9e73a35fb82dd72caff6d3b3009394ed7208c092525805bd6b41cd476693dd48243ccaca9bd4f6c003889cbf6614318dae16799
|
7
|
+
data.tar.gz: 11e33204b137a30a922d8c57d603d517c176f3bdce2f6cb3420fea31de524fb151c1348c55a2df32d330016eab88879b6e3eb3a4f2e7e776048b7de00bfd3603
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# APIHelper
|
2
|
+
|
3
|
+
Helpers for creating standard RESTful API for Rails or Grape with Active Record.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'APIHelper'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install APIHelper
|
21
|
+
|
22
|
+
|
23
|
+
## API Standards
|
24
|
+
|
25
|
+
<dl>
|
26
|
+
|
27
|
+
<dt><a href="http://www.rubydoc.info/github/Neson/api_helper/master/APIHelper/Fieldsettable" target="_blank">Fieldsettable</a></dt>
|
28
|
+
<dd>Let clients choose the fields they wanted to be returned with the "fields" query parameter, making their API calls optimizable to gain efficiency and speed.</dd>
|
29
|
+
|
30
|
+
<dt><a href="http://www.rubydoc.info/github/Neson/api_helper/master/APIHelper/Includable" target="_blank">Includable</a></dt>
|
31
|
+
<dd>Clients can use the "include" query parameter to enable inclusion of related items - for instance, get the author's data along with a post.</dd>
|
32
|
+
|
33
|
+
<dt><a href="http://www.rubydoc.info/github/Neson/api_helper/master/APIHelper/Paginatable" target="_blank">Paginatable</a></dt>
|
34
|
+
<dd>Paginate the results of a resource collection, client can get a specific page with the "page" query parameter and set a custom page size with the "per_page" query parameter.</dd>
|
35
|
+
|
36
|
+
<dt><a href="http://www.rubydoc.info/github/Neson/api_helper/master/APIHelper/Sortable" target="_blank">Sortable</a></dt>
|
37
|
+
<dd>Client can set custom sorting with the "sort" query parameter while getting a resource collection.</dd>
|
38
|
+
|
39
|
+
<dt><a href="http://www.rubydoc.info/github/Neson/api_helper/master/APIHelper/Filterable" target="_blank">Filterable</a></dt>
|
40
|
+
<dd>Enables clients to filter through a resource collection with their fields.</dd>
|
41
|
+
|
42
|
+
<dt><a href="http://www.rubydoc.info/github/Neson/api_helper/master/APIHelper/Multigettable" target="_blank">Multigettable</a></dt>
|
43
|
+
<dd>Let Client execute operations on multiple resources with a single request.</dd>
|
44
|
+
|
45
|
+
</dl>
|
46
|
+
|
47
|
+
|
48
|
+
## Usage
|
49
|
+
|
50
|
+
### Ruby on Rails (Action Pack)
|
51
|
+
|
52
|
+
Include each helper concern you need in an `ActionController::Base`:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
PostsController < ApplicationController
|
56
|
+
include APIHelpers::Filterable
|
57
|
+
include APIHelpers::Paginatable
|
58
|
+
include APIHelpers::Sortable
|
59
|
+
|
60
|
+
# ...
|
61
|
+
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Further usage of each helper can be found in the docs.
|
66
|
+
|
67
|
+
### Grape
|
68
|
+
|
69
|
+
Set the helpers you need in an `Grape::API`:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
class PostsAPI < Grape::API
|
73
|
+
helpers APIHelpers::Filterable
|
74
|
+
helpers APIHelpers::Paginatable
|
75
|
+
helpers APIHelpers::Sortable
|
76
|
+
|
77
|
+
# ...
|
78
|
+
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Further usage of each helper can be found in the docs.
|
83
|
+
|
84
|
+
|
85
|
+
## Development
|
86
|
+
|
87
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
88
|
+
|
89
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
90
|
+
|
91
|
+
|
92
|
+
## Contributing
|
93
|
+
|
94
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Neson/APIHelper.
|
data/Rakefile
ADDED
data/api_helper.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'api_helper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "api_helper"
|
8
|
+
spec.version = APIHelper::VERSION
|
9
|
+
spec.authors = ["Neson"]
|
10
|
+
spec.email = ["neson@dex.tw"]
|
11
|
+
|
12
|
+
spec.summary = %q{Helpers for creating standard web API.}
|
13
|
+
spec.description = %q{Helpers for creating standard web API for Rails or Grape with ActiveRecord.}
|
14
|
+
spec.homepage = "https://github.com/Neson/APIHelper"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
|
25
|
+
spec.add_development_dependency "activesupport", ">= 3"
|
26
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "api_helper"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# = Helper To Make Resource APIs Fieldsettable
|
4
|
+
#
|
5
|
+
# By making an API fieldsettable, you let API callers to choose the fields they
|
6
|
+
# wanted to be returned with query parameters. This is really useful for making
|
7
|
+
# API calls more efficient and fast.
|
8
|
+
#
|
9
|
+
# This design made references to the rules of <em>Sparse Fieldsets</em> in
|
10
|
+
# <em>JSON API</em>:
|
11
|
+
# http://jsonapi.org/format/#fetching-sparse-fieldsets
|
12
|
+
#
|
13
|
+
# A client can request that an API endpoint return only specific fields in the
|
14
|
+
# response by including a +fields+ parameter, which is a comma-separated (",")
|
15
|
+
# list that refers to the name(s) of the fields to be returned.
|
16
|
+
#
|
17
|
+
# GET /users?fields=id,name,avatar_url
|
18
|
+
#
|
19
|
+
# This functionality may also support requests specifying multiple fieldsets
|
20
|
+
# for several objects at a time (e.g. another object included in an field of
|
21
|
+
# another object) with <tt>fields[object_type]</tt> parameters.
|
22
|
+
#
|
23
|
+
# GET /posts?fields[posts]=id,title,author&fields[user]=id,name,avatar_url
|
24
|
+
#
|
25
|
+
# Note: +author+ of a +post+ is a +user+.
|
26
|
+
#
|
27
|
+
# The +fields+ and <tt>fields[object_type]</tt> parameters can not be mixed.
|
28
|
+
# If the latter format is used, then it must be used for the main object as well.
|
29
|
+
#
|
30
|
+
# == Usage
|
31
|
+
#
|
32
|
+
# Include this +Concern+ in your Action Controller:
|
33
|
+
#
|
34
|
+
# SamplesController < ApplicationController
|
35
|
+
# include APIHelper::Fieldsettable
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# or in your Grape API class:
|
39
|
+
#
|
40
|
+
# class SampleAPI < Grape::API
|
41
|
+
# include APIHelper::Fieldsettable
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# then set the options for the fieldset in the grape method:
|
45
|
+
#
|
46
|
+
# resources :posts do
|
47
|
+
# get do
|
48
|
+
# fieldset_for :post, root: true, default_fields: [:id, :title, :author]
|
49
|
+
# fieldset_for :user, permitted_fields: [:id, :name, :posts, :avatar_url],
|
50
|
+
# show_all_permitted_fields_by_default: true
|
51
|
+
# # ...
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# This helper parses the +fields+ and <tt>fields[object_type]</tt> parameters to
|
56
|
+
# determine what the API caller wants, and save the results into instance
|
57
|
+
# variables for further usage.
|
58
|
+
#
|
59
|
+
# After this you can use the +fieldset+ helper method to get the fieldset data
|
60
|
+
# that the request specifies.
|
61
|
+
#
|
62
|
+
# With <tt>GET /posts?fields=title,author</tt>:
|
63
|
+
#
|
64
|
+
# fieldset #=> { post: [:title, :author], user: [:id, :name, :posts, :avatar_url] }
|
65
|
+
#
|
66
|
+
# With <tt>GET /posts?fields[post]=title,author&fields[user]=name</tt>:
|
67
|
+
#
|
68
|
+
# fieldset #=> { post: [:title, :author], user: [:name] }
|
69
|
+
# fieldset(:post) #=> [:title, :author]
|
70
|
+
# fieldset(:post, :title) #=> true
|
71
|
+
# fieldset(:user, :avatar_url) #=> false
|
72
|
+
#
|
73
|
+
# You can make use of the information while dealing with requests, for example:
|
74
|
+
#
|
75
|
+
# Post.select(fieldset(:post))...
|
76
|
+
#
|
77
|
+
# If you're using RABL as the API view, it can be also setup like this:
|
78
|
+
#
|
79
|
+
# object @user
|
80
|
+
#
|
81
|
+
# # this ensures the +fieldset+ instance variable is least setted with
|
82
|
+
# # the default fields, and double check +permitted_fields+ at view layer -
|
83
|
+
# # in case of things going wrong in the controller
|
84
|
+
# set_fieldset :user, default_fields: [:id, :name, :avatar_url],
|
85
|
+
# permitted_fields: [:id, :name, :avatar_url, :posts]
|
86
|
+
#
|
87
|
+
# # determine the fields to show on the fly
|
88
|
+
# attributes(*fieldset[:user])
|
89
|
+
module APIHelper::Fieldsettable
|
90
|
+
extend ActiveSupport::Concern
|
91
|
+
|
92
|
+
# Gets the fields parameters, organize them into a +@fieldset+ hash for model to select certain
|
93
|
+
# fields and/or templates to render specified fieldset. Following the URL rules of JSON API:
|
94
|
+
# http://jsonapi.org/format/#fetching-sparse-fieldsets
|
95
|
+
#
|
96
|
+
# Params:
|
97
|
+
#
|
98
|
+
# +resource+::
|
99
|
+
# +Symbol+ name of resource to receive the fieldset
|
100
|
+
#
|
101
|
+
# +root+::
|
102
|
+
# +Boolean+ should this resource take the parameter from +fields+ while no type is specified
|
103
|
+
#
|
104
|
+
# +permitted_fields+::
|
105
|
+
# +Array+ of +Symbol+s list of accessible fields used to filter out unpermitted fields,
|
106
|
+
# defaults to permit all
|
107
|
+
#
|
108
|
+
# +default_fields+::
|
109
|
+
# +Array+ of +Symbol+s list of fields to show by default
|
110
|
+
#
|
111
|
+
# +show_all_permitted_fields_by_default+::
|
112
|
+
# +Boolean+ if set to true, @fieldset will be set to all permitted_fields when the current
|
113
|
+
# resource's fieldset isn't specified
|
114
|
+
#
|
115
|
+
# Example Result:
|
116
|
+
#
|
117
|
+
# fieldset_for :user, root: true
|
118
|
+
# fieldset_for :group
|
119
|
+
#
|
120
|
+
# # @fieldset => {
|
121
|
+
# # :user => [:id, :name, :email, :groups],
|
122
|
+
# # :group => [:id, :name]
|
123
|
+
# # }
|
124
|
+
def fieldset_for(resource, root: false, permitted_fields: [], show_all_permitted_fields_by_default: false, default_fields: [])
|
125
|
+
@fieldset ||= Hashie::Mash.new
|
126
|
+
@meta ||= Hashie::Mash.new
|
127
|
+
|
128
|
+
# put the fields in place
|
129
|
+
if params[:fields].is_a? Hash
|
130
|
+
@fieldset[resource] = params[:fields][resource] || params[:fields][resource]
|
131
|
+
elsif root
|
132
|
+
@fieldset[resource] = params[:fields]
|
133
|
+
end
|
134
|
+
|
135
|
+
# splits the string into array of symbles
|
136
|
+
@fieldset[resource] = @fieldset[resource].present? ? @fieldset[resource].split(',').map(&:to_sym) : default_fields
|
137
|
+
|
138
|
+
# filter out unpermitted fields by intersecting them
|
139
|
+
@fieldset[resource] &= permitted_fields if @fieldset[resource].present? && permitted_fields.present?
|
140
|
+
|
141
|
+
# set default fields to permitted_fields if needed
|
142
|
+
@fieldset[resource] = permitted_fields if show_all_permitted_fields_by_default && @fieldset[resource].blank? && permitted_fields.present?
|
143
|
+
end
|
144
|
+
|
145
|
+
# View Helper to set the default and permitted fields
|
146
|
+
def set_fieldset(resource, default_fields: [], permitted_fields: [])
|
147
|
+
@fieldset ||= {}
|
148
|
+
@fieldset[resource] = default_fields if @fieldset[resource].blank?
|
149
|
+
@fieldset[resource] &= permitted_fields
|
150
|
+
end
|
151
|
+
|
152
|
+
# Getter for the fieldset data
|
153
|
+
def fieldset(resource = nil, field = nil)
|
154
|
+
if resource.blank?
|
155
|
+
@fieldset ||= {}
|
156
|
+
elsif field.blank?
|
157
|
+
(@fieldset ||= {})[resource] ||= []
|
158
|
+
else
|
159
|
+
fieldset(resource).include?(field)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Return the 'fields' param description
|
164
|
+
def self.fields_param_desc(example: nil)
|
165
|
+
if example.present?
|
166
|
+
"Choose the fields to be returned. Example value: '#{example}'"
|
167
|
+
else
|
168
|
+
"Choose the fields to be returned."
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# = Helper To Make Resource APIs Filterable
|
4
|
+
#
|
5
|
+
# A filterable resource API supports requests to filter resources according to
|
6
|
+
# specific criteria, using the +filter+ query parameter.
|
7
|
+
#
|
8
|
+
# For example, the following is a request for all products that has a
|
9
|
+
# particular color:
|
10
|
+
#
|
11
|
+
# GET /products?filter[color]=red
|
12
|
+
#
|
13
|
+
# With this approach, multiple filters can be applied to a single request:
|
14
|
+
#
|
15
|
+
# GET /products?filter[color]=red&filter[status]=in-stock
|
16
|
+
#
|
17
|
+
# <em>Multiple filters are applied with the AND condition.</em>
|
18
|
+
#
|
19
|
+
# OR conditions of a single value can be represented as:
|
20
|
+
#
|
21
|
+
# GET /products?filter[color]=red,blue,yellow
|
22
|
+
#
|
23
|
+
# A few functions: +not+, +greater_then+, +less_then+, +greater_then_or_equal+,
|
24
|
+
# +less_then_or_equal+, +between+ and +like+ can be used while filtering
|
25
|
+
# the data, for example:
|
26
|
+
#
|
27
|
+
# GET /products?filter[color]=not(red)
|
28
|
+
# GET /products?filter[price]=greater_then(1000)
|
29
|
+
# GET /products?filter[price]=less_then_or_equal(2000)
|
30
|
+
# GET /products?filter[price]=between(1000,2000)
|
31
|
+
# GET /products?filter[name]=like(%lovely%)
|
32
|
+
#
|
33
|
+
# == Usage
|
34
|
+
#
|
35
|
+
# Include this +Concern+ in your Action Controller:
|
36
|
+
#
|
37
|
+
# SamplesController < ApplicationController
|
38
|
+
# include APIHelpers::Filterable
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# or in your Grape API class:
|
42
|
+
#
|
43
|
+
# class SampleAPI < Grape::API
|
44
|
+
# include APIHelper::Filterable
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# then use the +filter+ method like this:
|
48
|
+
#
|
49
|
+
# resources :products do
|
50
|
+
# get do
|
51
|
+
# @products = filter(Post, filterable_fields: [:name, :price, :color])
|
52
|
+
# # ...
|
53
|
+
# end
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# <em>The +filter+ method will return the scoped model, based directly
|
57
|
+
# from the requested URL.</em>
|
58
|
+
module APIHelper::Filterable
|
59
|
+
extend ActiveSupport::Concern
|
60
|
+
|
61
|
+
# Filter resources of a collection from the request parameter
|
62
|
+
#
|
63
|
+
# Params:
|
64
|
+
#
|
65
|
+
# +resource+::
|
66
|
+
# +ActiveRecord::Base+ or +ActiveRecord::Relation+ resource collection
|
67
|
+
# to filter data from
|
68
|
+
#
|
69
|
+
# +filterable_fields+::
|
70
|
+
# +Array+ of +Symbol+s fields that are allowed to be filtered, default
|
71
|
+
# to all
|
72
|
+
def filter(resource, filterable_fields: [])
|
73
|
+
# parse the request parameter
|
74
|
+
if params[:filter].is_a? Hash
|
75
|
+
@filter = params[:filter]
|
76
|
+
filterable_fields = filterable_fields.map(&:to_s)
|
77
|
+
|
78
|
+
@filter.each_pair do |field, condition|
|
79
|
+
next if filterable_fields.present? && !filterable_fields.include?(field)
|
80
|
+
field = resource.connection.quote_string(field)
|
81
|
+
|
82
|
+
next if resource.columns_hash[field].blank?
|
83
|
+
field_type = resource.columns_hash[field].type
|
84
|
+
|
85
|
+
# if a function is used
|
86
|
+
if func = condition.match(/(?<function>[^\(\)]+)\((?<param>.*)\)/)
|
87
|
+
case func[:function]
|
88
|
+
when 'not'
|
89
|
+
values = func[:param].split(',')
|
90
|
+
values.map!(&:to_bool) if field_type == :boolean
|
91
|
+
resource = resource.where.not(field => values)
|
92
|
+
when 'greater_then'
|
93
|
+
resource = resource.where("\"#{resource.table_name}\".\"#{field}\" > ?", func[:param])
|
94
|
+
when 'less_then'
|
95
|
+
resource = resource.where("\"#{resource.table_name}\".\"#{field}\" < ?", func[:param])
|
96
|
+
when 'greater_then_or_equal'
|
97
|
+
resource = resource.where("\"#{resource.table_name}\".\"#{field}\" >= ?", func[:param])
|
98
|
+
when 'less_then_or_equal'
|
99
|
+
resource = resource.where("\"#{resource.table_name}\".\"#{field}\" <= ?", func[:param])
|
100
|
+
when 'between'
|
101
|
+
param = func[:param].split(',')
|
102
|
+
resource = resource.where("\"#{resource.table_name}\".\"#{field}\" BETWEEN ? AND ?", param.first, param.last)
|
103
|
+
when 'like'
|
104
|
+
resource = resource.where("\"#{resource.table_name}\".\"#{field}\" LIKE ?", func[:param])
|
105
|
+
when 'null'
|
106
|
+
resource = resource.where(field => nil)
|
107
|
+
end
|
108
|
+
# if not function
|
109
|
+
else
|
110
|
+
values = condition.split(',')
|
111
|
+
values.map!(&:to_bool) if field_type == :boolean
|
112
|
+
resource = resource.where(field => values)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
return resource
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return the 'fields' param description
|
121
|
+
def self.filter_param_desc(for_field: nil)
|
122
|
+
if for_field.present?
|
123
|
+
"Filter data base on the '#{for_field}' field."
|
124
|
+
else
|
125
|
+
"Filter the data."
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# = Helper To Make Resource APIs Includable
|
4
|
+
#
|
5
|
+
# Inclusion of related resource lets your API return resources related to the
|
6
|
+
# primary data. This endpoint will support an +include+ request parameter to
|
7
|
+
# allow the client to customize which related resources should be returned.
|
8
|
+
#
|
9
|
+
# This design made references to the rules of <em>Inclusion of Related
|
10
|
+
# Resources</em> in <em>JSON API</em>:
|
11
|
+
# http://jsonapi.org/format/#fetching-includes
|
12
|
+
#
|
13
|
+
# For instance, comments could be requested with articles:
|
14
|
+
#
|
15
|
+
# GET /articles?include=comments
|
16
|
+
#
|
17
|
+
# The server will respond
|
18
|
+
#
|
19
|
+
# [
|
20
|
+
# {
|
21
|
+
# "id": 1,
|
22
|
+
# "title": "First Post",
|
23
|
+
# "content": "...",
|
24
|
+
# "comments": [
|
25
|
+
# {
|
26
|
+
# "id": 1,
|
27
|
+
# "content": "..."
|
28
|
+
# },
|
29
|
+
# {
|
30
|
+
# "id": 3,
|
31
|
+
# "content": "..."
|
32
|
+
# },
|
33
|
+
# {
|
34
|
+
# "id": 6,
|
35
|
+
# "content": "..."
|
36
|
+
# }
|
37
|
+
# ]
|
38
|
+
# },
|
39
|
+
# {
|
40
|
+
# "id": 2,
|
41
|
+
# "title": "Second Post",
|
42
|
+
# "content": "...",
|
43
|
+
# "comments": [
|
44
|
+
# {
|
45
|
+
# "id": 2,
|
46
|
+
# "content": "..."
|
47
|
+
# },
|
48
|
+
# {
|
49
|
+
# "id": 4,
|
50
|
+
# "content": "..."
|
51
|
+
# },
|
52
|
+
# {
|
53
|
+
# "id": 5,
|
54
|
+
# "content": "..."
|
55
|
+
# }
|
56
|
+
# ]
|
57
|
+
# }
|
58
|
+
# ]
|
59
|
+
#
|
60
|
+
# instead of just:
|
61
|
+
#
|
62
|
+
# [
|
63
|
+
# {
|
64
|
+
# "id": 1,
|
65
|
+
# "title": "First Post",
|
66
|
+
# "content": "...",
|
67
|
+
# "comments": [1, 3, 6]
|
68
|
+
# },
|
69
|
+
# {
|
70
|
+
# "id": 2,
|
71
|
+
# "title": "Second Post",
|
72
|
+
# "content": "...",
|
73
|
+
# "comments": [2, 4, 5]
|
74
|
+
# }
|
75
|
+
# ]
|
76
|
+
#
|
77
|
+
# If requesting multiple related resources is needed, they can be stated in a
|
78
|
+
# comma-separated list:
|
79
|
+
#
|
80
|
+
# GET /articles/12?include=author,comments
|
81
|
+
#
|
82
|
+
# == Usage
|
83
|
+
#
|
84
|
+
# Include this +Concern+ in your Action Controller:
|
85
|
+
#
|
86
|
+
# SamplesController < ApplicationController
|
87
|
+
# include APIHelper::Includable
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# or in your Grape API class:
|
91
|
+
#
|
92
|
+
# class SampleAPI < Grape::API
|
93
|
+
# include APIHelper::Includable
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# then set the options for the inclusion in the grape method:
|
97
|
+
#
|
98
|
+
# resources :posts do
|
99
|
+
# get do
|
100
|
+
# inclusion_for :post, root: true
|
101
|
+
# # ...
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# This helper parses the +include+ and <tt>include[object_type]</tt> parameters to
|
106
|
+
# determine what the API caller wants, and save the results into instance
|
107
|
+
# variables for further usage.
|
108
|
+
#
|
109
|
+
# After this you can use the +inclusion+ helper method to get the inclusion data
|
110
|
+
# that the request specifies, and do something like this in your controller:
|
111
|
+
#
|
112
|
+
# resource = resource.includes(:author) if inclusion(:post, :author)
|
113
|
+
#
|
114
|
+
# The +inclusion+ helper method returns data like this:
|
115
|
+
#
|
116
|
+
# inclusion #=> { post: [:author] }
|
117
|
+
# inclusion(:post) #=> [:author]
|
118
|
+
# inclusion(:post, :author) #=> true
|
119
|
+
#
|
120
|
+
# === API View with RABL
|
121
|
+
#
|
122
|
+
# If you're using RABL as the API view, it can be setup like this:
|
123
|
+
#
|
124
|
+
# # set the includable and default inclusion fields of the view
|
125
|
+
# set_inclusion :post, default_includes: [:author]
|
126
|
+
#
|
127
|
+
# # set the details for all includable fields
|
128
|
+
# set_inclusion_field :post, :author, :author_id
|
129
|
+
#
|
130
|
+
# # extends the partial to show included fields
|
131
|
+
# extends('extensions/includable_childs', locals: { self_resource: :post })
|
132
|
+
module APIHelper::Includable
|
133
|
+
extend ActiveSupport::Concern
|
134
|
+
|
135
|
+
# Gets the include parameters, organize them into a +@inclusion+ hash for model to use
|
136
|
+
# inner-join queries and/or templates to render relation attributes included.
|
137
|
+
# Following the URL rules of JSON API:
|
138
|
+
# http://jsonapi.org/format/#fetching-includes
|
139
|
+
#
|
140
|
+
# Params:
|
141
|
+
#
|
142
|
+
# +resource+::
|
143
|
+
# +Symbol+ name of resource to receive the inclusion
|
144
|
+
def inclusion_for(resource, root: false, default_includes: [])
|
145
|
+
@inclusion ||= Hashie::Mash.new
|
146
|
+
@meta ||= Hashie::Mash.new
|
147
|
+
|
148
|
+
# put the includes in place
|
149
|
+
if params[:include].is_a? Hash
|
150
|
+
@inclusion[resource] = params[:include][resource] || params[:include][resource]
|
151
|
+
elsif root
|
152
|
+
@inclusion[resource] = params[:include]
|
153
|
+
end
|
154
|
+
|
155
|
+
# splits the string into array of symbles
|
156
|
+
@inclusion[resource] = @inclusion[resource] ? @inclusion[resource].split(',').map(&:to_sym) : default_includes
|
157
|
+
end
|
158
|
+
|
159
|
+
# View Helper to set the inclusion and default_inclusion.
|
160
|
+
def set_inclusion(resource, default_includes: [])
|
161
|
+
@inclusion ||= {}
|
162
|
+
@inclusion_field ||= {}
|
163
|
+
@inclusion[resource] = default_includes if @inclusion[resource].blank?
|
164
|
+
end
|
165
|
+
|
166
|
+
# View Helper to set the inclusion details.
|
167
|
+
def set_inclusion_field(self_resource, field, id_field, class_name: nil, url: nil)
|
168
|
+
return if (@fieldset.present? && @fieldset[self_resource].present? && !@fieldset[self_resource].include?(field))
|
169
|
+
|
170
|
+
@inclusion_field ||= {}
|
171
|
+
@inclusion_field[self_resource] ||= []
|
172
|
+
field_data = {
|
173
|
+
field: field,
|
174
|
+
id_field: id_field,
|
175
|
+
class_name: class_name,
|
176
|
+
url: url
|
177
|
+
}
|
178
|
+
@inclusion_field[self_resource] << field_data
|
179
|
+
@fieldset[self_resource].delete(field) if @fieldset[self_resource].present?
|
180
|
+
end
|
181
|
+
|
182
|
+
# Getter for the inclusion data.
|
183
|
+
def inclusion(resource = nil, field = nil)
|
184
|
+
if resource.blank?
|
185
|
+
@inclusion ||= {}
|
186
|
+
elsif field.blank?
|
187
|
+
(@inclusion ||= {})[resource] ||= []
|
188
|
+
else
|
189
|
+
return false if (try(:fieldset, resource).present? && !fieldset(resource, field))
|
190
|
+
inclusion(resource).include?(field)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Return the 'include' param description
|
195
|
+
def self.include_param_desc(example: nil, default: nil)
|
196
|
+
if default.present?
|
197
|
+
desc = "Returning compound documents that include specific associated objects, defaults to '#{default}'."
|
198
|
+
else
|
199
|
+
desc = "Returning compound documents that include specific associated objects."
|
200
|
+
end
|
201
|
+
if example.present?
|
202
|
+
"#{desc} Example value: '#{example}'"
|
203
|
+
else
|
204
|
+
desc
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# = Helper To Make Resource APIs Multigettable
|
4
|
+
#
|
5
|
+
# A normal resource API can let clients retrieve one specified data at a time:
|
6
|
+
#
|
7
|
+
# GET /posts/3
|
8
|
+
#
|
9
|
+
# If it's declared to be multigettable, then clients can retrieve multiple
|
10
|
+
# specified data like this:
|
11
|
+
#
|
12
|
+
# GET /posts/3,4,8,9
|
13
|
+
#
|
14
|
+
# == Usage
|
15
|
+
#
|
16
|
+
# Include this +Concern+ in your Action Controller:
|
17
|
+
#
|
18
|
+
# SamplesController < ApplicationController
|
19
|
+
# include APIHelper::Multigettable
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# or in your Grape API class:
|
23
|
+
#
|
24
|
+
# class SampleAPI < Grape::API
|
25
|
+
# include APIHelper::Multigettable
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# then use the +multiget+ method like this:
|
29
|
+
#
|
30
|
+
# resources :posts do
|
31
|
+
# # ...
|
32
|
+
# get :id do
|
33
|
+
# @post = multiget(Post, find_by: :id, max: 12)
|
34
|
+
# # ...
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# <em>The +multiget+ method returns a array of or a single model, based
|
39
|
+
# directly from the requested URL.</em>
|
40
|
+
#
|
41
|
+
# There is also another helper method to determine whether the request is
|
42
|
+
# multigeting or not:
|
43
|
+
#
|
44
|
+
# multiget?(find_by: id) #=> true of false
|
45
|
+
#
|
46
|
+
# It can be used to interact with other condition and functionalities,
|
47
|
+
# like this:
|
48
|
+
#
|
49
|
+
# inclusion_for :post, root: true,
|
50
|
+
# default_includes: (multiget?(find_by: :id) ? [] : [:author])
|
51
|
+
module APIHelper::Multigettable
|
52
|
+
extend ActiveSupport::Concern
|
53
|
+
|
54
|
+
# Get multiple resources from a resource URL by specifing ids split by ','
|
55
|
+
#
|
56
|
+
# Params:
|
57
|
+
#
|
58
|
+
# +resource+::
|
59
|
+
# +ActiveRecord::Base+ or +ActiveRecord::Relation+ resource collection
|
60
|
+
# to find data from
|
61
|
+
#
|
62
|
+
# +find_by+::
|
63
|
+
# +Symbol+ the attribute that is used to find data
|
64
|
+
#
|
65
|
+
# +max+::
|
66
|
+
# +Integer+ maxium count of returning results
|
67
|
+
def multiget(resource, find_by: :id, max: 10)
|
68
|
+
ids = params[find_by].split(',')
|
69
|
+
ids = ids[0..(max - 1)]
|
70
|
+
|
71
|
+
if ids.count > 1
|
72
|
+
resource.where(find_by => ids)
|
73
|
+
else
|
74
|
+
resource.find_by(find_by => ids[0])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Is the a multiget request?
|
79
|
+
def multiget?(find_by: :id)
|
80
|
+
params[find_by].include?(',')
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# = Helper To Make Resource APIs Paginatable
|
4
|
+
#
|
5
|
+
# Paginating the requested items can avoid returning too much information
|
6
|
+
# in a single response. API callers can iterate over the results using
|
7
|
+
# pagination instead of rerteving all the data in one time, ruining the
|
8
|
+
# database connection or network.
|
9
|
+
#
|
10
|
+
# There are two parameters clients can use: +per_page+ and +page+. The former
|
11
|
+
# is used for setting how many data will be returned in each page, there will
|
12
|
+
# be a maxium limit and default value for each API:
|
13
|
+
#
|
14
|
+
# GET /posts?per_page=10
|
15
|
+
#
|
16
|
+
# <em>The server will respond 10 items at a time.</em>
|
17
|
+
#
|
18
|
+
# Use the +page+ parameter to specify which to retrieve:
|
19
|
+
#
|
20
|
+
# GET /posts?page=5
|
21
|
+
#
|
22
|
+
# Pagination info will be provided in the HTTP Link header like this:
|
23
|
+
#
|
24
|
+
# Link: <http://api-server.dev/movies?page=1>; rel="first",
|
25
|
+
# <http://api-server.dev/movies?page=4>; rel="prev"
|
26
|
+
# <http://api-server.dev/movies?page=6>; rel="next",
|
27
|
+
# <http://api-server.dev/movies?page=238>; rel="last"
|
28
|
+
#
|
29
|
+
# <em>Line breaks are added for readability.</em>
|
30
|
+
#
|
31
|
+
# Which follows the proposed RFC 5988 standard.
|
32
|
+
#
|
33
|
+
# == Usage
|
34
|
+
#
|
35
|
+
# Include this +Concern+ in your Action Controller:
|
36
|
+
#
|
37
|
+
# SamplesController < ApplicationController
|
38
|
+
# include APIHelper::Paginatable
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# or in your Grape API class:
|
42
|
+
#
|
43
|
+
# class SampleAPI < Grape::API
|
44
|
+
# include APIHelper::Paginatable
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# then set the options for pagination in the grape method:
|
48
|
+
#
|
49
|
+
# resources :posts do
|
50
|
+
# get do
|
51
|
+
# pagination User.count, default_per_page: 25, maxium_per_page: 100
|
52
|
+
#
|
53
|
+
# # ...
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Then use the helper methods, like this:
|
58
|
+
#
|
59
|
+
# User.page(page).per(per_page)
|
60
|
+
#
|
61
|
+
# HTTP Link header will be automatically set.
|
62
|
+
module APIHelper::Paginatable
|
63
|
+
extend ActiveSupport::Concern
|
64
|
+
|
65
|
+
def pagination(items_count, default_per_page: 20, maxium_per_page: 100, set_header: true)
|
66
|
+
items_count = items_count.count if items_count.respond_to? :count
|
67
|
+
|
68
|
+
@per_page = (params[:per_page] || default_per_page).to_i
|
69
|
+
@per_page = maxium_per_page if @per_page > maxium_per_page
|
70
|
+
@per_page = 1 if @per_page < 1
|
71
|
+
|
72
|
+
items_count = 0 if items_count < 0
|
73
|
+
pages_count = (items_count.to_f / @per_page).ceil
|
74
|
+
pages_count = 1 if pages_count < 1
|
75
|
+
|
76
|
+
@page = (params[:page] || 1).to_i
|
77
|
+
@page = pages_count if @page > pages_count
|
78
|
+
@page = 1 if @page < 1
|
79
|
+
|
80
|
+
link_headers ||= []
|
81
|
+
|
82
|
+
if current_page < pages_count
|
83
|
+
link_headers << "<#{add_or_replace_uri_param(request.url, :page, current_page + 1)}>; rel=\"next\""
|
84
|
+
link_headers << "<#{add_or_replace_uri_param(request.url, :page, pages_count)}>; rel=\"last\""
|
85
|
+
end
|
86
|
+
if current_page > 1
|
87
|
+
link_headers << "<#{add_or_replace_uri_param(request.url, :page, (current_page > pages_count ? pages_count : current_page - 1))}>; rel=\"prev\""
|
88
|
+
link_headers << "<#{add_or_replace_uri_param(request.url, :page, 1)}>; rel=\"first\""
|
89
|
+
end
|
90
|
+
|
91
|
+
link_header = link_headers.join(', ')
|
92
|
+
|
93
|
+
if set_header
|
94
|
+
if self.respond_to?(:header)
|
95
|
+
self.header('Link', link_header)
|
96
|
+
self.header('X-Items-Count', items_count.to_s)
|
97
|
+
end
|
98
|
+
|
99
|
+
if defined?(response) && response.respond_to?(:headers)
|
100
|
+
response.headers['Link'] = link_header
|
101
|
+
response.headers['X-Items-Count'] = items_count.to_s
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
link_header
|
106
|
+
end
|
107
|
+
|
108
|
+
# Getter for the current page
|
109
|
+
def page
|
110
|
+
@page
|
111
|
+
end
|
112
|
+
|
113
|
+
alias_method :current_page, :page
|
114
|
+
|
115
|
+
# Getter for per_page
|
116
|
+
def per_page
|
117
|
+
@per_page
|
118
|
+
end
|
119
|
+
|
120
|
+
alias_method :page_with, :per_page
|
121
|
+
|
122
|
+
def add_or_replace_uri_param(url, param_name, param_value)
|
123
|
+
uri = URI(url)
|
124
|
+
params = URI.decode_www_form(uri.query || '')
|
125
|
+
params.delete_if { |param| param[0].to_s == param_name.to_s }
|
126
|
+
params << [param_name, param_value]
|
127
|
+
uri.query = URI.encode_www_form(params)
|
128
|
+
uri.to_s
|
129
|
+
end
|
130
|
+
|
131
|
+
# Return the 'per_page' param description
|
132
|
+
def self.per_page_param_desc
|
133
|
+
"Specify how many items you want each page to return."
|
134
|
+
end
|
135
|
+
|
136
|
+
# Return the 'page' param description
|
137
|
+
def self.page_param_desc
|
138
|
+
"Specify which page you want to get."
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# = Helper To Make Resource APIs Sortable
|
4
|
+
#
|
5
|
+
# A Sortable Resource API gives the flexibility to change how the returned data
|
6
|
+
# is sorted to the client. Clients can use the +sort+ URL parameter to control
|
7
|
+
# how the returned data is sorted, as this example:
|
8
|
+
#
|
9
|
+
# GET /posts?sort=-created_at,title
|
10
|
+
#
|
11
|
+
# This means to sort the data by its created time descended and then the title
|
12
|
+
# ascended.
|
13
|
+
#
|
14
|
+
# == Usage
|
15
|
+
#
|
16
|
+
# Include this +Concern+ in your Action Controller:
|
17
|
+
#
|
18
|
+
# SamplesController < ApplicationController
|
19
|
+
# include APIHelper::Sortable
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# or in your Grape API class:
|
23
|
+
#
|
24
|
+
# class SampleAPI < Grape::API
|
25
|
+
# include APIHelper::Sortable
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# then use the +sortable+ method like this:
|
29
|
+
#
|
30
|
+
# resources :posts do
|
31
|
+
# get do
|
32
|
+
# sortable default_order: { created_at: :desc }
|
33
|
+
# # ...
|
34
|
+
# @posts = Post.order(sort)#...
|
35
|
+
# # ...
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
module APIHelper::Sortable
|
39
|
+
extend ActiveSupport::Concern
|
40
|
+
|
41
|
+
# Gets the `sort` parameter with the format 'resourses?sort=-created_at,name',
|
42
|
+
# verify and converts it into an safe Hash that can be passed into the .order
|
43
|
+
# method.
|
44
|
+
#
|
45
|
+
# Params:
|
46
|
+
#
|
47
|
+
# +default_order+::
|
48
|
+
# +Hash+ the default value to return if the sort parameter is not provided
|
49
|
+
def sortable(default_order: {})
|
50
|
+
# get the parameter
|
51
|
+
sort_by = params[:sort] || params[:sort_by]
|
52
|
+
|
53
|
+
if sort_by.is_a? String
|
54
|
+
# split it
|
55
|
+
sort_by_attrs = sort_by.gsub(/[^a-zA-Z0-9\-_,]/, '').split(',')
|
56
|
+
|
57
|
+
# save it
|
58
|
+
@sort = {}
|
59
|
+
sort_by_attrs.each do |attrb|
|
60
|
+
if attrb.match(/^-/)
|
61
|
+
@sort[attrb.gsub(/^-/, '')] = :desc
|
62
|
+
else
|
63
|
+
@sort[attrb] = :asc
|
64
|
+
end
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@sort = default_order
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Helper to get the sort data
|
72
|
+
def sort
|
73
|
+
@sort
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return the 'sort' param description
|
77
|
+
def self.sort_param_desc(example: nil, default: nil)
|
78
|
+
if default.present?
|
79
|
+
desc = "Specify how the returning data should be sorted, defaults to '#{default}'."
|
80
|
+
else
|
81
|
+
desc = "Specify how the returning data should be sorted."
|
82
|
+
end
|
83
|
+
if example.present?
|
84
|
+
"#{desc} Example value: '#{example}'"
|
85
|
+
else
|
86
|
+
desc
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/api_helper.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "api_helper/version"
|
2
|
+
require "api_helper/fieldsettable"
|
3
|
+
require "api_helper/includable"
|
4
|
+
require "api_helper/paginatable"
|
5
|
+
require "api_helper/sortable"
|
6
|
+
require "api_helper/filterable"
|
7
|
+
require "api_helper/multigettable"
|
8
|
+
|
9
|
+
module APIHelper
|
10
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: api_helper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Neson
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3'
|
69
|
+
description: Helpers for creating standard web API for Rails or Grape with ActiveRecord.
|
70
|
+
email:
|
71
|
+
- neson@dex.tw
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- ".travis.yml"
|
79
|
+
- Gemfile
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- api_helper.gemspec
|
83
|
+
- bin/console
|
84
|
+
- bin/setup
|
85
|
+
- lib/api_helper.rb
|
86
|
+
- lib/api_helper/fieldsettable.rb
|
87
|
+
- lib/api_helper/filterable.rb
|
88
|
+
- lib/api_helper/includable.rb
|
89
|
+
- lib/api_helper/multigettable.rb
|
90
|
+
- lib/api_helper/paginatable.rb
|
91
|
+
- lib/api_helper/sortable.rb
|
92
|
+
- lib/api_helper/version.rb
|
93
|
+
homepage: https://github.com/Neson/APIHelper
|
94
|
+
licenses: []
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubyforge_project:
|
112
|
+
rubygems_version: 2.4.6
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Helpers for creating standard web API.
|
116
|
+
test_files: []
|