Skip to content

Commit 515a0fe

Browse files
committed
add code
1 parent f7bde99 commit 515a0fe

File tree

11 files changed

+902
-32
lines changed

11 files changed

+902
-32
lines changed

README.md

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
# ApiHelper
1+
# APIHelper
22

3-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/api_helper`. To experiment with that code, run `bin/console` for an interactive prompt.
3+
Helpers for creating standard RESTful API for Rails or Grape with Active Record.
44

5-
TODO: Delete this and the text above, and describe your gem
65

76
## Installation
87

98
Add this line to your application's Gemfile:
109

1110
```ruby
12-
gem 'api_helper'
11+
gem 'APIHelper'
1312
```
1413

1514
And then execute:
@@ -18,19 +17,78 @@ And then execute:
1817

1918
Or install it yourself as:
2019

21-
$ gem install api_helper
20+
$ gem install APIHelper
21+
22+
23+
## API Standards
24+
25+
<dl>
26+
27+
<dt>Fieldsettable</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>Includable</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>Paginatable</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>Sortable</dt>
37+
<dd>Client can set custom sorting with the "sort" query parameter while getting a resource collection.</dd>
38+
39+
<dt>Filterable</dt>
40+
<dd>Enables clients to filter through a resource collection with their fields.</dd>
41+
42+
<dt>Multigettable</dt>
43+
<dd>Let Client execute operations on multiple resources with a single request.</dd>
44+
45+
</dl>
46+
2247

2348
## Usage
2449

25-
TODO: Write usage instructions here
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+
2684

2785
## Development
2886

2987
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.
3088

3189
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).
3290

33-
## Contributing
3491

35-
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/api_helper.
92+
## Contributing
3693

94+
Bug reports and pull requests are welcome on GitHub at https://github.com/Neson/APIHelper.

api_helper.gemspec

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,22 @@ require 'api_helper/version'
55

66
Gem::Specification.new do |spec|
77
spec.name = "api_helper"
8-
spec.version = ApiHelper::VERSION
8+
spec.version = APIHelper::VERSION
99
spec.authors = ["Neson"]
1010
spec.email = ["neson@dex.tw"]
1111

12-
spec.summary = %q{TODO: Write a short summary, because Rubygems requires one.}
13-
spec.description = %q{TODO: Write a longer description or delete this line.}
14-
spec.homepage = "TODO: Put your gem's website or public repo URL here."
15-
16-
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17-
# delete this section to allow pushing this gem to any host.
18-
if spec.respond_to?(:metadata)
19-
spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
20-
else
21-
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
22-
end
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"
2315

2416
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
2517
spec.bindir = "exe"
2618
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
2719
spec.require_paths = ["lib"]
2820

29-
spec.add_development_dependency "bundler", "~> 1.10"
30-
spec.add_development_dependency "rake", "~> 10.0"
21+
spec.add_development_dependency "bundler"
22+
spec.add_development_dependency "rake"
3123
spec.add_development_dependency "rspec"
24+
25+
spec.add_development_dependency "activesupport", ">= 3"
3226
end

lib/api_helper.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
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"
28

3-
module ApiHelper
4-
# Your code goes here...
9+
module APIHelper
510
end

lib/api_helper/fieldsettable.rb

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 commit comments

Comments
 (0)