jsonapi-utils 0.4.2 → 0.4.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
  SHA1:
3
- metadata.gz: d6eff0a7e2d11caccf5abf5a536e44169a753848
4
- data.tar.gz: 28548fd3c87c86c5812af6b98ab7211f85161cd0
3
+ metadata.gz: c5c764a38bce9c5b3858bb2cde1a345ca31f6a23
4
+ data.tar.gz: 8c12bdfdf4c949eda015c8a46534a1e1c3cf333b
5
5
  SHA512:
6
- metadata.gz: b25decbe1f936d4f47699941abbc6967f52d390bd83f760a975cc2eca02004205c908d64de1be152bd692969a9f4233ff0f4269f7c3339fbd1aeb35c0df7bad3
7
- data.tar.gz: 7ed12c4c431e2d4693e28e00b0cfe617dc17bb8088dd75946b04dc278d308227f0405e21be22b8218c7448a0e7d2f2d8b52b25da4ae8bede7431c5cb70ea993b
6
+ metadata.gz: 202613eae09e04c60f0ccb83c250c5c934667899af4a359c4e98fa146f05db16d9dd1185505081f023ab6edda8e07a8d30617c3ba2c84ba2bc6aa4a490c24485
7
+ data.tar.gz: fc7edd5cd32d6281ad6d639b5d0756c0a80b81ca6b3bcc622c8495015b627e967cbd476088da8e2acb2f215eea8b1bc3534cd36bc90b34e7bf623b0ff4d9739c
@@ -1,4 +1,9 @@
1
1
  language: ruby
2
+ sudo: false
3
+ env:
4
+ - 'RAILS_VERSION=4.1.0'
5
+ - 'RAILS_VERSION=4.2.0'
2
6
  rvm:
3
- - 2.2.2
7
+ - 2.2
4
8
  before_install: gem install bundler -v 1.10.4
9
+ script: bundle exec rspec spec/controllers/{user,post}s_controller_spec.rb
data/README.md CHANGED
@@ -2,16 +2,18 @@
2
2
 
3
3
  [![Code Climate](https://codeclimate.com/github/b2beauty/jsonapi-utils/badges/gpa.svg)](https://codeclimate.com/github/b2beauty/jsonapi-utils)
4
4
  [![Gem Version](https://badge.fury.io/rb/jsonapi-utils.svg)](https://badge.fury.io/rb/jsonapi-utils)
5
+ [![Build Status](https://travis-ci.org/b2beauty/jsonapi-utils.svg?branch=master)](https://travis-ci.org/b2beauty/jsonapi-utils)
5
6
 
6
- `JSONAPI::Utils` works on top of the awesome [jsonapi-resources](https://github.com/cerebris/jsonapi-resources) gem bringing to your controllers a Rails way to render [JSON API](http://jsonapi.org)-compliant responses.
7
+ Simple yet powerful way to have your Rails API compliant with [JSON API](http://jsonapi.org).
8
+
9
+ JSONAPI::Utils (JU) was built on top of [JSONAPI::Resources](https://github.com/cerebris/jsonapi-resources) taking advantage of its resource-driven style and bringing a Rails way to build modern APIs with no or less learning curve.
7
10
 
8
11
  ## Installation
9
12
 
10
13
  Add these lines to your application's Gemfile:
11
14
 
12
15
  ```ruby
13
- gem 'jsonapi-resources', '~> 0.7.0'
14
- gem 'jsonapi-utils'
16
+ gem 'jsonapi-utils', '~> 0.4.3'
15
17
  ```
16
18
 
17
19
  And then execute:
@@ -20,34 +22,53 @@ And then execute:
20
22
  $ bundle
21
23
  ```
22
24
 
23
- ## Main Macros
24
-
25
- * `jsonapi_render`: it works like ActionController's `render` method, receiving model objects and
26
- rendering them into JSON API's data format.
25
+ ## Macros
27
26
 
28
- * `jsonapi_serialize`: in the backstage, it's the method that actually parsers model objects or hashes and builds JSON data.
29
- It can be called anywhere in controllers, concerns etc.
27
+ ### jsonapi_render
30
28
 
31
- ## Options
29
+ Validates the request and renders ActiveRecord/Hash objects into JSON API-compliant responses.
32
30
 
33
- Those macros accept the following options:
31
+ Arguments:
34
32
 
35
- * `resource`: explicitly points the resource to be used in the serialization. By default, JSONAPI::Utils will
36
- select resources by inferencing from controller's name.
33
+ - `json`: ActiveRecord or Hash object to be rendered as JSON document;
34
+ - `status`: HTTP status code (Integer or Symbol). If ommited a status code will be automatically infered;
35
+ - `options`:
36
+ - `resource`: explicitly points the resource to be used in the serialization. By default, JU will select resources by inferencing from controller's name.
37
+ - `count`: explicitly points the total count of records for the request in order to build a proper pagination. By default, JU will count the total number of records.
38
+ - `model`: sets the model reference in cases when `json` is a Hash or a collection of Hashes.
37
39
 
38
40
  ```ruby
39
- # If in UsersController for some reason it needs to render a different resource:
40
- jsonapi_render json: Post.all, options: { resource: PostResource }
41
+ # Single resource rendering
42
+ jsonapi_render json: User.find(params[:id])
43
+
44
+ # Collection rendering
45
+ jsonapi_render json: User.all
46
+
47
+ # Forcing a different resource
48
+ jsonapi_render json: User.all, options: { resource: V2::UserResource }
49
+
50
+ # Using a specific count
51
+ jsonapi_render json: User.some_weird_scope, options: { count: User.some_weird_scope_count }
52
+
53
+ # Hash rendering
54
+ jsonapi_render json: { data: { id: 1, first_name: 'Tiago' } }, options: { model: User }
55
+
56
+ # Collection of Hashes rendering
57
+ jsonapi_render json: { data: [{ id: 1, first_name: 'Tiago' }, { id: 2, first_name: 'Doug' }] }, options: { model: User }
41
58
  ```
42
59
 
43
- * `count`: explicitly points the total count of records for the request, in order to build a proper pagination. By default, JSONAPI::Utils will
44
- count the total number of records for a given resource.
60
+ ### jsonapi_serialize
61
+
62
+ In the backstage that's the method that actually parsers ActiveRecord/Hash objects and builds a new Hash compliant with JSON API. It can be called anywhere in controllers, concerns etc.
45
63
 
46
64
  ```ruby
47
- users = User.all
48
- jsonapi_render json: users, options: { count: users.count }
65
+ collection = jsonapi_serialize(User.all)
66
+ render json: do_some_magic_with(collection)
49
67
  ```
50
68
 
69
+ Arguments:
70
+ - It receives a second optional argument with the same options for `jsonapi_render`.
71
+
51
72
  ## Usage
52
73
 
53
74
  Let's say we have a Rails app for a super simple blog.
@@ -342,7 +363,7 @@ Content-Type: application/vnd.api+json
342
363
  Request:
343
364
 
344
365
  ```
345
- GET /users?include=posts&fields[users]=full_name,posts&fields[posts]=title&page[number]=1&page[size]=1 HTTP/1.1
366
+ GET /users?include=posts&fields[users]=first_name,last_name,posts&fields[posts]=title&sort=first_name,last_name&page[number]=1&page[size]=1 HTTP/1.1
346
367
  Accept: application/vnd.api+json
347
368
  ```
348
369
 
@@ -354,6 +375,26 @@ Content-Type: application/vnd.api+json
354
375
 
355
376
  {
356
377
  "data": [
378
+ {
379
+ "id": "2",
380
+ "type": "users",
381
+ "links": {
382
+ "self": "http://api.myblog.com/users/2"
383
+ },
384
+ "attributes": {
385
+ "first_name": "Douglas",
386
+ "last_name": "André"
387
+ },
388
+ "relationships": {
389
+ "posts": {
390
+ "links": {
391
+ "self": "http://api.myblog.com/users/2/relationships/posts",
392
+ "related": "http://api.myblog.com/users/2/posts"
393
+ },
394
+ "data": []
395
+ }
396
+ }
397
+ },
357
398
  {
358
399
  "id": "1",
359
400
  "type": "users",
@@ -361,7 +402,8 @@ Content-Type: application/vnd.api+json
361
402
  "self": "http://api.myblog.com/users/1"
362
403
  },
363
404
  "attributes": {
364
- "full_name": "Tiago Guedes"
405
+ "first_name": "Tiago",
406
+ "last_name": "Guedes"
365
407
  },
366
408
  "relationships": {
367
409
  "posts": {
@@ -395,9 +437,8 @@ Content-Type: application/vnd.api+json
395
437
  "record_count": 2
396
438
  },
397
439
  "links": {
398
- "first": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=full_name%2Cposts&include=posts&page%5Bnumber%5D=1&page%5Bsize%5D=1",
399
- "next": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=full_name%2Cposts&include=posts&page%5Bnumber%5D=2&page%5Bsize%5D=1",
400
- "last": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=full_name%2Cposts&include=posts&page%5Bnumber%5D=2&page%5Bsize%5D=1"
440
+ "first": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=first_name%2Clast_name%2Cposts&include=posts&page%5Blimit%5D=2&page%5Boffset%5D=0&sort=first_name%2Clast_name",
441
+ "last": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=first_name%2Clast_name%2Cposts&include=posts&page%5Blimit%5D=2&page%5Boffset%5D=0&sort=first_name%2Clast_name"
401
442
  }
402
443
  }
403
444
  ```
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
- RSpec::Core::RakeTask.new(:spec)
4
+ RSpec::Core::RakeTask.new(:spec) do |test|
5
+ test.pattern = 'spec/controllers/{user,post}s_controller.rb'
6
+ end
5
7
 
6
- task :default => :spec
8
+ task default: :spec
@@ -4,26 +4,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'jsonapi/utils/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "jsonapi-utils"
7
+ spec.name = 'jsonapi-utils'
8
8
  spec.version = JSONAPI::Utils::VERSION
9
- spec.authors = ["Tiago Guedes", "Douglas André"]
10
- spec.email = ["tiagopog@gmail.com", "douglas@beautydate.com.br"]
9
+ spec.authors = ['Tiago Guedes', 'Douglas André']
10
+ spec.email = ['tiagopog@gmail.com', 'douglas@beautydate.com.br']
11
11
 
12
12
  spec.summary = %q{JSON::Utils is a simple way to get a full-featured JSON API serialization for your controller's responses.}
13
13
  spec.description = %q{A Rails way to get your API's data serialized through JSON API's specs (http://jsosapi.org)}
14
- spec.homepage = "https://github.com/b2beauty/jsonapi-utils"
15
- spec.license = "MIT"
14
+ spec.homepage = 'https://github.com/b2beauty/jsonapi-utils'
15
+ spec.license = 'MIT'
16
16
 
17
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
- spec.bindir = "exe"
18
+ spec.bindir = 'exe'
19
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
20
  spec.require_paths = ['lib']
21
21
 
22
+ spec.add_runtime_dependency 'jsonapi-resources', '~> 0.7.0'
23
+
22
24
  spec.add_development_dependency 'bundler', '~> 1.10'
23
25
  spec.add_development_dependency 'rake', '~> 10.0'
24
- spec.add_development_dependency 'rspec-rails'
26
+ spec.add_development_dependency 'rspec-rails', '~> 3.1'
25
27
  spec.add_development_dependency 'sqlite3'
26
28
  spec.add_development_dependency 'factory_girl', '~> 4.5'
27
29
  spec.add_development_dependency 'jsonapi-resources', '~> 0.7.0'
28
- spec.add_development_dependency 'rails', '>= 4.0'
30
+ spec.add_development_dependency 'rails', '~> 4.2'
31
+ spec.add_development_dependency 'pry'
29
32
  end
@@ -1,27 +1,28 @@
1
+ require 'jsonapi-resources'
1
2
  require 'jsonapi/utils/version'
2
- require 'active_support/concern'
3
3
  require 'jsonapi/utils/exceptions'
4
4
 
5
5
  module JSONAPI
6
6
  module Utils
7
- extend ::ActiveSupport::Concern
8
-
9
- include do
10
- helper_method :jsonapi_serialize
7
+ def self.included(base)
8
+ base.helper_method :jsonapi_serialize
11
9
  end
12
10
 
13
- def jsonapi_render(options)
14
- if options.has_key?(:json)
15
- response = jsonapi_serialize(options[:json], options[:options] || {})
16
- render json: response, status: options[:status] || :ok
11
+ def jsonapi_render(json:, status: nil, options: {})
12
+ setup_request
13
+
14
+ if @request.errors.present?
15
+ render_errors(@request.errors)
17
16
  else
18
- raise ArgumentError.new('":json" key must be set to JSONAPI::Utils#jsonapi_render')
17
+ body = jsonapi_serialize(json, options)
18
+ render json: body, status: status || @_response_document.status
19
19
  end
20
20
  rescue => e
21
- raise e unless e.class.name.starts_with?('JSONAPI::Exceptions')
22
21
  handle_exceptions(e)
23
22
  ensure
24
- headers['Content-Type'] = JSONAPI::MEDIA_TYPE
23
+ if response.body.size > 0
24
+ response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
25
+ end
25
26
  end
26
27
 
27
28
  def jsonapi_render_errors(exception)
@@ -42,8 +43,7 @@ module JSONAPI
42
43
  end
43
44
 
44
45
  def jsonapi_render_not_found(exception)
45
- setup_request
46
- id = exception.message.match(/=(\d+)/)[1]
46
+ id = exception.message.match(/=([\w-]+)/).try(:[], 1) || '(no identifier)'
47
47
  jsonapi_render_errors(JSONAPI::Exceptions::RecordNotFound.new(id))
48
48
  end
49
49
 
@@ -53,12 +53,41 @@ module JSONAPI
53
53
 
54
54
  def jsonapi_serialize(records, options = {})
55
55
  setup_request
56
- results = JSONAPI::OperationResults.new
56
+
57
+ if records.is_a?(Hash)
58
+ hash = records.with_indifferent_access
59
+ records = hash_to_active_record(hash[:data], options[:model])
60
+ end
57
61
 
58
62
  fix_request_options(params, records)
63
+ build_response_document(records, options).contents
64
+ end
65
+
66
+ def filter_params
67
+ @_filter_params ||=
68
+ if params[:filter].is_a?(Hash)
69
+ params[:filter].keys.each_with_object({}) do |resource, hash|
70
+ hash[resource] = params[:filter][resource]
71
+ end
72
+ end
73
+ end
74
+
75
+ def sort_params
76
+ @_sort_params ||=
77
+ if params[:sort].present?
78
+ params[:sort].split(',').each_with_object({}) do |criteria, hash|
79
+ order, field = criteria.match(/(\-?)(\w+)/i)[1..2]
80
+ hash[field] = order == '-' ? :desc : :asc
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def build_response_document(records, options)
88
+ results = JSONAPI::OperationResults.new
59
89
 
60
90
  if records.respond_to?(:to_ary)
61
- records = fix_when_hash(records, options) if needs_to_be_fixed?(records)
62
91
  @_records = build_collection(records, options)
63
92
  results.add_result(JSONAPI::ResourcesOperationResult.new(:ok, @_records, result_options(records, options)))
64
93
  else
@@ -66,10 +95,9 @@ module JSONAPI
66
95
  results.add_result(JSONAPI::ResourceOperationResult.new(:ok, @_record))
67
96
  end
68
97
 
69
- create_response_document(results).contents
98
+ @_response_document = create_response_document(results)
70
99
  end
71
100
 
72
- private
73
101
 
74
102
  def fix_request_options(params, records)
75
103
  return if request.method !~ /get/i ||
@@ -79,10 +107,6 @@ module JSONAPI
79
107
  @request.send("setup_#{action}_action", params)
80
108
  end
81
109
 
82
- def needs_to_be_fixed?(records)
83
- records.is_a?(Array) && records.all? { |e| e.is_a?(Hash) }
84
- end
85
-
86
110
  def result_options(records, options)
87
111
  {}.tap do |data|
88
112
  if JSONAPI.configuration.default_paginator != :none &&
@@ -147,13 +171,6 @@ module JSONAPI
147
171
  (options[:filter].nil? || options[:filter])
148
172
  end
149
173
 
150
- def filter_params
151
- @_filter_params ||=
152
- params[:filter].keys.each_with_object({}) do |resource, hash|
153
- hash[resource] = params[:filter][resource]
154
- end
155
- end
156
-
157
174
  def apply_pagination(records, options = {})
158
175
  return records unless apply_pagination?(options)
159
176
  pagination = set_pagination(options)
@@ -184,14 +201,6 @@ module JSONAPI
184
201
  end
185
202
  end
186
203
 
187
- def sort_params
188
- @_sort_params ||=
189
- params[:sort].split(',').each_with_object({}) do |criteria, hash|
190
- order, field = criteria.match(/(\-?)(\w+)/i)[1..2]
191
- hash[field] = order == '-' ? :desc : :asc
192
- end
193
- end
194
-
195
204
  def set_pagination(options)
196
205
  page_params = ActionController::Parameters.new(@request.params[:page])
197
206
  if JSONAPI.configuration.default_paginator == :paged
@@ -214,13 +223,17 @@ module JSONAPI
214
223
  (options[:paginate].nil? || options[:paginate])
215
224
  end
216
225
 
217
- def fix_when_hash(records, options)
218
- return [] unless options[:model]
219
- records.map { |hash| options[:model].new(hash) }
226
+ def hash_to_active_record(data, model)
227
+ return data if model.nil?
228
+ coerced = [data].flatten.map { |hash| model.new(hash) }
229
+ data.is_a?(Array) ? coerced : coerced.first
220
230
  rescue ActiveRecord::UnknownAttributeError
221
- ids = records.map { |e| e[:id] || e['id'] }
222
- scope = options[:scope] ? options[:model].send(options[:scope]) : options[:model]
223
- scope.where(id: ids)
231
+ if data.is_a?(Array)
232
+ ids = data.map { |e| e[:id] }
233
+ model.where(id: ids)
234
+ else
235
+ model.find_by(id: data[:id])
236
+ end
224
237
  end
225
238
 
226
239
  def count_records(records, options)
@@ -23,4 +23,3 @@ module JSONAPI::Utils::Exceptions
23
23
  end
24
24
  end
25
25
  end
26
-
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Utils
3
- VERSION = '0.4.2'
3
+ VERSION = '0.4.3'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonapi-utils
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago Guedes
@@ -9,8 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2016-04-10 00:00:00.000000000 Z
12
+ date: 2016-04-28 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: jsonapi-resources
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 0.7.0
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 0.7.0
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: bundler
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -43,16 +57,16 @@ dependencies:
43
57
  name: rspec-rails
44
58
  requirement: !ruby/object:Gem::Requirement
45
59
  requirements:
46
- - - ">="
60
+ - - "~>"
47
61
  - !ruby/object:Gem::Version
48
- version: '0'
62
+ version: '3.1'
49
63
  type: :development
50
64
  prerelease: false
51
65
  version_requirements: !ruby/object:Gem::Requirement
52
66
  requirements:
53
- - - ">="
67
+ - - "~>"
54
68
  - !ruby/object:Gem::Version
55
- version: '0'
69
+ version: '3.1'
56
70
  - !ruby/object:Gem::Dependency
57
71
  name: sqlite3
58
72
  requirement: !ruby/object:Gem::Requirement
@@ -97,18 +111,32 @@ dependencies:
97
111
  version: 0.7.0
98
112
  - !ruby/object:Gem::Dependency
99
113
  name: rails
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '4.2'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '4.2'
126
+ - !ruby/object:Gem::Dependency
127
+ name: pry
100
128
  requirement: !ruby/object:Gem::Requirement
101
129
  requirements:
102
130
  - - ">="
103
131
  - !ruby/object:Gem::Version
104
- version: '4.0'
132
+ version: '0'
105
133
  type: :development
106
134
  prerelease: false
107
135
  version_requirements: !ruby/object:Gem::Requirement
108
136
  requirements:
109
137
  - - ">="
110
138
  - !ruby/object:Gem::Version
111
- version: '4.0'
139
+ version: '0'
112
140
  description: A Rails way to get your API's data serialized through JSON API's specs
113
141
  (http://jsosapi.org)
114
142
  email: