Skip to content

Commit 0dffe4c

Browse files
committed
WIP #1105 fix bug with inclusion and linkage of has_one polymorphic
add tests for link/unlink polymorphic relationships with include them in response add tests for include not linked relationships
1 parent 38757c6 commit 0dffe4c

File tree

5 files changed

+149
-2
lines changed

5 files changed

+149
-2
lines changed

lib/jsonapi/relationship.rb

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def self.polymorphic_types(name)
4141
klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection|
4242
(hash[reflection.options[:as]] ||= []) << klass.name.downcase
4343
end
44+
klass.reflect_on_all_associations(:has_one).select{|r| r.options[:as] }.each do |reflection|
45+
(hash[reflection.options[:as]] ||= []) << klass.name.downcase
46+
end
4447
end
4548
end
4649
end

lib/jsonapi/resource.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -1012,8 +1012,10 @@ def define_relationship_methods(relationship_name, relationship_klass, options)
10121012
def define_foreign_key_setter(relationship)
10131013
if relationship.polymorphic?
10141014
define_on_resource "#{relationship.foreign_key}=" do |v|
1015-
_model.method("#{relationship.foreign_key}=").call(v[:id])
1016-
_model.public_send("#{relationship.polymorphic_type}=", v[:type])
1015+
model_id = v.nil? ? nil : v[:id]
1016+
model_type = v.nil? ? nil : self.class.resource_klass_for(v[:type].to_s)._model_class.to_s
1017+
_model.method("#{relationship.foreign_key}=").call(model_id)
1018+
_model.public_send("#{relationship.polymorphic_type}=", model_type)
10171019
end
10181020
else
10191021
define_on_resource "#{relationship.foreign_key}=" do |value|

test/fixtures/active_record.rb

+48
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,24 @@
333333
t.integer :access_card_id, null: false
334334
t.timestamps null: false
335335
end
336+
337+
create_table :options, force: true do |t|
338+
t.integer :optionable_id
339+
t.string :optionable_type
340+
t.integer :maintainer_id
341+
t.boolean :enabled, default: false, null: false
342+
t.timestamps null: false
343+
end
344+
345+
create_table :androids, force: true do |t|
346+
t.string :version_name
347+
t.timestamps null: false
348+
end
349+
350+
create_table :maintainers, force: true do |t|
351+
t.string :name
352+
t.timestamps null: false
353+
end
336354
end
337355

338356
### MODELS
@@ -696,6 +714,19 @@ class Worker < ActiveRecord::Base
696714
belongs_to :access_card
697715
end
698716

717+
class Option < ActiveRecord::Base
718+
belongs_to :optionable, polymorphic: true, required: false
719+
belongs_to :maintainer, required: false
720+
end
721+
722+
class Android < ActiveRecord::Base
723+
has_one :option, as: :optionable
724+
end
725+
726+
class Maintainer < ActiveRecord::Base
727+
has_one :maintained_option
728+
end
729+
699730
### CONTROLLERS
700731
class AuthorsController < JSONAPI::ResourceControllerMetal
701732
end
@@ -983,6 +1014,9 @@ class AccessCardsController < BaseController
9831014
class WorkersController < BaseController
9841015
end
9851016

1017+
class OptionsController < JSONAPI::ResourceController
1018+
end
1019+
9861020
### RESOURCES
9871021
class BaseResource < JSONAPI::Resource
9881022
abstract
@@ -2104,6 +2138,20 @@ class WorkerResource < JSONAPI::Resource
21042138
attribute :name
21052139
end
21062140

2141+
class OptionResource < JSONAPI::Resource
2142+
attribute :enabled
2143+
has_one :optionable, polymorphic: true, class_name: 'Android'
2144+
has_one :maintainer, class_name: 'Maintainer'
2145+
end
2146+
2147+
class AndroidResource < JSONAPI::Resource
2148+
attribute :version_name
2149+
end
2150+
2151+
class MaintainerResource < JSONAPI::Resource
2152+
attribute :name
2153+
end
2154+
21072155
### PORO Data - don't do this in a production app
21082156
$breed_data = BreedData.new
21092157
$breed_data.add(Breed.new(0, 'persian'))

test/integration/requests/request_test.rb

+93
Original file line numberDiff line numberDiff line change
@@ -1142,4 +1142,97 @@ def test_get_resource_with_belongs_to_relationship_and_changed_primary_key
11421142
assert_equal 'access_cards', included.first['type']
11431143
assert_equal access_card.token, included.first['id']
11441144
end
1145+
1146+
def test_update_option_link_with_optionable_include_optionable
1147+
android = Android.create! version_name: '1.0'
1148+
option = Option.create!
1149+
json_request = {
1150+
data: {
1151+
id: option.id.to_s,
1152+
type: 'options',
1153+
relationships: {
1154+
optionable: {
1155+
data: {
1156+
id: android.id.to_s,
1157+
type: 'androids'
1158+
}
1159+
}
1160+
}
1161+
}
1162+
}
1163+
json_api_headers = {
1164+
'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE,
1165+
'Accept' => JSONAPI::MEDIA_TYPE
1166+
}
1167+
patch "/options/#{option.id}?include=optionable", params: json_request.to_json, headers: json_api_headers
1168+
assert_jsonapi_response 200
1169+
relationship_data = {'id' => android.id.to_s, 'type' => 'androids'}
1170+
assert_equal relationship_data, json_response['data']['relationships']['optionable']['data']
1171+
assert_equal relationship_data, json_response['included'].first.slice('id', 'type')
1172+
assert_equal android, option.reload.optionable
1173+
end
1174+
1175+
def test_update_option_unlink_from_optionable_include_optionable
1176+
android = Android.create! version_name: '1.0'
1177+
option = Option.create! optionable: android
1178+
json_request = {
1179+
data: {
1180+
id: option.id.to_s,
1181+
type: 'options',
1182+
relationships: {
1183+
optionable: {
1184+
data: nil
1185+
}
1186+
}
1187+
}
1188+
}
1189+
json_api_headers = {
1190+
'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE,
1191+
'Accept' => JSONAPI::MEDIA_TYPE
1192+
}
1193+
patch "/options/#{option.id}?include=optionable", params: json_request.to_json, headers: json_api_headers
1194+
assert_jsonapi_response 200
1195+
assert_equal true, json_response['data']['relationships']['optionable'].has_key?('data')
1196+
assert_nil json_response['data']['relationships']['optionable']['data']
1197+
assert_equal false, json_response.has_key?('included')
1198+
assert_nil option.reload.optionable
1199+
end
1200+
1201+
def test_fetch_option_linked_with_optionable_include_optionable
1202+
android = Android.create! version_name: '1.0'
1203+
option = Option.create! optionable: android
1204+
assert_cacheable_jsonapi_get "/options/#{option.id}?include=optionable"
1205+
assert_jsonapi_response 200
1206+
relationship_data = {'id' => android.id.to_s, 'type' => 'androids'}
1207+
assert_equal relationship_data, json_response['data']['relationships']['optionable']['data']
1208+
assert_equal relationship_data, json_response['included'].first.slice('id', 'type')
1209+
end
1210+
1211+
def test_fetch_option_not_linked_with_optionable_include_optionable
1212+
option = Option.create!
1213+
assert_cacheable_jsonapi_get "/options/#{option.id}?include=optionable"
1214+
assert_jsonapi_response 200
1215+
assert_equal true, json_response['data']['relationships']['optionable'].has_key?('data')
1216+
assert_nil json_response['data']['relationships']['optionable']['data']
1217+
assert_equal false, json_response.has_key?('included')
1218+
end
1219+
1220+
def test_fetch_option_not_linked_with_maintainer_include_maintainer
1221+
option = Option.create!
1222+
assert_cacheable_jsonapi_get "/options/#{option.id}?include=maintainer"
1223+
assert_jsonapi_response 200
1224+
assert_equal true, json_response['data']['relationships']['maintainer'].has_key?('data')
1225+
assert_nil json_response['data']['relationships']['maintainer']['data']
1226+
assert_equal false, json_response.has_key?('included')
1227+
end
1228+
1229+
def test_fetch_option_linked_with_maintainer_include_maintainer
1230+
maintainer = Maintainer.create! name: 'John Doe'
1231+
option = Option.create! maintainer: maintainer
1232+
assert_cacheable_jsonapi_get "/options/#{option.id}?include=maintainer"
1233+
assert_jsonapi_response 200
1234+
relationship_data = {'id' => maintainer.id.to_s, 'type' => 'maintainers'}
1235+
assert_equal relationship_data, json_response['data']['relationships']['maintainer']['data']
1236+
assert_equal relationship_data, json_response['included'].first.slice('id', 'type')
1237+
end
11451238
end

test/test_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ class CatResource < JSONAPI::Resource
397397
jsonapi_resources :keepers, only: [:show]
398398
jsonapi_resources :storages
399399
jsonapi_resources :workers, only: [:show]
400+
jsonapi_resources :options, only: [:show, :update]
400401

401402
mount MyEngine::Engine => "/boomshaka", as: :my_engine
402403
mount ApiV2Engine::Engine => "/api_v2", as: :api_v2_engine

0 commit comments

Comments
 (0)