Skip to content

Commit

Permalink
WIP #1105 fix bug with inclusion and linkage of has_one polymorphic
Browse files Browse the repository at this point in the history
add tests for link/unlink polymorphic relationships with include them in response

add tests for include not linked relationships
  • Loading branch information
senid231 committed Sep 10, 2017
1 parent 38757c6 commit 0dffe4c
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 2 deletions.
3 changes: 3 additions & 0 deletions lib/jsonapi/relationship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def self.polymorphic_types(name)
klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection|
(hash[reflection.options[:as]] ||= []) << klass.name.downcase
end
klass.reflect_on_all_associations(:has_one).select{|r| r.options[:as] }.each do |reflection|
(hash[reflection.options[:as]] ||= []) << klass.name.downcase
end
end
end
end
Expand Down
6 changes: 4 additions & 2 deletions lib/jsonapi/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1012,8 +1012,10 @@ def define_relationship_methods(relationship_name, relationship_klass, options)
def define_foreign_key_setter(relationship)
if relationship.polymorphic?
define_on_resource "#{relationship.foreign_key}=" do |v|
_model.method("#{relationship.foreign_key}=").call(v[:id])
_model.public_send("#{relationship.polymorphic_type}=", v[:type])
model_id = v.nil? ? nil : v[:id]
model_type = v.nil? ? nil : self.class.resource_klass_for(v[:type].to_s)._model_class.to_s
_model.method("#{relationship.foreign_key}=").call(model_id)
_model.public_send("#{relationship.polymorphic_type}=", model_type)
end
else
define_on_resource "#{relationship.foreign_key}=" do |value|
Expand Down
48 changes: 48 additions & 0 deletions test/fixtures/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,24 @@
t.integer :access_card_id, null: false
t.timestamps null: false
end

create_table :options, force: true do |t|
t.integer :optionable_id
t.string :optionable_type
t.integer :maintainer_id
t.boolean :enabled, default: false, null: false
t.timestamps null: false
end

create_table :androids, force: true do |t|
t.string :version_name
t.timestamps null: false
end

create_table :maintainers, force: true do |t|
t.string :name
t.timestamps null: false
end
end

### MODELS
Expand Down Expand Up @@ -696,6 +714,19 @@ class Worker < ActiveRecord::Base
belongs_to :access_card
end

class Option < ActiveRecord::Base
belongs_to :optionable, polymorphic: true, required: false
belongs_to :maintainer, required: false
end

class Android < ActiveRecord::Base
has_one :option, as: :optionable
end

class Maintainer < ActiveRecord::Base
has_one :maintained_option
end

### CONTROLLERS
class AuthorsController < JSONAPI::ResourceControllerMetal
end
Expand Down Expand Up @@ -983,6 +1014,9 @@ class AccessCardsController < BaseController
class WorkersController < BaseController
end

class OptionsController < JSONAPI::ResourceController
end

### RESOURCES
class BaseResource < JSONAPI::Resource
abstract
Expand Down Expand Up @@ -2104,6 +2138,20 @@ class WorkerResource < JSONAPI::Resource
attribute :name
end

class OptionResource < JSONAPI::Resource
attribute :enabled
has_one :optionable, polymorphic: true, class_name: 'Android'
has_one :maintainer, class_name: 'Maintainer'
end

class AndroidResource < JSONAPI::Resource
attribute :version_name
end

class MaintainerResource < JSONAPI::Resource
attribute :name
end

### PORO Data - don't do this in a production app
$breed_data = BreedData.new
$breed_data.add(Breed.new(0, 'persian'))
Expand Down
93 changes: 93 additions & 0 deletions test/integration/requests/request_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1142,4 +1142,97 @@ def test_get_resource_with_belongs_to_relationship_and_changed_primary_key
assert_equal 'access_cards', included.first['type']
assert_equal access_card.token, included.first['id']
end

def test_update_option_link_with_optionable_include_optionable
android = Android.create! version_name: '1.0'
option = Option.create!
json_request = {
data: {
id: option.id.to_s,
type: 'options',
relationships: {
optionable: {
data: {
id: android.id.to_s,
type: 'androids'
}
}
}
}
}
json_api_headers = {
'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE,
'Accept' => JSONAPI::MEDIA_TYPE
}
patch "/options/#{option.id}?include=optionable", params: json_request.to_json, headers: json_api_headers
assert_jsonapi_response 200
relationship_data = {'id' => android.id.to_s, 'type' => 'androids'}
assert_equal relationship_data, json_response['data']['relationships']['optionable']['data']
assert_equal relationship_data, json_response['included'].first.slice('id', 'type')
assert_equal android, option.reload.optionable
end

def test_update_option_unlink_from_optionable_include_optionable
android = Android.create! version_name: '1.0'
option = Option.create! optionable: android
json_request = {
data: {
id: option.id.to_s,
type: 'options',
relationships: {
optionable: {
data: nil
}
}
}
}
json_api_headers = {
'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE,
'Accept' => JSONAPI::MEDIA_TYPE
}
patch "/options/#{option.id}?include=optionable", params: json_request.to_json, headers: json_api_headers
assert_jsonapi_response 200
assert_equal true, json_response['data']['relationships']['optionable'].has_key?('data')
assert_nil json_response['data']['relationships']['optionable']['data']
assert_equal false, json_response.has_key?('included')
assert_nil option.reload.optionable
end

def test_fetch_option_linked_with_optionable_include_optionable
android = Android.create! version_name: '1.0'
option = Option.create! optionable: android
assert_cacheable_jsonapi_get "/options/#{option.id}?include=optionable"
assert_jsonapi_response 200
relationship_data = {'id' => android.id.to_s, 'type' => 'androids'}
assert_equal relationship_data, json_response['data']['relationships']['optionable']['data']
assert_equal relationship_data, json_response['included'].first.slice('id', 'type')
end

def test_fetch_option_not_linked_with_optionable_include_optionable
option = Option.create!
assert_cacheable_jsonapi_get "/options/#{option.id}?include=optionable"
assert_jsonapi_response 200
assert_equal true, json_response['data']['relationships']['optionable'].has_key?('data')
assert_nil json_response['data']['relationships']['optionable']['data']
assert_equal false, json_response.has_key?('included')
end

def test_fetch_option_not_linked_with_maintainer_include_maintainer
option = Option.create!
assert_cacheable_jsonapi_get "/options/#{option.id}?include=maintainer"
assert_jsonapi_response 200
assert_equal true, json_response['data']['relationships']['maintainer'].has_key?('data')
assert_nil json_response['data']['relationships']['maintainer']['data']
assert_equal false, json_response.has_key?('included')
end

def test_fetch_option_linked_with_maintainer_include_maintainer
maintainer = Maintainer.create! name: 'John Doe'
option = Option.create! maintainer: maintainer
assert_cacheable_jsonapi_get "/options/#{option.id}?include=maintainer"
assert_jsonapi_response 200
relationship_data = {'id' => maintainer.id.to_s, 'type' => 'maintainers'}
assert_equal relationship_data, json_response['data']['relationships']['maintainer']['data']
assert_equal relationship_data, json_response['included'].first.slice('id', 'type')
end
end
1 change: 1 addition & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ class CatResource < JSONAPI::Resource
jsonapi_resources :keepers, only: [:show]
jsonapi_resources :storages
jsonapi_resources :workers, only: [:show]
jsonapi_resources :options, only: [:show, :update]

mount MyEngine::Engine => "/boomshaka", as: :my_engine
mount ApiV2Engine::Engine => "/api_v2", as: :api_v2_engine
Expand Down

0 comments on commit 0dffe4c

Please sign in to comment.