jsonapi-utils 0.4.2 → 0.4.3

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 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: