jsonapi-resources 0.6.0 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f1f659549f6b8804be7a7bed6ca3a359a37fc5e
4
- data.tar.gz: 3703986e36f362f348ce463d4977b268171d8f0b
3
+ metadata.gz: 885923e290e5ac1db4d49ed616f8f93b978edabe
4
+ data.tar.gz: f9ba1571591967f00bd09b274b2aae34b01153e0
5
5
  SHA512:
6
- metadata.gz: 7b368231330b4c2a21bcfde117c7c4797a16c3bf145e20525b6fcdff429fde0a276e2b9b95b0e265dee890f37b4862529e7780f4caef90886d99ea36a4877af5
7
- data.tar.gz: 4518564dceb728f5a594dd93c9fcf22de5051ad6397a4282513ad8f8f7d27c7bae355cae3bc8a9fd593fc11bdfdb91885f878a17a809d0150d1ef4daa4a7a174
6
+ metadata.gz: 305b0ae0b9f37ec829b178769044957f12f90c0272b69339b930deabac8f8f34bf310539597eeb01adc911c3bb897fd9aa60b52b3b077c9d925e926fa9fca157
7
+ data.tar.gz: d9716961f3daeb72d56ae12b37c42960f8dcd99042f8cf6d884d67081ceebfc1ae20510eef1d682fe504e726433ce443dc5007c6cd3da0a1065c1eef708948c3
data/README.md CHANGED
@@ -106,6 +106,50 @@ class ContactResource < BaseResource
106
106
  end
107
107
  ```
108
108
 
109
+ ##### Immutable Resources
110
+
111
+ Resources that are immutable should be declared as such with the `immutable` method. Immutable resources will only
112
+ generate routes for `index`, `show` and `show_relationship`.
113
+
114
+ ###### Immutable for Readonly
115
+
116
+ Some resources are read-only and are not to be modified through the API. Declaring a resource as immutable prevents
117
+ creation of routes that allow modification of the resource.
118
+
119
+ ###### Immutable Heterogeneous Collections
120
+
121
+ Immutable resources can be used as the basis for a heterogeneous collection. Resources in heterogeneous collections can
122
+ still be mutated through their own type-specific endpoints.
123
+
124
+ ```ruby
125
+ class VehicleResource < JSONAPI::Resource
126
+ immutable
127
+
128
+ has_one :owner
129
+ attributes :make, :model, :serial_number
130
+ end
131
+
132
+ class CarResource < VehicleResource
133
+ attributes :drive_layout
134
+ has_one :driver
135
+ end
136
+
137
+ class BoatResource < VehicleResource
138
+ attributes :length_at_water_line
139
+ has_one :captain
140
+ end
141
+
142
+ # routes
143
+ jsonapi_resources :vehicles
144
+ jsonapi_resources :cars
145
+ jsonapi_resources :boats
146
+
147
+ ```
148
+
149
+ In the above example vehicles are immutable. A call to `/vehicles` or `/vehicles/1` will return vehicles with types
150
+ of either `car` or `boat`. But calls to PUT or POST a `car` must be made to `/cars`. The rails models backing the above
151
+ code use Single Table Inheritance.
152
+
109
153
  #### Attributes
110
154
 
111
155
  Any of a resource's attributes that are accessible must be explicitly declared. Single attributes can be declared using
@@ -516,7 +560,7 @@ section for additional details on raising errors.
516
560
  class BaseResource < JSONAPI::Resource
517
561
  def records_for(relation_name)
518
562
  context = options[:context]
519
- records = model.public_send(relation_name)
563
+ records = _model.public_send(relation_name)
520
564
 
521
565
  unless context[:current_user].can_view?(records)
522
566
  raise NotAuthorizedError
@@ -779,12 +823,12 @@ Callbacks can be defined for the following `JSONAPI::Resource` events:
779
823
  - `:update`
780
824
  - `:remove`
781
825
  - `:save`
782
- - `:create_has_many_link`
783
- - `:replace_has_many_links`
784
- - `:create_has_one_link`
785
- - `:replace_has_one_link`
786
- - `:remove_has_many_link`
787
- - `:remove_has_one_link`
826
+ - `:create_to_many_link`
827
+ - `:replace_to_many_links`
828
+ - `:create_to_one_link`
829
+ - `:replace_to_one_link`
830
+ - `:remove_to_many_link`
831
+ - `:remove_to_one_link`
788
832
  - `:replace_fields`
789
833
 
790
834
  ##### `JSONAPI::OperationsProcessor` Callbacks
@@ -800,11 +844,11 @@ Callbacks can also be defined for `JSONAPI::OperationsProcessor` events:
800
844
  - `:create_resource_operation`: A `create_resource_operation`.
801
845
  - `:remove_resource_operation`: A `remove_resource_operation`.
802
846
  - `:replace_fields_operation`: A `replace_fields_operation`.
803
- - `:replace_has_one_relationship_operation`: A `replace_has_one_relationship_operation`.
804
- - `:create_has_many_relationship_operation`: A `create_has_many_relationship_operation`.
805
- - `:replace_has_many_relationship_operation`: A `replace_has_many_relationship_operation`.
806
- - `:remove_has_many_relationship_operation`: A `remove_has_many_relationship_operation`.
807
- - `:remove_has_one_relationship_operation`: A `remove_has_one_relationship_operation`.
847
+ - `:replace_to_one_relationship_operation`: A `replace_to_one_relationship_operation`.
848
+ - `:create_to_many_relationship_operation`: A `create_to_many_relationship_operation`.
849
+ - `:replace_to_many_relationship_operation`: A `replace_to_many_relationship_operation`.
850
+ - `:remove_to_many_relationship_operation`: A `remove_to_many_relationship_operation`.
851
+ - `:remove_to_one_relationship_operation`: A `remove_to_one_relationship_operation`.
808
852
 
809
853
  The operation callbacks have access to two meta data hashes, `@operations_meta` and `@operation_meta`, two links hashes,
810
854
  `@operations_links` and `@operation_links`, the full list of `@operations`, each individual `@operation` and the
@@ -297,7 +297,7 @@ module JSONAPI
297
297
  attr_reader :error_messages, :resource_relationships
298
298
 
299
299
  def initialize(resource)
300
- @error_messages = resource.model.errors.messages
300
+ @error_messages = resource._model.errors.messages
301
301
  @resource_relationships = resource.class._relationships.keys
302
302
  @key_formatter = JSONAPI.configuration.key_formatter
303
303
  end
@@ -148,7 +148,7 @@ class PagedPaginator < JSONAPI::Paginator
148
148
  }
149
149
 
150
150
  if @number > 1
151
- links_page_params['previous'] = {
151
+ links_page_params['prev'] = {
152
152
  'number' => @number - 1,
153
153
  'size' => @size
154
154
  }
@@ -7,7 +7,6 @@ module JSONAPI
7
7
  @@resource_types = {}
8
8
 
9
9
  attr_reader :context
10
- attr_reader :model
11
10
 
12
11
  define_jsonapi_resources_callbacks :create,
13
12
  :update,
@@ -27,8 +26,12 @@ module JSONAPI
27
26
  @context = context
28
27
  end
29
28
 
29
+ def _model
30
+ @model
31
+ end
32
+
30
33
  def id
31
- model.public_send(self.class._primary_key)
34
+ _model.public_send(self.class._primary_key)
32
35
  end
33
36
 
34
37
  def is_new?
@@ -112,7 +115,7 @@ module JSONAPI
112
115
  # Override this on a resource to customize how the associated records
113
116
  # are fetched for a model. Particularly helpful for authorization.
114
117
  def records_for(relation_name)
115
- model.public_send relation_name
118
+ _model.public_send relation_name
116
119
  end
117
120
 
118
121
  private
@@ -169,7 +172,7 @@ module JSONAPI
169
172
  # TODO: Add option to skip relations that already exist instead of returning an error?
170
173
  relation = @model.public_send(relation_name).where(relationship.primary_key => relationship_key_value).first
171
174
  if relation.nil?
172
- @model.public_send(relation_name) << related_resource.model
175
+ @model.public_send(relation_name) << related_resource._model
173
176
  else
174
177
  fail JSONAPI::Exceptions::HasManyRelationExists.new(relationship_key_value)
175
178
  end
@@ -198,8 +201,8 @@ module JSONAPI
198
201
  def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
199
202
  relationship = self.class._relationships[relationship_type.to_sym]
200
203
 
201
- model.public_send("#{relationship.foreign_key}=", key_value)
202
- model.public_send("#{relationship.polymorphic_type}=", key_type.to_s.classify)
204
+ _model.public_send("#{relationship.foreign_key}=", key_value)
205
+ _model.public_send("#{relationship.polymorphic_type}=", key_type.to_s.classify)
203
206
 
204
207
  @save_needed = true
205
208
 
@@ -258,6 +261,7 @@ module JSONAPI
258
261
  class << self
259
262
  def inherited(base)
260
263
  base.abstract(false)
264
+ base.immutable(false)
261
265
  base._attributes = (_attributes || {}).dup
262
266
  base._relationships = (_relationships || {}).dup
263
267
  base._allowed_filters = (_allowed_filters || Set.new).dup
@@ -499,7 +503,7 @@ module JSONAPI
499
503
 
500
504
  resources = []
501
505
  records.each do |model|
502
- resources.push new(model, context)
506
+ resources.push resource_for(resource_type_for(model)).new(model, context)
503
507
  end
504
508
 
505
509
  resources
@@ -511,7 +515,11 @@ module JSONAPI
511
515
  records = apply_includes(records, options)
512
516
  model = records.where({_primary_key => key}).first
513
517
  fail JSONAPI::Exceptions::RecordNotFound.new(key) if model.nil?
514
- new(model, context)
518
+ resource_for(resource_type_for(model)).new(model, context)
519
+ end
520
+
521
+ def resource_type_for(model)
522
+ self.module_path + model.class.to_s.underscore
515
523
  end
516
524
 
517
525
  # Override this method if you want to customize the relation for
@@ -554,40 +562,28 @@ module JSONAPI
554
562
 
555
563
  def verify_key(key, context = nil)
556
564
  key_type = resource_key_type
557
- verification_proc = case key_type
558
565
 
566
+ case key_type
559
567
  when :integer
560
- -> (key, context) {
561
- begin
562
- return key if key.nil?
563
- Integer(key)
564
- rescue
565
- raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
566
- end
567
- }
568
+ return if key.nil?
569
+ Integer(key)
568
570
  when :string
569
- -> (key, context) {
570
- return key if key.nil?
571
- if key.to_s.include?(',')
572
- raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
573
- else
574
- key
575
- end
576
- }
571
+ return if key.nil?
572
+ if key.to_s.include?(',')
573
+ raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
574
+ else
575
+ key
576
+ end
577
577
  when :uuid
578
- -> (key, context) {
579
- return key if key.nil?
580
- if key.to_s.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)
581
- key
582
- else
583
- raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
584
- end
585
- }
578
+ return if key.nil?
579
+ if key.to_s.match(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)
580
+ key
581
+ else
582
+ raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
583
+ end
586
584
  else
587
- key_type
585
+ key_type.call(key, context)
588
586
  end
589
-
590
- verification_proc.call(key, context)
591
587
  rescue
592
588
  raise JSONAPI::Exceptions::InvalidFieldValue.new(:id, key)
593
589
  end
@@ -664,6 +660,18 @@ module JSONAPI
664
660
  @abstract
665
661
  end
666
662
 
663
+ def immutable(val = true)
664
+ @immutable = val
665
+ end
666
+
667
+ def _immutable
668
+ @immutable
669
+ end
670
+
671
+ def mutable?
672
+ !@immutable
673
+ end
674
+
667
675
  def _model_class
668
676
  return nil if _abstract
669
677
 
@@ -678,7 +686,7 @@ module JSONAPI
678
686
  end
679
687
 
680
688
  def module_path
681
- @module_path ||= name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
689
+ name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
682
690
  end
683
691
 
684
692
  def construct_order_options(sort_params)
@@ -702,13 +710,13 @@ module JSONAPI
702
710
  def check_reserved_attribute_name(name)
703
711
  # Allow :id since it can be used to specify the format. Since it is a method on the base Resource
704
712
  # an attribute method won't be created for it.
705
- if [:type, :href, :links, :model].include?(name.to_sym)
713
+ if [:type].include?(name.to_sym)
706
714
  warn "[NAME COLLISION] `#{name}` is a reserved key in #{@@resource_types[_type]}."
707
715
  end
708
716
  end
709
717
 
710
718
  def check_reserved_relationship_name(name)
711
- if [:id, :ids, :type, :types, :href, :hrefs, :link, :links].include?(name.to_sym)
719
+ if [:id, :ids, :type, :types].include?(name.to_sym)
712
720
  warn "[NAME COLLISION] `#{name}` is a reserved relationship name in #{@@resource_types[_type]}."
713
721
  end
714
722
  end
@@ -755,7 +763,7 @@ module JSONAPI
755
763
  define_method attr do |options = {}|
756
764
  if relationship.polymorphic?
757
765
  associated_model = public_send(associated_records_method_name)
758
- resource_klass = Resource.resource_for(self.class.module_path + associated_model.class.to_s.underscore) if associated_model
766
+ resource_klass = Resource.resource_for(self.class.resource_type_for(associated_model)) if associated_model
759
767
  return resource_klass.new(associated_model, @context) if resource_klass
760
768
  else
761
769
  resource_klass = relationship.resource_klass
@@ -808,9 +816,7 @@ module JSONAPI
808
816
  end
809
817
 
810
818
  return records.collect do |record|
811
- if relationship.polymorphic?
812
- resource_klass = Resource.resource_for(self.class.module_path + record.class.to_s.underscore)
813
- end
819
+ resource_klass = Resource.resource_for(self.class.resource_type_for(record))
814
820
  resource_klass.new(record, @context)
815
821
  end
816
822
  end unless method_defined?(attr)
@@ -282,7 +282,7 @@ module JSONAPI
282
282
  def foreign_key_types_and_values(source, relationship)
283
283
  if relationship.is_a?(JSONAPI::Relationship::ToMany)
284
284
  if relationship.polymorphic?
285
- source.model.public_send(relationship.name).pluck(:type, :id).map do |type, id|
285
+ source._model.public_send(relationship.name).pluck(:type, :id).map do |type, id|
286
286
  [type.pluralize, IdValueFormatter.format(id)]
287
287
  end
288
288
  else
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Resources
3
- VERSION = '0.6.0'
3
+ VERSION = '0.6.1'
4
4
  end
5
5
  end
@@ -80,6 +80,12 @@ module ActionDispatch
80
80
  options[:except] = [:new, :edit]
81
81
  end
82
82
 
83
+ if res._immutable
84
+ options[:except] << :create
85
+ options[:except] << :update
86
+ options[:except] << :destroy
87
+ end
88
+
83
89
  resources @resource_type, options do
84
90
  @scope[:jsonapi_resource] = @resource_type
85
91
 
@@ -117,14 +123,16 @@ module ActionDispatch
117
123
  action: 'show_relationship', relationship: link_type.to_s, via: [:get]
118
124
  end
119
125
 
120
- if methods.include?(:update)
121
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
122
- action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
123
- end
126
+ if res.mutable?
127
+ if methods.include?(:update)
128
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
129
+ action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
130
+ end
124
131
 
125
- if methods.include?(:destroy)
126
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
127
- action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
132
+ if methods.include?(:destroy)
133
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
134
+ action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
135
+ end
128
136
  end
129
137
  end
130
138
 
@@ -143,19 +151,21 @@ module ActionDispatch
143
151
  action: 'show_relationship', relationship: link_type.to_s, via: [:get]
144
152
  end
145
153
 
146
- if methods.include?(:create)
147
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
148
- action: 'create_relationship', relationship: link_type.to_s, via: [:post]
149
- end
154
+ if res.mutable?
155
+ if methods.include?(:create)
156
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
157
+ action: 'create_relationship', relationship: link_type.to_s, via: [:post]
158
+ end
150
159
 
151
- if methods.include?(:update)
152
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
153
- action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
154
- end
160
+ if methods.include?(:update)
161
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
162
+ action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
163
+ end
155
164
 
156
- if methods.include?(:destroy)
157
- match "relationships/#{formatted_relationship_name}", controller: options[:controller],
158
- action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
165
+ if methods.include?(:destroy)
166
+ match "relationships/#{formatted_relationship_name}", controller: options[:controller],
167
+ action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
168
+ end
159
169
  end
160
170
  end
161
171
 
@@ -3069,15 +3069,15 @@ class Api::V1::CratersControllerTest < ActionController::TestCase
3069
3069
  data: [
3070
3070
  {id:"A4D3",
3071
3071
  type:"craters",
3072
- links:{self: "http://test.host/craters/A4D3"},
3072
+ links:{self: "http://test.host/api/v1/craters/A4D3"},
3073
3073
  attributes:{code: "A4D3", description: "Small crater"},
3074
- relationships:{moon: {links: {self: "http://test.host/craters/A4D3/relationships/moon", related: "http://test.host/craters/A4D3/moon"}}}
3074
+ relationships:{moon: {links: {self: "http://test.host/api/v1/craters/A4D3/relationships/moon", related: "http://test.host/api/v1/craters/A4D3/moon"}}}
3075
3075
  },
3076
3076
  {id: "S56D",
3077
3077
  type: "craters",
3078
- links:{self: "http://test.host/craters/S56D"},
3078
+ links:{self: "http://test.host/api/v1/craters/S56D"},
3079
3079
  attributes:{code: "S56D", description: "Very large crater"},
3080
- relationships:{moon: {links: {self: "http://test.host/craters/S56D/relationships/moon", related: "http://test.host/craters/S56D/moon"}}}
3080
+ relationships:{moon: {links: {self: "http://test.host/api/v1/craters/S56D/relationships/moon", related: "http://test.host/api/v1/craters/S56D/moon"}}}
3081
3081
  }
3082
3082
  ]
3083
3083
  }
@@ -3091,3 +3091,71 @@ class Api::V1::CratersControllerTest < ActionController::TestCase
3091
3091
  assert_equal "1", json_response['data']['id']
3092
3092
  end
3093
3093
  end
3094
+
3095
+ class CarsControllerTest < ActionController::TestCase
3096
+ def setup
3097
+ JSONAPI.configuration.json_key_format = :camelized_key
3098
+ end
3099
+
3100
+ def test_create_sti
3101
+ set_content_type_header!
3102
+ post :create,
3103
+ {
3104
+ data: {
3105
+ type: 'cars',
3106
+ attributes: {
3107
+ make: 'Toyota',
3108
+ model: 'Tercel',
3109
+ serialNumber: 'asasdsdadsa13544235',
3110
+ driveLayout: 'FWD'
3111
+ }
3112
+ }
3113
+ }
3114
+
3115
+ assert_response :created
3116
+ assert json_response['data'].is_a?(Hash)
3117
+ assert_equal 'cars', json_response['data']['type']
3118
+ assert_equal 'Toyota', json_response['data']['attributes']['make']
3119
+ assert_equal 'FWD', json_response['data']['attributes']['driveLayout']
3120
+ end
3121
+ end
3122
+
3123
+ class VehiclesControllerTest < ActionController::TestCase
3124
+ def setup
3125
+ JSONAPI.configuration.json_key_format = :camelized_key
3126
+ end
3127
+
3128
+ def test_immutable_create_not_supported
3129
+ set_content_type_header!
3130
+
3131
+ assert_raises ActionController::UrlGenerationError do
3132
+ post :create,
3133
+ {
3134
+ data: {
3135
+ type: 'cars',
3136
+ attributes: {
3137
+ make: 'Toyota',
3138
+ model: 'Corrola',
3139
+ serialNumber: 'dsvffsfv',
3140
+ driveLayout: 'FWD'
3141
+ }
3142
+ }
3143
+ }
3144
+ end
3145
+ end
3146
+
3147
+ def test_immutable_update_not_supported
3148
+ set_content_type_header!
3149
+
3150
+ assert_raises ActionController::UrlGenerationError do
3151
+ patch :update,
3152
+ data: {
3153
+ id: '1',
3154
+ type: 'cars',
3155
+ attributes: {
3156
+ make: 'Toyota',
3157
+ }
3158
+ }
3159
+ end
3160
+ end
3161
+ end
@@ -214,12 +214,34 @@ ActiveRecord::Schema.define do
214
214
  create_table :vehicles, force: true do |t|
215
215
  t.string :type
216
216
  t.string :make
217
- t.string :vehicle_model
217
+ t.string :model
218
218
  t.string :length_at_water_line
219
219
  t.string :drive_layout
220
220
  t.string :serial_number
221
221
  t.integer :person_id
222
222
  end
223
+
224
+ create_table :makes, force: true do |t|
225
+ t.string :model
226
+ end
227
+
228
+ # special cases - fields that look like they should be reserved names
229
+ create_table :hrefs, force: true do |t|
230
+ t.string :name
231
+ end
232
+
233
+ create_table :links, force: true do |t|
234
+ t.string :name
235
+ end
236
+
237
+ create_table :web_pages, force: true do |t|
238
+ t.string :href
239
+ t.string :link
240
+ end
241
+
242
+ create_table :questionables, force: true do |t|
243
+ end
244
+ # special cases
223
245
  end
224
246
 
225
247
  ### MODELS
@@ -474,6 +496,12 @@ class Product < ActiveRecord::Base
474
496
  has_one :picture, as: :imageable
475
497
  end
476
498
 
499
+ class Make < ActiveRecord::Base
500
+ end
501
+
502
+ class WebPage < ActiveRecord::Base
503
+ end
504
+
477
505
  ### OperationsProcessor
478
506
  class CountingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
479
507
  after_find_operation do
@@ -558,6 +586,15 @@ end
558
586
  class ImageablesController < JSONAPI::ResourceController
559
587
  end
560
588
 
589
+ class VehiclesController < JSONAPI::ResourceController
590
+ end
591
+
592
+ class CarsController < JSONAPI::ResourceController
593
+ end
594
+
595
+ class BoatsController < JSONAPI::ResourceController
596
+ end
597
+
561
598
  ### CONTROLLERS
562
599
  module Api
563
600
  module V1
@@ -701,7 +738,7 @@ class BaseResource < JSONAPI::Resource
701
738
  end
702
739
 
703
740
  class PersonResource < BaseResource
704
- attributes :id, :name, :email
741
+ attributes :name, :email
705
742
  attribute :date_joined, format: :date_with_timezone
706
743
 
707
744
  has_many :comments
@@ -727,8 +764,10 @@ class PersonResource < BaseResource
727
764
  end
728
765
 
729
766
  class VehicleResource < JSONAPI::Resource
767
+ immutable
768
+
730
769
  has_one :person
731
- attributes :make, :vehicle_model, :serial_number
770
+ attributes :make, :model, :serial_number
732
771
  end
733
772
 
734
773
  class CarResource < VehicleResource
@@ -763,12 +802,6 @@ class TagResource < JSONAPI::Resource
763
802
  #has_many :planets
764
803
  end
765
804
 
766
- class SpecialTagResource < JSONAPI::Resource
767
- attributes :name
768
-
769
- has_many :posts
770
- end
771
-
772
805
  class SectionResource < JSONAPI::Resource
773
806
  attributes 'name'
774
807
  end
@@ -893,7 +926,6 @@ class FriendResource < JSONAPI::Resource
893
926
  end
894
927
 
895
928
  class BreedResource < JSONAPI::Resource
896
- attribute :id, format_misspelled: :does_not_exist
897
929
  attribute :name, format: :title
898
930
 
899
931
  # This is unneeded, just here for testing
@@ -1003,13 +1035,22 @@ class ProductResource < JSONAPI::Resource
1003
1035
  has_one :picture, always_include_linkage_data: true
1004
1036
 
1005
1037
  def picture_id
1006
- model.picture.id
1038
+ _model.picture.id
1007
1039
  end
1008
1040
  end
1009
1041
 
1010
1042
  class ImageableResource < JSONAPI::Resource
1011
1043
  end
1012
1044
 
1045
+ class MakeResource < JSONAPI::Resource
1046
+ attribute :model
1047
+ end
1048
+
1049
+ class WebPageResource < JSONAPI::Resource
1050
+ attribute :href
1051
+ attribute :link
1052
+ end
1053
+
1013
1054
  module Api
1014
1055
  module V1
1015
1056
  class WriterResource < JSONAPI::Resource
@@ -1328,22 +1369,6 @@ module MyEngine
1328
1369
  end
1329
1370
  end
1330
1371
 
1331
- warn 'start testing Name Collisions'
1332
- # The name collisions only emmit warnings. Exceptions would change the flow of the tests
1333
-
1334
- class LinksResource < JSONAPI::Resource
1335
- end
1336
-
1337
- class BadlyNamedAttributesResource < JSONAPI::Resource
1338
- attributes :type, :href, :links
1339
-
1340
- has_many :links
1341
- has_one :href
1342
- has_one :id
1343
- has_many :types
1344
- end
1345
- warn 'end testing Name Collisions'
1346
-
1347
1372
  ### PORO Data - don't do this in a production app
1348
1373
  $breed_data = BreedData.new
1349
1374
  $breed_data.add(Breed.new(0, 'persian'))
@@ -0,0 +1,2 @@
1
+ make1:
2
+ model: A model attribute
@@ -2,15 +2,16 @@ Miata:
2
2
  id: 1
3
3
  type: Car
4
4
  make: Mazda
5
- vehicle_model: Miata MX5
5
+ model: Miata MX5
6
6
  drive_layout: Front Engine RWD
7
7
  serial_number: 32432adfsfdysua
8
8
  person_id: 1
9
+
9
10
  Launch20:
10
11
  id: 2
11
12
  type: Boat
12
13
  make: Chris-Craft
13
- vehicle_model: Launch 20
14
+ model: Launch 20
14
15
  length_at_water_line: 15.5ft
15
16
  serial_number: 434253JJJSD
16
17
  person_id: 1
@@ -0,0 +1,3 @@
1
+ web_page1:
2
+ href: http://example.com
3
+ link: http://link.example.com
@@ -910,4 +910,17 @@ class RequestTest < ActionDispatch::IntegrationTest
910
910
  ensure
911
911
  JSONAPI.configuration.allow_sort = true
912
912
  end
913
+
914
+ def test_getting_different_resources_when_sti
915
+ get '/vehicles'
916
+ assert_equal 200, status
917
+ types = json_response['data'].map{|r| r['type']}.sort
918
+ assert_array_equals ['boats', 'cars'], types
919
+ end
920
+
921
+ def test_getting_resource_with_correct_type_when_sti
922
+ get '/vehicles/1'
923
+ assert_equal 200, status
924
+ assert_equal 'cars', json_response['data']['type']
925
+ end
913
926
  end
@@ -1,4 +1,4 @@
1
- require 'test_helper'
1
+ require File.expand_path('../../../../test_helper', __FILE__)
2
2
  require 'generators/jsonapi/resource_generator'
3
3
 
4
4
  module Jsonapi
@@ -39,6 +39,7 @@ class TestApp < Rails::Application
39
39
  #Raise errors on unsupported parameters
40
40
  config.action_controller.action_on_unpermitted_parameters = :raise
41
41
 
42
+ ActiveRecord::Schema.verbose = false
42
43
  config.active_record.schema_format = :none
43
44
  config.active_support.test_order = :random
44
45
 
@@ -104,7 +105,6 @@ module Pets
104
105
  end
105
106
 
106
107
  class CatResource < JSONAPI::Resource
107
- attribute :id
108
108
  attribute :name
109
109
  attribute :breed
110
110
 
@@ -137,8 +137,9 @@ TestApp.routes.draw do
137
137
  jsonapi_resources :pictures
138
138
  jsonapi_resources :documents
139
139
  jsonapi_resources :products
140
-
141
-
140
+ jsonapi_resources :vehicles
141
+ jsonapi_resources :cars
142
+ jsonapi_resources :boats
142
143
 
143
144
  namespace :api do
144
145
  namespace :v1 do
@@ -1,7 +1,6 @@
1
1
  require File.expand_path('../../../test_helper', __FILE__)
2
2
 
3
3
  class CatResource < JSONAPI::Resource
4
- attribute :id
5
4
  attribute :name
6
5
  attribute :breed
7
6
 
@@ -184,8 +184,8 @@ class PagedPaginatorTest < ActiveSupport::TestCase
184
184
  assert_equal 5, links_params['first']['size']
185
185
  assert_equal 1, links_params['first']['number']
186
186
 
187
- assert_equal 5, links_params['previous']['size']
188
- assert_equal 3, links_params['previous']['number']
187
+ assert_equal 5, links_params['prev']['size']
188
+ assert_equal 3, links_params['prev']['number']
189
189
 
190
190
  assert_equal 5, links_params['next']['size']
191
191
  assert_equal 5, links_params['next']['number']
@@ -210,8 +210,8 @@ class PagedPaginatorTest < ActiveSupport::TestCase
210
210
  assert_equal 5, links_params['first']['size']
211
211
  assert_equal 1, links_params['first']['number']
212
212
 
213
- assert_equal 5, links_params['previous']['size']
214
- assert_equal 9, links_params['previous']['number']
213
+ assert_equal 5, links_params['prev']['size']
214
+ assert_equal 9, links_params['prev']['number']
215
215
 
216
216
  assert_equal 5, links_params['last']['size']
217
217
  assert_equal 10, links_params['last']['number']
@@ -233,8 +233,8 @@ class PagedPaginatorTest < ActiveSupport::TestCase
233
233
  assert_equal 5, links_params['first']['size']
234
234
  assert_equal 1, links_params['first']['number']
235
235
 
236
- assert_equal 5, links_params['previous']['size']
237
- assert_equal 10, links_params['previous']['number']
236
+ assert_equal 5, links_params['prev']['size']
237
+ assert_equal 10, links_params['prev']['number']
238
238
 
239
239
  assert_equal 5, links_params['last']['size']
240
240
  assert_equal 10, links_params['last']['number']
@@ -16,7 +16,6 @@ class NoMatchAbstractResource < JSONAPI::Resource
16
16
  end
17
17
 
18
18
  class CatResource < JSONAPI::Resource
19
- attribute :id
20
19
  attribute :name
21
20
  attribute :breed
22
21
 
@@ -111,7 +110,7 @@ class ResourceTest < ActiveSupport::TestCase
111
110
 
112
111
  def test_find_with_customized_base_records
113
112
  author = Person.find(1)
114
- posts = ArticleResource.find([], context: author).map(&:model)
113
+ posts = ArticleResource.find([], context: author).map(&:_model)
115
114
 
116
115
  assert(posts.include?(Post.find(1)))
117
116
  refute(posts.include?(Post.find(3)))
@@ -123,10 +122,10 @@ class ResourceTest < ActiveSupport::TestCase
123
122
  refute(preferences == nil)
124
123
  author.update! preferences: preferences
125
124
  author_resource = PersonResource.new(author, nil)
126
- assert_equal(author_resource.preferences.model, preferences)
125
+ assert_equal(author_resource.preferences._model, preferences)
127
126
 
128
127
  author_resource = PersonWithCustomRecordsForResource.new(author, nil)
129
- assert_equal(author_resource.preferences.model, :records_for)
128
+ assert_equal(author_resource.preferences._model, :records_for)
130
129
 
131
130
  author_resource = PersonWithCustomRecordsForErrorResource.new(author, nil)
132
131
  assert_raises PersonWithCustomRecordsForErrorResource::AuthorizationError do
@@ -165,11 +164,11 @@ class ResourceTest < ActiveSupport::TestCase
165
164
  def test_find_by_key_with_customized_base_records
166
165
  author = Person.find(1)
167
166
 
168
- post = ArticleResource.find_by_key(1, context: author).model
167
+ post = ArticleResource.find_by_key(1, context: author)._model
169
168
  assert_equal(post, Post.find(1))
170
169
 
171
170
  assert_raises JSONAPI::Exceptions::RecordNotFound do
172
- ArticleResource.find_by_key(3, context: author).model
171
+ ArticleResource.find_by_key(3, context: author)._model
173
172
  end
174
173
  end
175
174
 
@@ -221,7 +220,7 @@ class ResourceTest < ActiveSupport::TestCase
221
220
 
222
221
  def test_to_many_relationship_sorts
223
222
  post_resource = PostResource.new(Post.find(1), nil)
224
- comment_ids = post_resource.comments.map{|c| c.model.id }
223
+ comment_ids = post_resource.comments.map{|c| c._model.id }
225
224
  assert_equal [1,2], comment_ids
226
225
 
227
226
  # define apply_filters method on post resource to not respect filters
@@ -233,7 +232,7 @@ class ResourceTest < ActiveSupport::TestCase
233
232
  end
234
233
  end
235
234
 
236
- sorted_comment_ids = post_resource.comments(sort_criteria: [{ field: 'id', direction: :desc}]).map{|c| c.model.id }
235
+ sorted_comment_ids = post_resource.comments(sort_criteria: [{ field: 'id', direction: :desc}]).map{|c| c._model.id }
237
236
  assert_equal [2,1], sorted_comment_ids
238
237
 
239
238
  ensure
@@ -362,4 +361,90 @@ class ResourceTest < ActiveSupport::TestCase
362
361
  key_type nil
363
362
  end
364
363
  end
364
+
365
+ def test_id_attr_deprecation
366
+ _out, err = capture_io do
367
+ eval <<-CODE
368
+ class ProblemResource < JSONAPI::Resource
369
+ attribute :id
370
+ end
371
+ CODE
372
+ end
373
+ assert_match /DEPRECATION WARNING: Id without format is no longer supported. Please remove ids from attributes, or specify a format./, err
374
+ end
375
+
376
+ def test_id_attr_with_format
377
+ _out, err = capture_io do
378
+ eval <<-CODE
379
+ class NotProblemResource < JSONAPI::Resource
380
+ attribute :id, format: :string
381
+ end
382
+ CODE
383
+ end
384
+ assert_equal "", err
385
+ end
386
+
387
+ def test_links_resource_warning
388
+ _out, err = capture_io do
389
+ eval "class LinksResource < JSONAPI::Resource; end"
390
+ end
391
+ assert_match /LinksResource` is a reserved resource name/, err
392
+ end
393
+
394
+ def test_reserved_key_warnings
395
+ _out, err = capture_io do
396
+ eval <<-CODE
397
+ class BadlyNamedAttributesResource < JSONAPI::Resource
398
+ attributes :type
399
+ end
400
+ CODE
401
+ end
402
+ assert_match /`type` is a reserved key in ./, err
403
+ end
404
+
405
+ def test_reserved_relationship_warnings
406
+ %w(id type).each do |key|
407
+ _out, err = capture_io do
408
+ eval <<-CODE
409
+ class BadlyNamedAttributesResource < JSONAPI::Resource
410
+ has_one :#{key}
411
+ end
412
+ CODE
413
+ end
414
+ assert_match /`#{key}` is a reserved relationship name in ./, err
415
+ end
416
+ %w(types ids).each do |key|
417
+ _out, err = capture_io do
418
+ eval <<-CODE
419
+ class BadlyNamedAttributesResource < JSONAPI::Resource
420
+ has_many :#{key}
421
+ end
422
+ CODE
423
+ end
424
+ assert_match /`#{key}` is a reserved relationship name in ./, err
425
+ end
426
+ end
427
+
428
+ def test_abstract_warning
429
+ _out, err = capture_io do
430
+ eval <<-CODE
431
+ class NoModelResource < JSONAPI::Resource
432
+ end
433
+ NoModelResource._model_class
434
+ CODE
435
+ end
436
+ assert_match "[MODEL NOT FOUND] Model could not be found for ResourceTest::NoModelResource. If this a base Resource declare it as abstract.\n", err
437
+ end
438
+
439
+ def test_no_warning_when_abstract
440
+ _out, err = capture_io do
441
+ eval <<-CODE
442
+ class NoModelAbstractResource < JSONAPI::Resource
443
+ abstract
444
+ end
445
+ NoModelAbstractResource._model_class
446
+ CODE
447
+ end
448
+ assert_match "", err
449
+ end
365
450
  end
@@ -88,7 +88,7 @@ class PolymorphismTest < ActionDispatch::IntegrationTest
88
88
  },
89
89
  attributes: {
90
90
  make: 'Mazda',
91
- vehicleModel: 'Miata MX5',
91
+ model: 'Miata MX5',
92
92
  driveLayout: 'Front Engine RWD',
93
93
  serialNumber: '32432adfsfdysua'
94
94
  },
@@ -109,7 +109,7 @@ class PolymorphismTest < ActionDispatch::IntegrationTest
109
109
  },
110
110
  attributes: {
111
111
  make: 'Chris-Craft',
112
- vehicleModel: 'Launch 20',
112
+ model: 'Launch 20',
113
113
  lengthAtWaterLine: '15.5ft',
114
114
  serialNumber: '434253JJJSD'
115
115
  },
@@ -1736,4 +1736,139 @@ class SerializerTest < ActionDispatch::IntegrationTest
1736
1736
  serialized
1737
1737
  )
1738
1738
  end
1739
+
1740
+ def test_serialize_model_attr
1741
+ @make = Make.first
1742
+ serialized = JSONAPI::ResourceSerializer.new(
1743
+ MakeResource,
1744
+ ).serialize_to_hash(MakeResource.new(@make, nil))
1745
+
1746
+ assert_hash_equals(
1747
+ {
1748
+ "model" => "A model attribute"
1749
+ },
1750
+ serialized[:data]["attributes"]
1751
+ )
1752
+ end
1753
+
1754
+ def test_confusingly_named_attrs
1755
+ @wp = WebPage.first
1756
+ serialized = JSONAPI::ResourceSerializer.new(
1757
+ WebPageResource,
1758
+ ).serialize_to_hash(WebPageResource.new(@wp, nil))
1759
+
1760
+ assert_hash_equals(
1761
+ {
1762
+ :data=>{
1763
+ "id"=>"#{@wp.id}",
1764
+ "type"=>"webPages",
1765
+ "links"=>{
1766
+ :self=>"/webPages/#{@wp.id}"
1767
+ },
1768
+ "attributes"=>{
1769
+ "href"=>"http://example.com",
1770
+ "link"=>"http://link.example.com"
1771
+ }
1772
+ }
1773
+ },
1774
+ serialized
1775
+ )
1776
+ end
1777
+
1778
+ def test_questionable_has_one
1779
+ # has_one
1780
+ out, err = capture_io do
1781
+ eval <<-CODE
1782
+ class ::Questionable < ActiveRecord::Base
1783
+ has_one :link
1784
+ has_one :href
1785
+ end
1786
+ class ::QuestionableResource < JSONAPI::Resource
1787
+ model_name '::Questionable'
1788
+ has_one :link
1789
+ has_one :href
1790
+ end
1791
+ cn = ::Questionable.new id: 1
1792
+ puts JSONAPI::ResourceSerializer.new(
1793
+ ::QuestionableResource,
1794
+ ).serialize_to_hash(::QuestionableResource.new(cn, nil))
1795
+ CODE
1796
+ end
1797
+ assert err.blank?
1798
+ assert_equal(
1799
+ {
1800
+ :data=>{
1801
+ "id"=>"1",
1802
+ "type"=>"questionables",
1803
+ "links"=>{
1804
+ :self=>"/questionables/1"
1805
+ },
1806
+ "relationships"=>{
1807
+ "link"=>{
1808
+ :links=>{
1809
+ :self=>"/questionables/1/relationships/link",
1810
+ :related=>"/questionables/1/link"
1811
+ }
1812
+ },
1813
+ "href"=>{
1814
+ :links=>{
1815
+ :self=>"/questionables/1/relationships/href",
1816
+ :related=>"/questionables/1/href"
1817
+ }
1818
+ }
1819
+ }
1820
+ }
1821
+ }.to_s,
1822
+ out.strip
1823
+ )
1824
+ end
1825
+
1826
+ def test_questionable_has_many
1827
+ # has_one
1828
+ out, err = capture_io do
1829
+ eval <<-CODE
1830
+ class ::Questionable2 < ActiveRecord::Base
1831
+ self.table_name = 'questionables'
1832
+ has_many :links
1833
+ has_many :hrefs
1834
+ end
1835
+ class ::Questionable2Resource < JSONAPI::Resource
1836
+ model_name '::Questionable2'
1837
+ has_many :links
1838
+ has_many :hrefs
1839
+ end
1840
+ cn = ::Questionable2.new id: 1
1841
+ puts JSONAPI::ResourceSerializer.new(
1842
+ ::Questionable2Resource,
1843
+ ).serialize_to_hash(::Questionable2Resource.new(cn, nil))
1844
+ CODE
1845
+ end
1846
+ assert err.blank?
1847
+ assert_equal(
1848
+ {
1849
+ :data=>{
1850
+ "id"=>"1",
1851
+ "type"=>"questionable2s",
1852
+ "links"=>{
1853
+ :self=>"/questionable2s/1"
1854
+ },
1855
+ "relationships"=>{
1856
+ "links"=>{
1857
+ :links=>{
1858
+ :self=>"/questionable2s/1/relationships/links",
1859
+ :related=>"/questionable2s/1/links"
1860
+ }
1861
+ },
1862
+ "hrefs"=>{
1863
+ :links=>{
1864
+ :self=>"/questionable2s/1/relationships/hrefs",
1865
+ :related=>"/questionable2s/1/hrefs"
1866
+ }
1867
+ }
1868
+ }
1869
+ }
1870
+ }.to_s,
1871
+ out.strip
1872
+ )
1873
+ end
1739
1874
  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.6.0
4
+ version: 0.6.1
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: 2015-10-09 00:00:00.000000000 Z
12
+ date: 2015-10-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -171,6 +171,7 @@ files:
171
171
  - test/fixtures/hair_cuts.yml
172
172
  - test/fixtures/iso_currencies.yml
173
173
  - test/fixtures/line_items.yml
174
+ - test/fixtures/makes.yml
174
175
  - test/fixtures/moons.yml
175
176
  - test/fixtures/numeros_telefone.yml
176
177
  - test/fixtures/order_flags.yml
@@ -186,6 +187,7 @@ files:
186
187
  - test/fixtures/sections.yml
187
188
  - test/fixtures/tags.yml
188
189
  - test/fixtures/vehicles.yml
190
+ - test/fixtures/web_pages.yml
189
191
  - test/helpers/assertions.rb
190
192
  - test/helpers/functional_helpers.rb
191
193
  - test/helpers/value_matchers.rb
@@ -248,6 +250,7 @@ test_files:
248
250
  - test/fixtures/hair_cuts.yml
249
251
  - test/fixtures/iso_currencies.yml
250
252
  - test/fixtures/line_items.yml
253
+ - test/fixtures/makes.yml
251
254
  - test/fixtures/moons.yml
252
255
  - test/fixtures/numeros_telefone.yml
253
256
  - test/fixtures/order_flags.yml
@@ -263,6 +266,7 @@ test_files:
263
266
  - test/fixtures/sections.yml
264
267
  - test/fixtures/tags.yml
265
268
  - test/fixtures/vehicles.yml
269
+ - test/fixtures/web_pages.yml
266
270
  - test/helpers/assertions.rb
267
271
  - test/helpers/functional_helpers.rb
268
272
  - test/helpers/value_matchers.rb