Skip to content

Commit

Permalink
fix: more flexible polymorphic types lookup (#1434)
Browse files Browse the repository at this point in the history
* fix: more flexible polymorphic types lookup

* test: add polymorphic lookup tests

they pass on v-11-dev

I'm going to look into the existing lookup warnings

now
```
[POLYMORPHIC TYPE NOT FOUND] No polymorphic types found for fileable
[POLYMORPHIC TYPE] No polymorphic types found for FilePropertiesResource fileable
[POLYMORPHIC TYPE NOT FOUND] No polymorphic types found for respondent
[POLYMORPHIC TYPE] No polymorphic types found for QuestionResource respondent
[POLYMORPHIC TYPE NOT FOUND] No polymorphic types found for respondent
[POLYMORPHIC TYPE] No polymorphic types found for AnswerResource respondent
[POLYMORPHIC TYPE NOT FOUND] No polymorphic types found for keepable
[POLYMORPHIC TYPE] No polymorphic types found for KeeperResource keepable
```

* Revert "test: add polymorphic lookup tests"

This reverts commit 0979a7243b6bc816dd2327d3ff23f70209c52dce.

* feat: easily clear the lookup

* feat: add a descendents strategy

* test: polymorphic type lookup

* feat: make polymorphic type lookup configurable

* feat: clear polymorphic lookup after initialize
  • Loading branch information
bf4 authored and lgebhardt committed Apr 18, 2024
1 parent 7fa3352 commit f3bbf0c
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 10 deletions.
2 changes: 1 addition & 1 deletion lib/jsonapi/relationship.rb
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def polymorphic_type
def setup_implicit_relationships_for_polymorphic_types(exclude_linkage_data: true)
types = self.class.polymorphic_types(_relation_name)
unless types.present?
warn "No polymorphic types found for #{parent_resource.name} #{_relation_name}"
warn "[POLYMORPHIC TYPE] No polymorphic types found for #{parent_resource.name} #{_relation_name}"
return
end

Expand Down
4 changes: 4 additions & 0 deletions lib/jsonapi/resources/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class Railtie < ::Rails::Railtie
::JSONAPI::MimeTypes.parser.call(body)
}
end

initializer "jsonapi_resources.initialize", after: :initialize do
JSONAPI::Utils::PolymorphicTypesLookup.polymorphic_types_lookup_clear!
end
end
end
end
71 changes: 62 additions & 9 deletions lib/jsonapi/utils/polymorphic_types_lookup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,79 @@ module Utils
module PolymorphicTypesLookup
extend self

def polymorphic_types(name)
polymorphic_types_lookup[name.to_sym]
singleton_class.attr_accessor :build_polymorphic_types_lookup_strategy
self.build_polymorphic_types_lookup_strategy =
:build_polymorphic_types_lookup_from_object_space

def polymorphic_types(name, rebuild: false)
polymorphic_types_lookup(rebuild: rebuild).fetch(name.to_sym, &handle_polymorphic_type_name_found)
end

def handle_polymorphic_type_name_found
@handle_polymorphic_type_name_found ||= lambda do |name|
warn "[POLYMORPHIC TYPE NOT FOUND] No polymorphic types found for #{name}"
nil
end
end

def polymorphic_types_lookup
def polymorphic_types_lookup(rebuild: false)
polymorphic_types_lookup_clear! if rebuild
@polymorphic_types_lookup ||= build_polymorphic_types_lookup
end

def polymorphic_types_lookup_clear!
@polymorphic_types_lookup = nil
end

def build_polymorphic_types_lookup
{}.tap do |hash|
public_send(build_polymorphic_types_lookup_strategy)
end

def build_polymorphic_types_lookup_from_descendants
{}.tap do |lookup|
ActiveRecord::Base
.descendants
.select(&:name)
.reject(&:abstract_class)
.select(&:model_name).map {|klass|
add_polymorphic_types_lookup(klass: klass, lookup: lookup)
}
end
end

def build_polymorphic_types_lookup_from_object_space
{}.tap do |lookup|
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.underscore
end
end
next unless ActiveRecord::Base > klass
add_polymorphic_types_lookup(klass: klass, lookup: lookup)
end
end
end

# TODO(BF): Consider adding the following conditions
# is_active_record_inspectable = true
# is_active_record_inspectable &&= klass.respond_to?(:reflect_on_all_associations, true)
# is_active_record_inspectable &&= format_polymorphic_klass_type(klass).present?
# return unless is_active_record_inspectable
def add_polymorphic_types_lookup(klass:, lookup:)
klass.reflect_on_all_associations(:has_many).select { |r| r.options[:as] }.each do |reflection|
(lookup[reflection.options[:as]] ||= []) << format_polymorphic_klass_type(klass).underscore
end
end

# TODO(BF): Consider adding the following conditions
# klass.name ||
# begin
# klass.model_name.name
# rescue ArgumentError => ex
# # klass.base_class may be nil
# warn "[POLYMORPHIC TYPE] #{__callee__} #{klass} #{ex.inspect}"
# nil
# end
def format_polymorphic_klass_type(klass)
klass.name
end
end
end
end
35 changes: 35 additions & 0 deletions test/unit/utils/polymorphic_types_lookup_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require File.expand_path('../../../test_helper', __FILE__)

class PolymorphicTypesLookupTest < ActiveSupport::TestCase
def setup
JSONAPI::Utils::PolymorphicTypesLookup.polymorphic_types_lookup_clear!
end

def test_build_polymorphic_types_lookup_from_object_space
expected = {
:imageable=>["product", "document"]
}
actual = JSONAPI::Utils::PolymorphicTypesLookup.build_polymorphic_types_lookup_from_object_space
actual_keys = actual.keys.sort
assert_equal(actual_keys, expected.keys.sort)
actual_keys.each do |actual_key|
actual_values = actual[actual_key].sort
expected_values = expected[actual_key].sort
assert_equal(actual_values, expected_values)
end
end

def test_build_polymorphic_types_lookup_from_descendants
expected = {
:imageable=>["document", "product"]
}
actual = JSONAPI::Utils::PolymorphicTypesLookup.build_polymorphic_types_lookup_from_descendants
actual_keys = actual.keys.sort
assert_equal(actual_keys, expected.keys.sort)
actual_keys.each do |actual_key|
actual_values = actual[actual_key].sort
expected_values = expected[actual_key].sort
assert_equal(actual_values, expected_values)
end
end
end

0 comments on commit f3bbf0c

Please sign in to comment.