Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP #1105 fix bug with inclusion and linkage of has_one polymorphic #1108

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1039,8 +1039,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
49 changes: 49 additions & 0 deletions test/fixtures/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,25 @@
t.integer :version
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 @@ -734,6 +753,19 @@ class Widget < ActiveRecord::Base
class Robot < ActiveRecord::Base
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 @@ -1033,6 +1065,9 @@ class IndicatorsController < JSONAPI::ResourceController
class RobotsController < JSONAPI::ResourceController
end

class OptionsController < JSONAPI::ResourceController
end

### RESOURCES
class BaseResource < JSONAPI::Resource
abstract
Expand Down Expand Up @@ -2207,6 +2242,20 @@ class RobotResource < ::JSONAPI::Resource
end
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 @@ -1162,4 +1162,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 @@ -401,6 +401,7 @@ class CatResource < JSONAPI::Resource
jsonapi_resources :widgets, only: [:index]
jsonapi_resources :indicators, only: [:index]
jsonapi_resources :robots, only: [:index]
jsonapi_resources :options, only: [:show, :update]

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