jsonapi-resources 0.6.0 → 0.6.1

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