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