jsonapi-resources 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 +4 -4
- data/Gemfile +2 -2
- data/README.md +103 -71
- data/Rakefile +2 -2
- data/jsonapi-resources.gemspec +2 -2
- data/lib/jsonapi-resources.rb +0 -1
- data/lib/jsonapi/active_record_operations_processor.rb +10 -2
- data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
- data/lib/jsonapi/association.rb +50 -15
- data/lib/jsonapi/callbacks.rb +1 -2
- data/lib/jsonapi/configuration.rb +8 -24
- data/lib/jsonapi/error.rb +1 -2
- data/lib/jsonapi/error_codes.rb +3 -1
- data/lib/jsonapi/exceptions.rb +59 -47
- data/lib/jsonapi/include_directives.rb +11 -11
- data/lib/jsonapi/mime_types.rb +2 -2
- data/lib/jsonapi/operation.rb +28 -11
- data/lib/jsonapi/operations_processor.rb +16 -5
- data/lib/jsonapi/paginator.rb +19 -19
- data/lib/jsonapi/request.rb +175 -196
- data/lib/jsonapi/resource.rb +158 -105
- data/lib/jsonapi/resource_serializer.rb +37 -26
- data/lib/jsonapi/resources/version.rb +2 -2
- data/lib/jsonapi/response_document.rb +5 -4
- data/lib/jsonapi/routing_ext.rb +24 -19
- data/test/controllers/controller_test.rb +261 -31
- data/test/fixtures/active_record.rb +206 -8
- data/test/fixtures/book_comments.yml +2 -1
- data/test/fixtures/books.yml +1 -0
- data/test/fixtures/documents.yml +3 -0
- data/test/fixtures/people.yml +8 -1
- data/test/fixtures/pictures.yml +15 -0
- data/test/fixtures/products.yml +3 -0
- data/test/fixtures/vehicles.yml +8 -0
- data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
- data/test/integration/requests/request_test.rb +14 -3
- data/test/integration/routes/routes_test.rb +47 -0
- data/test/test_helper.rb +27 -4
- data/test/unit/serializer/include_directives_test.rb +5 -0
- data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
- data/test/unit/serializer/serializer_test.rb +19 -1
- metadata +14 -4
@@ -13,7 +13,8 @@ ActiveRecord::Schema.define do
|
|
13
13
|
t.string :email
|
14
14
|
t.datetime :date_joined
|
15
15
|
t.belongs_to :preferences
|
16
|
-
t.integer
|
16
|
+
t.integer :hair_cut_id, index: true
|
17
|
+
t.boolean :book_admin, default: false
|
17
18
|
t.timestamps null: false
|
18
19
|
end
|
19
20
|
|
@@ -96,7 +97,7 @@ ActiveRecord::Schema.define do
|
|
96
97
|
t.string :spouse_name
|
97
98
|
t.text :bio
|
98
99
|
t.float :quality_rating
|
99
|
-
t.decimal :salary, :
|
100
|
+
t.decimal :salary, precision: 12, scale: 2
|
100
101
|
t.datetime :date_time_joined
|
101
102
|
t.date :birthday
|
102
103
|
t.time :bedtime
|
@@ -107,12 +108,14 @@ ActiveRecord::Schema.define do
|
|
107
108
|
create_table :books, force: true do |t|
|
108
109
|
t.string :title
|
109
110
|
t.string :isbn
|
111
|
+
t.boolean :banned, default: false
|
110
112
|
end
|
111
113
|
|
112
114
|
create_table :book_comments, force: true do |t|
|
113
115
|
t.text :body
|
114
116
|
t.belongs_to :book, index: true
|
115
117
|
t.integer :author_id
|
118
|
+
t.boolean :approved, default: true
|
116
119
|
t.timestamps null: false
|
117
120
|
end
|
118
121
|
|
@@ -167,6 +170,28 @@ ActiveRecord::Schema.define do
|
|
167
170
|
t.string :name
|
168
171
|
t.string :status, limit: 10
|
169
172
|
end
|
173
|
+
|
174
|
+
create_table :pictures, force: true do |t|
|
175
|
+
t.string :name
|
176
|
+
t.integer :imageable_id
|
177
|
+
t.string :imageable_type
|
178
|
+
t.timestamps null: false
|
179
|
+
end
|
180
|
+
|
181
|
+
create_table :documents, force: true do |t|
|
182
|
+
t.string :name
|
183
|
+
t.timestamps null: false
|
184
|
+
end
|
185
|
+
|
186
|
+
create_table :products, force: true do |t|
|
187
|
+
t.string :name
|
188
|
+
t.timestamps null: false
|
189
|
+
end
|
190
|
+
|
191
|
+
create_table :vehicles, force: true do |t|
|
192
|
+
t.string :type
|
193
|
+
t.integer :person_id
|
194
|
+
end
|
170
195
|
end
|
171
196
|
|
172
197
|
### MODELS
|
@@ -174,6 +199,7 @@ class Person < ActiveRecord::Base
|
|
174
199
|
has_many :posts, foreign_key: 'author_id'
|
175
200
|
has_many :comments, foreign_key: 'author_id'
|
176
201
|
has_many :expense_entries, foreign_key: 'employee_id', dependent: :restrict_with_exception
|
202
|
+
has_many :vehicles
|
177
203
|
belongs_to :preferences
|
178
204
|
belongs_to :hair_cut
|
179
205
|
|
@@ -289,6 +315,7 @@ end
|
|
289
315
|
|
290
316
|
class Book < ActiveRecord::Base
|
291
317
|
has_many :book_comments
|
318
|
+
has_many :approved_book_comments, -> { where(approved: true) }, class_name: "BookComment"
|
292
319
|
end
|
293
320
|
|
294
321
|
class BookComment < ActiveRecord::Base
|
@@ -343,6 +370,28 @@ end
|
|
343
370
|
class Category < ActiveRecord::Base
|
344
371
|
end
|
345
372
|
|
373
|
+
class Picture < ActiveRecord::Base
|
374
|
+
belongs_to :imageable, polymorphic: true
|
375
|
+
end
|
376
|
+
|
377
|
+
class Vehicle < ActiveRecord::Base
|
378
|
+
belongs_to :person
|
379
|
+
end
|
380
|
+
|
381
|
+
class Car < Vehicle
|
382
|
+
end
|
383
|
+
|
384
|
+
class Boat < Vehicle
|
385
|
+
end
|
386
|
+
|
387
|
+
class Document < ActiveRecord::Base
|
388
|
+
has_many :pictures, as: :imageable
|
389
|
+
end
|
390
|
+
|
391
|
+
class Product < ActiveRecord::Base
|
392
|
+
has_one :picture, as: :imageable
|
393
|
+
end
|
394
|
+
|
346
395
|
### PORO Data - don't do this in a production app
|
347
396
|
$breed_data = BreedData.new
|
348
397
|
$breed_data.add(Breed.new(0, 'persian'))
|
@@ -393,6 +442,18 @@ end
|
|
393
442
|
class CategoriesController < JSONAPI::ResourceController
|
394
443
|
end
|
395
444
|
|
445
|
+
class PicturesController < JSONAPI::ResourceController
|
446
|
+
end
|
447
|
+
|
448
|
+
class DocumentsController < JSONAPI::ResourceController
|
449
|
+
end
|
450
|
+
|
451
|
+
class ProductsController < JSONAPI::ResourceController
|
452
|
+
end
|
453
|
+
|
454
|
+
class ImageablesController < JSONAPI::ResourceController
|
455
|
+
end
|
456
|
+
|
396
457
|
### CONTROLLERS
|
397
458
|
module Api
|
398
459
|
module V1
|
@@ -445,9 +506,15 @@ module Api
|
|
445
506
|
end
|
446
507
|
|
447
508
|
class BooksController < JSONAPI::ResourceController
|
509
|
+
def context
|
510
|
+
{current_user: $test_user}
|
511
|
+
end
|
448
512
|
end
|
449
513
|
|
450
514
|
class BookCommentsController < JSONAPI::ResourceController
|
515
|
+
def context
|
516
|
+
{current_user: $test_user}
|
517
|
+
end
|
451
518
|
end
|
452
519
|
end
|
453
520
|
|
@@ -525,6 +592,7 @@ class PersonResource < JSONAPI::Resource
|
|
525
592
|
|
526
593
|
has_many :comments
|
527
594
|
has_many :posts
|
595
|
+
has_many :vehicles, polymorphic: true
|
528
596
|
|
529
597
|
has_one :preferences
|
530
598
|
has_one :hair_cut
|
@@ -544,6 +612,16 @@ class PersonResource < JSONAPI::Resource
|
|
544
612
|
end
|
545
613
|
end
|
546
614
|
|
615
|
+
class VehicleResource < JSONAPI::Resource
|
616
|
+
has_one :person
|
617
|
+
end
|
618
|
+
|
619
|
+
class CarResource < VehicleResource
|
620
|
+
end
|
621
|
+
|
622
|
+
class BoatResource < VehicleResource
|
623
|
+
end
|
624
|
+
|
547
625
|
class CommentResource < JSONAPI::Resource
|
548
626
|
attributes :body
|
549
627
|
has_one :post
|
@@ -611,6 +689,13 @@ class PostResource < JSONAPI::Resource
|
|
611
689
|
@model.title
|
612
690
|
end
|
613
691
|
|
692
|
+
def title=(title)
|
693
|
+
@model.title = title
|
694
|
+
if title == 'BOOM'
|
695
|
+
raise 'The Server just tested going boom. If this was a real emergency you would be really dead right now.'
|
696
|
+
end
|
697
|
+
end
|
698
|
+
|
614
699
|
filters :title, :author, :tags, :comments
|
615
700
|
filters :id, :ids
|
616
701
|
|
@@ -689,7 +774,7 @@ class BreedResource < JSONAPI::Resource
|
|
689
774
|
attribute :name, format: :title
|
690
775
|
|
691
776
|
# This is unneeded, just here for testing
|
692
|
-
routing_options :
|
777
|
+
routing_options param: :id
|
693
778
|
|
694
779
|
def self.find(filters, options = {})
|
695
780
|
breeds = []
|
@@ -764,6 +849,28 @@ class CategoryResource < JSONAPI::Resource
|
|
764
849
|
filter :status, default: 'active'
|
765
850
|
end
|
766
851
|
|
852
|
+
class PictureResource < JSONAPI::Resource
|
853
|
+
attribute :name
|
854
|
+
has_one :imageable, polymorphic: true
|
855
|
+
end
|
856
|
+
|
857
|
+
class DocumentResource < JSONAPI::Resource
|
858
|
+
attribute :name
|
859
|
+
has_many :pictures
|
860
|
+
end
|
861
|
+
|
862
|
+
class ProductResource < JSONAPI::Resource
|
863
|
+
attribute :name
|
864
|
+
has_one :picture
|
865
|
+
|
866
|
+
def picture_id
|
867
|
+
model.picture.id
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
class ImageableResource < JSONAPI::Resource
|
872
|
+
end
|
873
|
+
|
767
874
|
module Api
|
768
875
|
module V1
|
769
876
|
class WriterResource < JSONAPI::Resource
|
@@ -794,7 +901,6 @@ module Api
|
|
794
901
|
filters :writer
|
795
902
|
end
|
796
903
|
|
797
|
-
# AuthorResource = AuthorResource.dup
|
798
904
|
PersonResource = PersonResource.dup
|
799
905
|
CommentResource = CommentResource.dup
|
800
906
|
TagResource = TagResource.dup
|
@@ -809,27 +915,119 @@ module Api
|
|
809
915
|
EmployeeResource = EmployeeResource.dup
|
810
916
|
FriendResource = FriendResource.dup
|
811
917
|
HairCutResource = HairCutResource.dup
|
918
|
+
VehicleResource = VehicleResource.dup
|
919
|
+
CarResource = CarResource.dup
|
920
|
+
BoatResource = BoatResource.dup
|
812
921
|
end
|
813
922
|
end
|
814
923
|
|
815
924
|
module Api
|
816
925
|
module V2
|
817
926
|
PreferencesResource = PreferencesResource.dup
|
818
|
-
# AuthorResource = AuthorResource.dup
|
819
927
|
PersonResource = PersonResource.dup
|
820
928
|
PostResource = PostResource.dup
|
821
929
|
|
822
930
|
class BookResource < JSONAPI::Resource
|
823
931
|
attribute :title
|
824
|
-
|
932
|
+
attributes :isbn, :banned
|
933
|
+
|
934
|
+
has_many :book_comments, relation_name: -> (options = {}) {
|
935
|
+
context = options[:context]
|
936
|
+
current_user = context ? context[:current_user] : nil
|
825
937
|
|
826
|
-
|
938
|
+
unless current_user && current_user.book_admin
|
939
|
+
:approved_book_comments
|
940
|
+
else
|
941
|
+
:book_comments
|
942
|
+
end
|
943
|
+
}
|
944
|
+
|
945
|
+
filters :banned, :book_comments
|
946
|
+
|
947
|
+
class << self
|
948
|
+
def apply_filter(records, filter, value, options)
|
949
|
+
context = options[:context]
|
950
|
+
current_user = context ? context[:current_user] : nil
|
951
|
+
|
952
|
+
case filter
|
953
|
+
when :banned
|
954
|
+
# Only book admins my filter for banned books
|
955
|
+
if current_user && current_user.book_admin
|
956
|
+
return records.where('books.banned = ?', value[0] == 'true')
|
957
|
+
end
|
958
|
+
else
|
959
|
+
return super(records, filter, value)
|
960
|
+
end
|
961
|
+
end
|
962
|
+
|
963
|
+
def books
|
964
|
+
Book.arel_table
|
965
|
+
end
|
966
|
+
|
967
|
+
def not_banned_books
|
968
|
+
books[:banned].eq(false)
|
969
|
+
end
|
970
|
+
|
971
|
+
def records(options = {})
|
972
|
+
context = options[:context]
|
973
|
+
current_user = context ? context[:current_user] : nil
|
974
|
+
|
975
|
+
records = _model_class
|
976
|
+
# Hide the banned books from people who are not book admins
|
977
|
+
unless current_user && current_user.book_admin
|
978
|
+
records = records.where(not_banned_books)
|
979
|
+
end
|
980
|
+
records
|
981
|
+
end
|
982
|
+
end
|
827
983
|
end
|
828
984
|
|
829
985
|
class BookCommentResource < JSONAPI::Resource
|
830
|
-
attributes :body
|
986
|
+
attributes :body, :approved
|
987
|
+
|
831
988
|
has_one :book
|
832
989
|
has_one :author, class_name: 'Person'
|
990
|
+
|
991
|
+
filters :approved, :book
|
992
|
+
|
993
|
+
class << self
|
994
|
+
def book_comments
|
995
|
+
BookComment.arel_table
|
996
|
+
end
|
997
|
+
|
998
|
+
def approved_comments(approved = true)
|
999
|
+
book_comments[:approved].eq(approved)
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
def apply_filter(records, filter, value, options)
|
1003
|
+
context = options[:context]
|
1004
|
+
current_user = context ? context[:current_user] : nil
|
1005
|
+
|
1006
|
+
case filter
|
1007
|
+
when :approved
|
1008
|
+
# Only book admins my filter for unapproved comments
|
1009
|
+
if current_user && current_user.book_admin
|
1010
|
+
records.where(approved_comments(value[0] == 'true'))
|
1011
|
+
end
|
1012
|
+
else
|
1013
|
+
#:nocov:
|
1014
|
+
return super(records, filter, value)
|
1015
|
+
#:nocov:
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
def records(options = {})
|
1020
|
+
context = options[:context]
|
1021
|
+
current_user = context ? context[:current_user] : nil
|
1022
|
+
|
1023
|
+
records = _model_class
|
1024
|
+
# Hide the unapproved comments from people who are not book admins
|
1025
|
+
unless current_user && current_user.book_admin
|
1026
|
+
records = records.where(approved_comments)
|
1027
|
+
end
|
1028
|
+
records
|
1029
|
+
end
|
1030
|
+
end
|
833
1031
|
end
|
834
1032
|
end
|
835
1033
|
end
|
@@ -4,8 +4,9 @@
|
|
4
4
|
book_<%= book_num %>_comment_<%= comment_num %>:
|
5
5
|
id: <%= comment_id %>
|
6
6
|
body: This is comment <%= comment_num %> on book <%= book_num %>.
|
7
|
-
author_id:
|
7
|
+
author_id: <%= book_num.even? ? comment_id % 2 : (comment_id % 2) + 2 %>
|
8
8
|
book_id: <%= book_num %>
|
9
|
+
approved: <%= comment_num.even? %>
|
9
10
|
<% comment_id = comment_id + 1 %>
|
10
11
|
<% end %>
|
11
12
|
<% end %>
|
data/test/fixtures/books.yml
CHANGED
data/test/fixtures/people.yml
CHANGED
@@ -21,4 +21,11 @@ d:
|
|
21
21
|
id: 4
|
22
22
|
name: Tag Crazy Author
|
23
23
|
email: taggy@xyz.fake
|
24
|
-
date_joined: <%= DateTime.parse('2013-11-30 4:20:00 UTC +00:00') %>
|
24
|
+
date_joined: <%= DateTime.parse('2013-11-30 4:20:00 UTC +00:00') %>
|
25
|
+
|
26
|
+
e:
|
27
|
+
id: 5
|
28
|
+
name: Wilma Librarian
|
29
|
+
email: lib@xyz.fake
|
30
|
+
date_joined: <%= DateTime.parse('2013-11-30 4:20:00 UTC +00:00') %>
|
31
|
+
book_admin: true
|
@@ -1,8 +1,13 @@
|
|
1
1
|
module Helpers
|
2
|
-
module
|
2
|
+
module Assertions
|
3
3
|
def assert_hash_equals(exp, act, msg = nil)
|
4
4
|
msg = message(msg, '') { diff exp, act }
|
5
5
|
assert(matches_hash?(exp, act, {exact: true}), msg)
|
6
6
|
end
|
7
|
+
|
8
|
+
def assert_array_equals(exp, act, msg = nil)
|
9
|
+
msg = message(msg, '') { diff exp, act }
|
10
|
+
assert(matches_array?(exp, act, {exact: true}), msg)
|
11
|
+
end
|
7
12
|
end
|
8
13
|
end
|
@@ -4,6 +4,7 @@ class RequestTest < ActionDispatch::IntegrationTest
|
|
4
4
|
def setup
|
5
5
|
JSONAPI.configuration.json_key_format = :underscored_key
|
6
6
|
JSONAPI.configuration.route_format = :underscored_route
|
7
|
+
$test_user = Person.find(1)
|
7
8
|
end
|
8
9
|
|
9
10
|
def after_teardown
|
@@ -337,7 +338,7 @@ class RequestTest < ActionDispatch::IntegrationTest
|
|
337
338
|
Api::V2::BookResource.paginator :none
|
338
339
|
get '/api/v2/books'
|
339
340
|
assert_equal 200, status
|
340
|
-
assert_equal
|
341
|
+
assert_equal 901, json_response['data'].size
|
341
342
|
end
|
342
343
|
|
343
344
|
def test_pagination_offset_style
|
@@ -385,7 +386,7 @@ class RequestTest < ActionDispatch::IntegrationTest
|
|
385
386
|
get '/api/v2/books/1/book_comments?page[limit]=10'
|
386
387
|
assert_equal 200, status
|
387
388
|
assert_equal 10, json_response['data'].size
|
388
|
-
assert_equal 'This is comment
|
389
|
+
assert_equal 'This is comment 18 on book 1.', json_response['data'][9]['attributes']['body']
|
389
390
|
end
|
390
391
|
|
391
392
|
def test_pagination_related_resources_data_includes
|
@@ -394,9 +395,19 @@ class RequestTest < ActionDispatch::IntegrationTest
|
|
394
395
|
get '/api/v2/books/1/book_comments?page[limit]=10&include=author,book'
|
395
396
|
assert_equal 200, status
|
396
397
|
assert_equal 10, json_response['data'].size
|
397
|
-
assert_equal 'This is comment
|
398
|
+
assert_equal 'This is comment 18 on book 1.', json_response['data'][9]['attributes']['body']
|
398
399
|
end
|
399
400
|
|
401
|
+
# def test_pagination_related_resources_data_includes
|
402
|
+
# Api::V2::BookResource.paginator :none
|
403
|
+
# Api::V2::BookCommentResource.paginator :none
|
404
|
+
# get '/api/v2/books?filter[]'
|
405
|
+
# assert_equal 200, status
|
406
|
+
# assert_equal 10, json_response['data'].size
|
407
|
+
# assert_equal 'This is comment 18 on book 1.', json_response['data'][9]['attributes']['body']
|
408
|
+
# end
|
409
|
+
|
410
|
+
|
400
411
|
def test_flow_self
|
401
412
|
get '/posts'
|
402
413
|
assert_equal 200, status
|