diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 77e700b78..12b89c7fd 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -59,18 +59,40 @@ def table_name def self.polymorphic_types(name) @poly_hash ||= {}.tap do |hash| - ObjectSpace.each_object do |klass| - next unless Module === klass - if ActiveRecord::Base > klass - klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection| - (hash[reflection.options[:as]] ||= []) << klass.name.downcase - end + candidate_polymorphic_classes.each do |klass| + klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection| + (hash[reflection.options[:as]] ||= []) << klass.name.downcase end end end @poly_hash[name.to_sym] end + def self.candidate_polymorphic_classes + candidate_polymorphic_classes = [] + ObjectSpace.each_object do |klass| + next unless Module === klass + if ActiveRecord::Base > klass + if !klass.name.nil? + candidate_polymorphic_classes << klass + else + model_name = + if klass.respond_to?(:model_name) + begin + klass.model_name.name + rescue ArgumentError => e + "Responds to ActiveModel::Naming but #{e.message}" + end + else + "Does not extend ActiveModel::Naming" + end + warn "No class name found for #{klass} (#{model_name})" + end + end + end + candidate_polymorphic_classes + end + def resource_types if polymorphic? && belongs_to? @polymorphic_types ||= self.class.polymorphic_types(@relation_name).collect {|t| t.pluralize} diff --git a/test/bug-1305.rb b/test/bug-1305.rb new file mode 100644 index 000000000..d3113152d --- /dev/null +++ b/test/bug-1305.rb @@ -0,0 +1,42 @@ +require File.expand_path('../test_helper', __FILE__) + +# Replace this with the code necessary to make your test fail. +class BugTest < Minitest::Test + include Rack::Test::Methods + + def json_api_headers + {'Accept' => JSONAPI::MEDIA_TYPE, 'CONTENT_TYPE' => JSONAPI::MEDIA_TYPE} + end + + def teardown + Individual.delete_all + ContactMedium.delete_all + end + + def test_find_party_via_contact_medium + individual = Individual.create(name: 'test') + contact_medium = ContactMedium.create(party: individual, name: 'test contact medium') + fetched_party = contact_medium.party + assert_same individual, fetched_party, "Expect an individual to have been found via contact medium model's relationship 'party'" + end + + def test_get_individual + individual = Individual.create(name: 'test') + ContactMedium.create(party: individual, name: 'test contact medium') + get "/individuals/#{individual.id}" + assert last_response.ok? + end + + def test_get_party_via_contact_medium + individual = Individual.create(name: 'test') + contact_medium = ContactMedium.create(party: individual, name: 'test contact medium') + get "/contact_media/#{contact_medium.id}/party" + assert last_response.ok?, "Expect an individual to have been found via contact medium resource's relationship 'party'" + end + + private + + def app + Rails.application + end +end diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index bdb718bbf..706845bbb 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -429,6 +429,19 @@ t.integer :version t.timestamps null: false end + + create_table :contact_media do |t| + t.string :name + t.references :party, polymorphic: true, index: true + end + + create_table :individuals do |t| + t.string :name + end + + create_table :organizations do |t| + t.string :name + end end ### MODELS @@ -643,6 +656,22 @@ class Fact < ActiveRecord::Base class Like < ActiveRecord::Base end + class TestApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end + +class ContactMedium < TestApplicationRecord + belongs_to :party, polymorphic: true, inverse_of: :contact_media +end + +class Individual < TestApplicationRecord + has_many :contact_media, as: :party +end + +class Organization < TestApplicationRecord + has_many :contact_media, as: :party +end + class Breed include ActiveModel::Model @@ -1246,6 +1275,18 @@ class IndicatorsController < JSONAPI::ResourceController class RobotsController < JSONAPI::ResourceController end +class IndividualsController < BaseController +end + +class OrganizationsController < BaseController +end + +class ContactMediaController < BaseController +end + +class PartiesController < BaseController +end + ### RESOURCES class BaseResource < JSONAPI::Resource abstract @@ -2688,6 +2729,24 @@ class RobotResource < ::JSONAPI::Resource end end +class ContactMediumResource < JSONAPI::Resource + attribute :name + has_one :party, polymorphic: true +end + +class IndividualResource < JSONAPI::Resource + attribute :name + has_many :contact_media +end + +class OrganizationResource < JSONAPI::Resource + attribute :name + has_many :contact_media +end + +class PartyResource < JSONAPI::Resource +end + ### PORO Data - don't do this in a production app $breed_data = BreedData.new $breed_data.add(Breed.new(0, 'persian')) diff --git a/test/test_helper.rb b/test/test_helper.rb index 97e51fe7d..775fbaf39 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -450,6 +450,16 @@ class CatResource < JSONAPI::Resource mount MyEngine::Engine => "/boomshaka", as: :my_engine mount ApiV2Engine::Engine => "/api_v2", as: :api_v2_engine + + jsonapi_resources :contact_media do + jsonapi_relationships + end + jsonapi_resources :individuals do + jsonapi_relationships + end + jsonapi_resources :organizations do + jsonapi_relationships + end end MyEngine::Engine.routes.draw do