jsonapi-resources 0.0.13 → 0.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/README.md +50 -20
- data/lib/jsonapi/formatter.rb +1 -6
- data/lib/jsonapi/request.rb +8 -4
- data/lib/jsonapi/resource.rb +11 -6
- data/lib/jsonapi/resource_for.rb +7 -3
- data/lib/jsonapi/resources/version.rb +1 -1
- data/test/controllers/controller_test.rb +21 -3
- data/test/fixtures/active_record.rb +45 -2
- data/test/test_helper.rb +3 -0
- data/test/unit/serializer/serializer_test.rb +43 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6700d2a30e18dc6391ba4aa8bc0ecdd517e2fdfb
|
4
|
+
data.tar.gz: ad330dfe0d8fdc5da07d7ee40db08e892293296d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 893a1947cd78da813d77e69c92dafbaac07b50c8dab2ab495ebdf49da50c082f8123c99361cf95793e606dd4916d140401566a4465829bf137aa1f86144589db
|
7
|
+
data.tar.gz: d00fb87cc0c3a787d28fb68bc1d8072b6dc30143108dc3672770b19704430126511bf0aa73d2b86434546812ff6a4d194d710eb521a6f9d88840d8faeeef59d6
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -260,42 +260,72 @@ end
|
|
260
260
|
|
261
261
|
##### Finders
|
262
262
|
|
263
|
-
Basic finding by filters is supported by resources.
|
263
|
+
Basic finding by filters is supported by resources. This is implemented in the `find`, `find_by_key` and `find_by_keys` finder methods. Currently this is implemented for ActiveRecord based resources. The finder methods rely on the `records` method to get an Arel relation. It is therefore possible to override `records` to affect the three find related methods.
|
264
264
|
|
265
|
-
|
265
|
+
###### Customizing base records for finder methods
|
266
266
|
|
267
|
-
|
268
|
-
class AuthorResource < JSONAPI::Resource
|
269
|
-
attributes :id, :name
|
270
|
-
model_name 'Person'
|
271
|
-
has_many :posts
|
267
|
+
If you need to change the base records on which `find`, `find_by_key` and `find_by_keys` operate, you can override the `records` method on the resource class.
|
272
268
|
|
273
|
-
|
269
|
+
For example to allow a user to only retrieve his own posts you can do the following:
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
class PostResource < JSONAPI::Resource
|
273
|
+
attribute :id, :title, :body
|
274
274
|
|
275
|
-
def self.
|
275
|
+
def self.records(options = {})
|
276
276
|
context = options[:context]
|
277
|
-
|
277
|
+
context.current_user.posts
|
278
|
+
end
|
279
|
+
end
|
280
|
+
```
|
278
281
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
+
###### Applying Filters
|
283
|
+
|
284
|
+
The `apply_filter` method is called to apply each filter to the Arel relation. You may override this method to gain control over how the filters are applied to the Arel relation.
|
285
|
+
|
286
|
+
For example to change how a
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
def apply_filter(records, filter, value)
|
290
|
+
case filter
|
291
|
+
when :visibility
|
292
|
+
records.where('users.publicly_visible = ?', value == :public)
|
293
|
+
when :last_name, :first_name, :name
|
294
|
+
if value.is_a?(Array)
|
295
|
+
value.each do |val|
|
296
|
+
records = records.where(_model_class.arel_table[filter].matches(val))
|
297
|
+
end
|
298
|
+
return records
|
299
|
+
else
|
300
|
+
records.where(_model_class.arel_table[filter].matches(value))
|
301
|
+
end
|
302
|
+
else
|
303
|
+
return super(records, filter, value)
|
282
304
|
end
|
283
305
|
end
|
284
306
|
```
|
285
307
|
|
286
|
-
|
308
|
+
###### Override finder methods
|
287
309
|
|
288
|
-
|
310
|
+
Finally if you have more complex requirements for finding you can override the `find`, `find_by_key` and `find_by_keys` methods on the resource class.
|
289
311
|
|
290
|
-
|
312
|
+
Here's an example that defers the `find` operation to a `current_user` set on the `context` option:
|
291
313
|
|
292
314
|
```ruby
|
293
|
-
class
|
294
|
-
|
315
|
+
class AuthorResource < JSONAPI::Resource
|
316
|
+
attributes :id, :name
|
317
|
+
model_name 'Person'
|
318
|
+
has_many :posts
|
295
319
|
|
296
|
-
|
320
|
+
filter :name
|
321
|
+
|
322
|
+
def self.find(filters, options = {})
|
297
323
|
context = options[:context]
|
298
|
-
context.current_user.
|
324
|
+
authors = context.current_user.find_authors(filters)
|
325
|
+
|
326
|
+
return authors.map do |author|
|
327
|
+
self.new(author)
|
328
|
+
end
|
299
329
|
end
|
300
330
|
end
|
301
331
|
```
|
data/lib/jsonapi/formatter.rb
CHANGED
@@ -97,12 +97,7 @@ end
|
|
97
97
|
class DefaultValueFormatter < JSONAPI::ValueFormatter
|
98
98
|
class << self
|
99
99
|
def format(raw_value, context)
|
100
|
-
|
101
|
-
when String, Integer
|
102
|
-
return raw_value
|
103
|
-
else
|
104
|
-
return raw_value.to_s
|
105
|
-
end
|
100
|
+
raw_value
|
106
101
|
end
|
107
102
|
end
|
108
103
|
end
|
data/lib/jsonapi/request.rb
CHANGED
@@ -20,7 +20,7 @@ module JSONAPI
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def setup(params)
|
23
|
-
@resource_klass ||= self.class.resource_for(params[:controller]) if params[:controller]
|
23
|
+
@resource_klass ||= self.class.resource_for(params[:controller].split('/').last) if params[:controller]
|
24
24
|
|
25
25
|
unless params.nil?
|
26
26
|
case params[:action]
|
@@ -76,10 +76,14 @@ module JSONAPI
|
|
76
76
|
fields.each do |type, values|
|
77
77
|
underscored_type = unformat_key(type)
|
78
78
|
fields[type] = []
|
79
|
-
|
80
|
-
|
81
|
-
|
79
|
+
begin
|
80
|
+
type_resource = self.class.resource_for(underscored_type)
|
81
|
+
rescue NameError
|
82
82
|
@errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
|
83
|
+
end
|
84
|
+
if type_resource.nil? || !(@resource_klass._type == underscored_type ||
|
85
|
+
@resource_klass._has_association?(underscored_type))
|
86
|
+
@errors.concat(JSONAPI::Exceptions::InvalidResource.new(type).errors)
|
83
87
|
else
|
84
88
|
unless values.nil?
|
85
89
|
valid_fields = type_resource.fields.collect {|key| format_key(key)}
|
data/lib/jsonapi/resource.rb
CHANGED
@@ -217,29 +217,34 @@ module JSONAPI
|
|
217
217
|
_associations.keys | _attributes.keys
|
218
218
|
end
|
219
219
|
|
220
|
+
def apply_filter(records, filter, value)
|
221
|
+
records.where(filter => value)
|
222
|
+
end
|
223
|
+
|
220
224
|
# Override this method if you have more complex requirements than this basic find method provides
|
221
225
|
def find(filters, options = {})
|
222
226
|
context = options[:context]
|
223
227
|
sort_params = options.fetch(:sort_params) { [] }
|
224
228
|
includes = []
|
225
|
-
|
229
|
+
|
230
|
+
records = records(options)
|
226
231
|
|
227
232
|
filters.each do |filter, value|
|
228
233
|
if _associations.include?(filter)
|
229
234
|
if _associations[filter].is_a?(JSONAPI::Association::HasMany)
|
230
235
|
includes.push(filter)
|
231
|
-
|
236
|
+
records = apply_filter(records, "#{filter}.#{_associations[filter].primary_key}", value)
|
232
237
|
else
|
233
|
-
|
238
|
+
records = apply_filter(records, "#{_associations[filter].foreign_key}", value)
|
234
239
|
end
|
235
240
|
else
|
236
|
-
|
241
|
+
records = apply_filter(records, filter, value)
|
237
242
|
end
|
238
243
|
end
|
239
244
|
|
240
245
|
resources = []
|
241
246
|
order_options = construct_order_options(sort_params)
|
242
|
-
records
|
247
|
+
records.order(order_options).includes(includes).each do |model|
|
243
248
|
resources.push self.new(model, context)
|
244
249
|
end
|
245
250
|
|
@@ -260,7 +265,7 @@ module JSONAPI
|
|
260
265
|
_models = records(options).where({_primary_key => keys})
|
261
266
|
|
262
267
|
unless _models.length == keys.length
|
263
|
-
key = (keys - _models.pluck(
|
268
|
+
key = (keys - _models.pluck(_primary_key).map(&:to_s)).first
|
264
269
|
raise JSONAPI::Exceptions::RecordNotFound.new(key)
|
265
270
|
end
|
266
271
|
|
data/lib/jsonapi/resource_for.rb
CHANGED
@@ -11,15 +11,19 @@ module JSONAPI
|
|
11
11
|
resource_name = JSONAPI::Resource._resource_name_from_type(type)
|
12
12
|
Object.const_get resource_name if resource_name
|
13
13
|
rescue NameError
|
14
|
-
|
14
|
+
raise NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
|
15
15
|
end
|
16
16
|
else
|
17
17
|
def resource_for(type)
|
18
18
|
resource_name = JSONAPI::Resource._resource_name_from_type(type)
|
19
|
-
resource_name.safe_constantize if resource_name
|
19
|
+
resource = resource_name.safe_constantize if resource_name
|
20
|
+
if resource.nil?
|
21
|
+
raise NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
|
22
|
+
end
|
23
|
+
resource
|
20
24
|
end
|
21
25
|
end
|
22
26
|
# :nocov:
|
23
27
|
end
|
24
28
|
end
|
25
|
-
end
|
29
|
+
end
|
@@ -1106,7 +1106,7 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
|
|
1106
1106
|
assert json_response['expense_entries'].is_a?(Hash)
|
1107
1107
|
assert_equal 3, json_response['expense_entries']['links']['employee']
|
1108
1108
|
assert_equal 'USD', json_response['expense_entries']['links']['iso_currency']
|
1109
|
-
assert_equal
|
1109
|
+
assert_equal 50.58, json_response['expense_entries']['cost']
|
1110
1110
|
|
1111
1111
|
delete :destroy, {id: json_response['expense_entries']['id']}
|
1112
1112
|
assert_response :no_content
|
@@ -1133,7 +1133,7 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
|
|
1133
1133
|
assert json_response['expenseEntries'].is_a?(Hash)
|
1134
1134
|
assert_equal 3, json_response['expenseEntries']['links']['employee']
|
1135
1135
|
assert_equal 'USD', json_response['expenseEntries']['links']['isoCurrency']
|
1136
|
-
assert_equal
|
1136
|
+
assert_equal 50.58, json_response['expenseEntries']['cost']
|
1137
1137
|
|
1138
1138
|
delete :destroy, {id: json_response['expenseEntries']['id']}
|
1139
1139
|
assert_response :no_content
|
@@ -1160,7 +1160,7 @@ class ExpenseEntriesControllerTest < ActionController::TestCase
|
|
1160
1160
|
assert json_response['expense-entries'].is_a?(Hash)
|
1161
1161
|
assert_equal 3, json_response['expense-entries']['links']['employee']
|
1162
1162
|
assert_equal 'USD', json_response['expense-entries']['links']['iso-currency']
|
1163
|
-
assert_equal
|
1163
|
+
assert_equal 50.58, json_response['expense-entries']['cost']
|
1164
1164
|
|
1165
1165
|
delete :destroy, {id: json_response['expense-entries']['id']}
|
1166
1166
|
assert_response :no_content
|
@@ -1466,3 +1466,21 @@ class Api::V2::PreferencesControllerTest < ActionController::TestCase
|
|
1466
1466
|
assert_response :success
|
1467
1467
|
end
|
1468
1468
|
end
|
1469
|
+
|
1470
|
+
class FactsControllerTest < ActionController::TestCase
|
1471
|
+
def test_type_formatting
|
1472
|
+
get :show, {id: '1'}
|
1473
|
+
assert_response :success
|
1474
|
+
assert json_response['facts'].is_a?(Hash)
|
1475
|
+
assert_equal 'Jane Author', json_response['facts']['spouseName']
|
1476
|
+
assert_equal 'First man to run across Antartica.', json_response['facts']['bio']
|
1477
|
+
assert_equal 23.89/45.6, json_response['facts']['qualityRating']
|
1478
|
+
assert_equal 47000.56, json_response['facts']['salary']
|
1479
|
+
assert_equal '2013-08-07T20:25:00.000Z', json_response['facts']['dateTimeJoined']
|
1480
|
+
assert_equal '1965-06-30', json_response['facts']['birthday']
|
1481
|
+
assert_equal '2000-01-01T20:00:00Z', json_response['facts']['bedtime']
|
1482
|
+
assert_equal 'abc', json_response['facts']['photo']
|
1483
|
+
assert_equal false, json_response['facts']['cool']
|
1484
|
+
end
|
1485
|
+
end
|
1486
|
+
|
@@ -92,6 +92,19 @@ ActiveRecord::Schema.define do
|
|
92
92
|
t.integer :person_id
|
93
93
|
t.boolean :advanced_mode, default: false
|
94
94
|
end
|
95
|
+
|
96
|
+
create_table :facts, force: true do |t|
|
97
|
+
t.integer :person_id
|
98
|
+
t.string :spouse_name
|
99
|
+
t.text :bio
|
100
|
+
t.float :quality_rating
|
101
|
+
t.decimal :salary, :precision => 12, :scale => 2
|
102
|
+
t.datetime :date_time_joined
|
103
|
+
t.date :birthday
|
104
|
+
t.time :bedtime
|
105
|
+
t.binary :photo, limit: 1.kilobyte
|
106
|
+
t.boolean :cool
|
107
|
+
end
|
95
108
|
end
|
96
109
|
|
97
110
|
### MODELS
|
@@ -160,6 +173,9 @@ class Preferences < ActiveRecord::Base
|
|
160
173
|
has_many :friends, class_name: 'Person'
|
161
174
|
end
|
162
175
|
|
176
|
+
class Fact < ActiveRecord::Base
|
177
|
+
end
|
178
|
+
|
163
179
|
class Breed
|
164
180
|
|
165
181
|
def initialize(id = nil, name = nil)
|
@@ -236,6 +252,9 @@ end
|
|
236
252
|
class BreedsController < JSONAPI::ResourceController
|
237
253
|
end
|
238
254
|
|
255
|
+
class FactsController < JSONAPI::ResourceController
|
256
|
+
end
|
257
|
+
|
239
258
|
### CONTROLLERS
|
240
259
|
module Api
|
241
260
|
module V1
|
@@ -455,7 +474,7 @@ class BreedResource < JSONAPI::Resource
|
|
455
474
|
# This is unneeded, just here for testing
|
456
475
|
routing_options :param => :id
|
457
476
|
|
458
|
-
def self.find(
|
477
|
+
def self.find(filters, options = {})
|
459
478
|
breeds = []
|
460
479
|
$breed_data.breeds.values.each do |breed|
|
461
480
|
breeds.push(BreedResource.new(breed, options[:context]))
|
@@ -509,11 +528,24 @@ class PreferencesResource < JSONAPI::Resource
|
|
509
528
|
has_one :author, foreign_key: :person_id
|
510
529
|
has_many :friends
|
511
530
|
|
512
|
-
def self.find_by_key(key,
|
531
|
+
def self.find_by_key(key, options = {})
|
513
532
|
new(Preferences.first)
|
514
533
|
end
|
515
534
|
end
|
516
535
|
|
536
|
+
class FactResource < JSONAPI::Resource
|
537
|
+
attribute :id
|
538
|
+
attribute :spouse_name
|
539
|
+
attribute :bio
|
540
|
+
attribute :quality_rating
|
541
|
+
attribute :salary
|
542
|
+
attribute :date_time_joined
|
543
|
+
attribute :birthday
|
544
|
+
attribute :bedtime
|
545
|
+
attribute :photo
|
546
|
+
attribute :cool
|
547
|
+
end
|
548
|
+
|
517
549
|
warn 'start testing Name Collisions'
|
518
550
|
# The name collisions only emmit warnings. Exceptions would change the flow of the tests
|
519
551
|
|
@@ -685,3 +717,14 @@ betaz = Planet.create(name: 'Beta X', description: 'Newly discovered Planet Z',
|
|
685
717
|
betaw = Planet.create(name: 'Beta W', description: 'Newly discovered Planet W')
|
686
718
|
|
687
719
|
preference = Preferences.create
|
720
|
+
|
721
|
+
fact = Fact.create(spouse_name: 'Jane Author',
|
722
|
+
bio: 'First man to run across Antartica.',
|
723
|
+
quality_rating: 23.89/45.6,
|
724
|
+
salary: BigDecimal('47000.56'),
|
725
|
+
date_time_joined: DateTime.parse('2013-08-07 20:25:00 UTC +00:00'),
|
726
|
+
birthday: Date.parse('1965-06-30'),
|
727
|
+
bedtime: Time.parse('2000-01-01 20:00:00 UTC +00:00'),
|
728
|
+
photo: "abc",
|
729
|
+
cool: false
|
730
|
+
)
|
data/test/test_helper.rb
CHANGED
@@ -31,6 +31,8 @@ class TestApp < Rails::Application
|
|
31
31
|
config.session_store :cookie_store, key: 'session'
|
32
32
|
config.secret_key_base = 'secret'
|
33
33
|
|
34
|
+
ActiveSupport::JSON::Encoding.encode_big_decimal_as_string = false
|
35
|
+
|
34
36
|
#Raise errors on unsupported parameters
|
35
37
|
config.action_controller.action_on_unpermitted_parameters = :raise
|
36
38
|
end
|
@@ -53,6 +55,7 @@ TestApp.routes.draw do
|
|
53
55
|
jsonapi_resources :planet_types
|
54
56
|
jsonapi_resources :moons
|
55
57
|
jsonapi_resources :preferences
|
58
|
+
jsonapi_resources :facts
|
56
59
|
|
57
60
|
namespace :api do
|
58
61
|
namespace :v1 do
|
@@ -505,7 +505,7 @@ class SerializerTest < MiniTest::Unit::TestCase
|
|
505
505
|
expenseEntries: {
|
506
506
|
id: 1,
|
507
507
|
transactionDate: '04/15/2014',
|
508
|
-
cost:
|
508
|
+
cost: 12.05,
|
509
509
|
links: {
|
510
510
|
isoCurrency: 'USD',
|
511
511
|
employee: 3
|
@@ -602,4 +602,46 @@ class SerializerTest < MiniTest::Unit::TestCase
|
|
602
602
|
assert_match /\"planetType\":null/, json
|
603
603
|
assert_match /\"moons\":\[\]/, json
|
604
604
|
end
|
605
|
+
|
606
|
+
def test_serializer_booleans
|
607
|
+
JSONAPI.configuration.json_key_format = :underscored_key
|
608
|
+
|
609
|
+
preferences = PreferencesResource.new(Preferences.find(1))
|
610
|
+
|
611
|
+
assert_hash_equals(
|
612
|
+
{
|
613
|
+
preferences: {
|
614
|
+
id: 1,
|
615
|
+
advanced_mode: false,
|
616
|
+
links: {
|
617
|
+
author: nil,
|
618
|
+
friends: []
|
619
|
+
}
|
620
|
+
}
|
621
|
+
},
|
622
|
+
JSONAPI::ResourceSerializer.new(PreferencesResource).serialize_to_hash(preferences))
|
623
|
+
end
|
624
|
+
|
625
|
+
def test_serializer_data_types
|
626
|
+
JSONAPI.configuration.json_key_format = :underscored_key
|
627
|
+
|
628
|
+
facts = FactResource.new(Fact.find(1))
|
629
|
+
|
630
|
+
assert_hash_equals(
|
631
|
+
{
|
632
|
+
facts: {
|
633
|
+
id: 1,
|
634
|
+
spouse_name: 'Jane Author',
|
635
|
+
bio: 'First man to run across Antartica.',
|
636
|
+
quality_rating: 23.89/45.6,
|
637
|
+
salary: BigDecimal('47000.56', 30),
|
638
|
+
date_time_joined: DateTime.parse('2013-08-07 20:25:00 UTC +00:00'),
|
639
|
+
birthday: Date.parse('1965-06-30'),
|
640
|
+
bedtime: Time.parse('2000-01-01 20:00:00 UTC +00:00'), #DB seems to set the date to 2001-01-01 for time types
|
641
|
+
photo: "abc",
|
642
|
+
cool: false
|
643
|
+
}
|
644
|
+
},
|
645
|
+
JSONAPI::ResourceSerializer.new(FactResource).serialize_to_hash(facts))
|
646
|
+
end
|
605
647
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-resources
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Gebhardt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|