jsonapi-resources 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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +103 -71
  4. data/Rakefile +2 -2
  5. data/jsonapi-resources.gemspec +2 -2
  6. data/lib/jsonapi-resources.rb +0 -1
  7. data/lib/jsonapi/active_record_operations_processor.rb +10 -2
  8. data/lib/jsonapi/acts_as_resource_controller.rb +26 -24
  9. data/lib/jsonapi/association.rb +50 -15
  10. data/lib/jsonapi/callbacks.rb +1 -2
  11. data/lib/jsonapi/configuration.rb +8 -24
  12. data/lib/jsonapi/error.rb +1 -2
  13. data/lib/jsonapi/error_codes.rb +3 -1
  14. data/lib/jsonapi/exceptions.rb +59 -47
  15. data/lib/jsonapi/include_directives.rb +11 -11
  16. data/lib/jsonapi/mime_types.rb +2 -2
  17. data/lib/jsonapi/operation.rb +28 -11
  18. data/lib/jsonapi/operations_processor.rb +16 -5
  19. data/lib/jsonapi/paginator.rb +19 -19
  20. data/lib/jsonapi/request.rb +175 -196
  21. data/lib/jsonapi/resource.rb +158 -105
  22. data/lib/jsonapi/resource_serializer.rb +37 -26
  23. data/lib/jsonapi/resources/version.rb +2 -2
  24. data/lib/jsonapi/response_document.rb +5 -4
  25. data/lib/jsonapi/routing_ext.rb +24 -19
  26. data/test/controllers/controller_test.rb +261 -31
  27. data/test/fixtures/active_record.rb +206 -8
  28. data/test/fixtures/book_comments.yml +2 -1
  29. data/test/fixtures/books.yml +1 -0
  30. data/test/fixtures/documents.yml +3 -0
  31. data/test/fixtures/people.yml +8 -1
  32. data/test/fixtures/pictures.yml +15 -0
  33. data/test/fixtures/products.yml +3 -0
  34. data/test/fixtures/vehicles.yml +8 -0
  35. data/test/helpers/{hash_helpers.rb → assertions.rb} +6 -1
  36. data/test/integration/requests/request_test.rb +14 -3
  37. data/test/integration/routes/routes_test.rb +47 -0
  38. data/test/test_helper.rb +27 -4
  39. data/test/unit/serializer/include_directives_test.rb +5 -0
  40. data/test/unit/serializer/polymorphic_serializer_test.rb +384 -0
  41. data/test/unit/serializer/serializer_test.rb +19 -1
  42. 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 :hair_cut_id, index: true
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, :precision => 12, :scale => 2
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 :param => :id
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
- attribute :isbn
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
- has_many :book_comments
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: 1
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 %>
@@ -3,4 +3,5 @@ book_<%= book_num %>:
3
3
  id: <%= book_num %>
4
4
  title: Book <%= book_num %>
5
5
  isbn: 12345-<%= book_num %>-6789
6
+ banned: <%= book_num > 600 && book_num < 700 %>
6
7
  <% end %>
@@ -0,0 +1,3 @@
1
+ document_1:
2
+ id: 1
3
+ name: Company Brochure
@@ -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
@@ -0,0 +1,15 @@
1
+ picture_1:
2
+ id: 1
3
+ name: enterprise_gizmo.jpg
4
+ imageable_id: 1
5
+ imageable_type: Product
6
+
7
+ picture_2:
8
+ id: 2
9
+ name: company_brochure.jpg
10
+ imageable_id: 1
11
+ imageable_type: Document
12
+
13
+ picture_3:
14
+ id: 3
15
+ name: group_photo.jpg
@@ -0,0 +1,3 @@
1
+ product_1:
2
+ id: 1
3
+ name: Enterprise Gizmo
@@ -0,0 +1,8 @@
1
+ car:
2
+ id: 1
3
+ type: Car
4
+ person_id: 1
5
+ boat:
6
+ id: 2
7
+ type: Boat
8
+ person_id: 1
@@ -1,8 +1,13 @@
1
1
  module Helpers
2
- module HashHelpers
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 1000, json_response['data'].size
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 9 on book 1.', json_response['data'][9]['attributes']['body']
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 9 on book 1.', json_response['data'][9]['attributes']['body']
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