diff --git a/lib/jsonapi/acts_as_resource_controller.rb b/lib/jsonapi/acts_as_resource_controller.rb index 7422a56ea..a999d43d8 100644 --- a/lib/jsonapi/acts_as_resource_controller.rb +++ b/lib/jsonapi/acts_as_resource_controller.rb @@ -125,7 +125,8 @@ def resource_serializer base_url: base_url, key_formatter: key_formatter, route_formatter: route_formatter, - serialization_options: serialization_options + serialization_options: serialization_options, + controller: self ) @resource_serializer end diff --git a/lib/jsonapi/link_builder.rb b/lib/jsonapi/link_builder.rb index 70d23ba07..99cd6223c 100644 --- a/lib/jsonapi/link_builder.rb +++ b/lib/jsonapi/link_builder.rb @@ -2,23 +2,24 @@ module JSONAPI class LinkBuilder attr_reader :base_url, :primary_resource_klass, + :route_formatter, :engine, - :routes + :engine_mount_point, + :url_helpers + + @@url_helper_methods = {} def initialize(config = {}) - @base_url = config[:base_url] + @base_url = config[:base_url] @primary_resource_klass = config[:primary_resource_klass] - @engine = build_engine - - if engine? - @routes = @engine.routes - else - @routes = Rails.application.routes - end + @route_formatter = config[:route_formatter] + @engine = build_engine + @engine_mount_point = @engine ? @engine.routes.find_script_name({}) : "" - # ToDo: Use NaiveCache for values. For this we need to not return nils and create composite keys which work - # as efficient cache lookups. This could be an array of the [source.identifier, relationship] since the - # ResourceIdentity will compare equality correctly + # url_helpers may be either a controller which has the route helper methods, or the application router's + # url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a + # singleton, and it's expensive to generate the module, the controller is preferred. + @url_helpers = config[:url_helpers] end def engine? @@ -26,50 +27,60 @@ def engine? end def primary_resources_url - @primary_resources_url_cached ||= "#{ base_url }#{ primary_resources_path }" - rescue NoMethodError - warn "primary_resources_url for #{@primary_resource_klass} could not be generated" if JSONAPI.configuration.warn_on_missing_routes + if @primary_resource_klass._routed + primary_resources_path = resources_path(primary_resource_klass) + @primary_resources_url_cached ||= "#{ base_url }#{ engine_mount_point }#{ primary_resources_path }" + else + if JSONAPI.configuration.warn_on_missing_routes && !@primary_resource_klass._warned_missing_route + warn "primary_resources_url for #{@primary_resource_klass} could not be generated" + @primary_resource_klass._warned_missing_route = true + end + nil + end end def query_link(query_params) - "#{ primary_resources_url }?#{ query_params.to_query }" + url = primary_resources_url + return url if url.nil? + "#{ url }?#{ query_params.to_query }" end def relationships_related_link(source, relationship, query_params = {}) - if relationship.parent_resource.singleton? - url_helper_name = singleton_related_url_helper_name(relationship) - url = call_url_helper(url_helper_name) + if relationship._routed + url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }" + url = "#{ url }?#{ query_params.to_query }" if query_params.present? + url else - url_helper_name = related_url_helper_name(relationship) - url = call_url_helper(url_helper_name, source.id) + if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route + warn "related_link for #{relationship} could not be generated" + relationship._warned_missing_route = true + end + nil end - - url = "#{ base_url }#{ url }" - url = "#{ url }?#{ query_params.to_query }" if query_params.present? - url - rescue NoMethodError - warn "related_link for #{relationship} could not be generated" if JSONAPI.configuration.warn_on_missing_routes end def relationships_self_link(source, relationship) - if relationship.parent_resource.singleton? - url_helper_name = singleton_relationship_self_url_helper_name(relationship) - url = call_url_helper(url_helper_name) + if relationship._routed + "#{ self_link(source) }/relationships/#{ route_for_relationship(relationship) }" else - url_helper_name = relationship_self_url_helper_name(relationship) - url = call_url_helper(url_helper_name, source.id) + if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route + warn "self_link for #{relationship} could not be generated" + relationship._warned_missing_route = true + end + nil end - - url = "#{ base_url }#{ url }" - url - rescue NoMethodError - warn "self_link for #{relationship} could not be generated" if JSONAPI.configuration.warn_on_missing_routes end def self_link(source) - "#{ base_url }#{ resource_path(source) }" - rescue NoMethodError - warn "self_link for #{source.class} could not be generated" if JSONAPI.configuration.warn_on_missing_routes + if source.class._routed + resource_url(source) + else + if JSONAPI.configuration.warn_on_missing_routes && !source.class._warned_missing_route + warn "self_link for #{source.class} could not be generated" + source.class._warned_missing_route = true + end + nil + end end private @@ -81,6 +92,7 @@ def build_engine unless scopes.empty? "#{ scopes.first.to_s.camelize }::Engine".safe_constantize end + # :nocov: rescue LoadError => _e nil @@ -88,98 +100,47 @@ def build_engine end end - def call_url_helper(method, *args) - routes.url_helpers.public_send(method, args) - rescue NoMethodError => e - raise e + def format_route(route) + route_formatter.format(route) end - def path_from_resource_class(klass) - url_helper_name = resources_url_helper_name_from_class(klass) - call_url_helper(url_helper_name) - end + def formatted_module_path_from_class(klass) + scopes = if @engine + module_scopes_from_class(klass)[1..-1] + else + module_scopes_from_class(klass) + end - def resource_path(source) - url_helper_name = resource_url_helper_name_from_source(source) - if source.class.singleton? - call_url_helper(url_helper_name) + unless scopes.empty? + "/#{ scopes.map {|scope| format_route(scope.to_s.underscore)}.compact.join('/') }/" else - call_url_helper(url_helper_name, source.id) + "/" end end - def primary_resources_path - path_from_resource_class(primary_resource_klass) + def module_scopes_from_class(klass) + klass.name.to_s.split("::")[0...-1] end - def url_helper_name_from_parts(parts) - (parts << "path").reject(&:blank?).join("_") + def resources_path(source_klass) + formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s) end - def resources_path_parts_from_class(klass) - if engine? - scopes = module_scopes_from_class(klass)[1..-1] - else - scopes = module_scopes_from_class(klass) - end - - base_path_name = scopes.map { |scope| scope.underscore }.join("_") - end_path_name = klass._type.to_s - [base_path_name, end_path_name] - end - - def resources_url_helper_name_from_class(klass) - url_helper_name_from_parts(resources_path_parts_from_class(klass)) - end + def resource_path(source) + url = "#{resources_path(source.class)}" - def resource_path_parts_from_class(klass) - if engine? - scopes = module_scopes_from_class(klass)[1..-1] - else - scopes = module_scopes_from_class(klass) + unless source.class.singleton? + url = "#{url}/#{source.id}" end - - base_path_name = scopes.map { |scope| scope.underscore }.join("_") - end_path_name = klass._type.to_s.singularize - [base_path_name, end_path_name] - end - - def resource_url_helper_name_from_source(source) - url_helper_name_from_parts(resource_path_parts_from_class(source.class)) - end - - def related_url_helper_name(relationship) - relationship_parts = resource_path_parts_from_class(relationship.parent_resource) - relationship_parts << "related" - relationship_parts << relationship.name - url_helper_name_from_parts(relationship_parts) - end - - def singleton_related_url_helper_name(relationship) - relationship_parts = [] - relationship_parts << "related" - relationship_parts << relationship.name - relationship_parts += resource_path_parts_from_class(relationship.parent_resource) - url_helper_name_from_parts(relationship_parts) - end - - def relationship_self_url_helper_name(relationship) - relationship_parts = resource_path_parts_from_class(relationship.parent_resource) - relationship_parts << "relationships" - relationship_parts << relationship.name - url_helper_name_from_parts(relationship_parts) + url end - def singleton_relationship_self_url_helper_name(relationship) - relationship_parts = [] - relationship_parts << "relationships" - relationship_parts << relationship.name - relationship_parts += resource_path_parts_from_class(relationship.parent_resource) - url_helper_name_from_parts(relationship_parts) + def resource_url(source) + "#{ base_url }#{ engine_mount_point }#{ resource_path(source) }" end - def module_scopes_from_class(klass) - klass.name.to_s.split("::")[0...-1] + def route_for_relationship(relationship) + format_route(relationship.name) end end end diff --git a/lib/jsonapi/relationship.rb b/lib/jsonapi/relationship.rb index 5a0304bf1..8f17658df 100644 --- a/lib/jsonapi/relationship.rb +++ b/lib/jsonapi/relationship.rb @@ -4,6 +4,8 @@ class Relationship :class_name, :polymorphic, :always_include_linkage_data, :parent_resource, :eager_load_on_include + attr_accessor :_routed, :_warned_missing_route + def initialize(name, options = {}) @name = name.to_s @options = options @@ -15,6 +17,9 @@ def initialize(name, options = {}) @always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true @eager_load_on_include = options.fetch(:eager_load_on_include, true) == true + @_routed = false + @_warned_missing_route = false + exclude_links(options.fetch(:exclude_links, :none)) end diff --git a/lib/jsonapi/resource.rb b/lib/jsonapi/resource.rb index b55b64bcf..fc288a96b 100644 --- a/lib/jsonapi/resource.rb +++ b/lib/jsonapi/resource.rb @@ -457,6 +457,9 @@ def inherited(subclass) end check_reserved_resource_name(subclass._type, subclass.name) + + subclass._routed = false + subclass._warned_missing_route = false end def rebuild_relationships(relationships) @@ -502,7 +505,7 @@ def resource_type_for(model) end end - attr_accessor :_attributes, :_relationships, :_type, :_model_hints + attr_accessor :_attributes, :_relationships, :_type, :_model_hints, :_routed, :_warned_missing_route attr_writer :_allowed_filters, :_paginator def create(context) diff --git a/lib/jsonapi/resource_serializer.rb b/lib/jsonapi/resource_serializer.rb index dee017e93..3bdf4dd49 100644 --- a/lib/jsonapi/resource_serializer.rb +++ b/lib/jsonapi/resource_serializer.rb @@ -539,6 +539,8 @@ def generate_link_builder(primary_resource_klass, options) LinkBuilder.new( base_url: options.fetch(:base_url, ''), primary_resource_klass: primary_resource_klass, + route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter), + url_helpers: options.fetch(:url_helpers, options[:controller]), ) end end diff --git a/lib/jsonapi/routing_ext.rb b/lib/jsonapi/routing_ext.rb index 7140522d4..5aae3537e 100644 --- a/lib/jsonapi/routing_ext.rb +++ b/lib/jsonapi/routing_ext.rb @@ -20,6 +20,8 @@ def jsonapi_resource(*resources, &_block) @resource_type = resources.first res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type)) + res._routed = true + unless res.singleton? warn "Singleton routes created for non singleton resource #{res}. Links may not be generated correctly." end @@ -84,6 +86,8 @@ def jsonapi_resources(*resources, &_block) @resource_type = resources.first res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type)) + res._routed = true + if res.singleton? warn "Singleton resource #{res} should use `jsonapi_resource` instead." end @@ -223,6 +227,8 @@ def jsonapi_related_resource(*relationship) relationship_name = relationship.first relationship = source._relationships[relationship_name] + relationship._routed = true + formatted_relationship_name = format_route(relationship.name) if relationship.polymorphic? @@ -245,6 +251,8 @@ def jsonapi_related_resources(*relationship) relationship_name = relationship.first relationship = source._relationships[relationship_name] + relationship._routed = true + formatted_relationship_name = format_route(relationship.name) related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(relationship.class_name.underscore)) options[:controller] ||= related_resource._type.to_s diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 8f9c1d727..adbb99969 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -1681,7 +1681,9 @@ module V4 class PostResource < PostResource; end class PersonResource < PersonResource; end class ExpenseEntryResource < ExpenseEntryResource; end - class IsoCurrencyResource < IsoCurrencyResource; end + class IsoCurrencyResource < IsoCurrencyResource + has_many :expense_entries, exclude_links: :default + end class BookResource < Api::V2::BookResource paginator :paged @@ -1987,6 +1989,7 @@ class PersonResource < JSONAPI::Resource module ApiV2Engine class PostResource < PostResource + has_one :person end class PersonResource < JSONAPI::Resource diff --git a/test/integration/requests/request_test.rb b/test/integration/requests/request_test.rb index 2f0f46882..4dfc967db 100644 --- a/test/integration/requests/request_test.rb +++ b/test/integration/requests/request_test.rb @@ -92,6 +92,7 @@ def test_get_camelized_route_and_key_filtered def test_get_camelized_route_and_links original_config = JSONAPI.configuration.dup JSONAPI.configuration.json_key_format = :camelized_key + JSONAPI.configuration.route_format = :camelized_route assert_cacheable_jsonapi_get '/api/v4/expenseEntries/1/relationships/isoCurrency' assert_hash_equals({'links' => { 'self' => 'http://www.example.com/api/v4/expenseEntries/1/relationships/isoCurrency', diff --git a/test/unit/serializer/link_builder_test.rb b/test/unit/serializer/link_builder_test.rb index a1affcf8c..d7c277ad2 100644 --- a/test/unit/serializer/link_builder_test.rb +++ b/test/unit/serializer/link_builder_test.rb @@ -2,6 +2,20 @@ require 'jsonapi-resources' require 'json' +module Api + module Secret + class PostResource < JSONAPI::Resource + attribute :title + attribute :body + + has_one :author, class_name: 'Person' + end + + class PersonResource < JSONAPI::Resource + end + end +end + class LinkBuilderTest < ActionDispatch::IntegrationTest def setup # the route format is being set directly in test_helper and is being set differently depending on @@ -11,7 +25,9 @@ def setup @base_url = "http://example.com" @route_formatter = JSONAPI.configuration.route_formatter - @steve = Person.create(name: "Steve Rogers", date_joined: "1941-03-01") + @steve = Person.create(name: "Steve Rogers", date_joined: "1941-03-01", id: 777) + @steves_prefs = Preferences.create(advanced_mode: true, id: 444, person_id: 777) + @great_post = Post.create(title: "Greatest Post", id: 555) end def test_engine_boolean @@ -51,6 +67,7 @@ def test_self_link_regular_app base_url: @base_url, route_formatter: @route_formatter, primary_resource_klass: primary_resource_klass, + url_helpers: TestApp.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -60,13 +77,200 @@ def test_self_link_regular_app assert_equal expected_link, builder.self_link(source) end + def test_self_link_regular_app_not_routed + primary_resource_klass = Api::Secret::PostResource + + config = { + base_url: @base_url, + route_formatter: @route_formatter, + primary_resource_klass: primary_resource_klass, + url_helpers: TestApp.routes.url_helpers, + } + + builder = JSONAPI::LinkBuilder.new(config) + source = primary_resource_klass.new(@great_post, nil) + + + # Should not warn if warn_on_missing_routes is false + JSONAPI.configuration.warn_on_missing_routes = false + primary_resource_klass._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.self_link(source) + assert_nil link + end + assert_empty(err) + + # Test warn_on_missing_routes + JSONAPI.configuration.warn_on_missing_routes = true + primary_resource_klass._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.self_link(source) + assert_nil link + end + assert_equal(err, "self_link for Api::Secret::PostResource could not be generated\n") + + # should only warn once + builder = JSONAPI::LinkBuilder.new(config) + _out, err = capture_subprocess_io do + link = builder.self_link(source) + assert_nil link + end + assert_empty(err) + + ensure + JSONAPI.configuration.warn_on_missing_routes = true + end + + def test_primary_resources_url_not_routed + primary_resource_klass = Api::Secret::PostResource + + config = { + base_url: @base_url, + route_formatter: @route_formatter, + primary_resource_klass: primary_resource_klass, + url_helpers: TestApp.routes.url_helpers, + } + + builder = JSONAPI::LinkBuilder.new(config) + + # Should not warn if warn_on_missing_routes is false + JSONAPI.configuration.warn_on_missing_routes = false + primary_resource_klass._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.primary_resources_url + assert_nil link + end + assert_empty(err) + + # Test warn_on_missing_routes + JSONAPI.configuration.warn_on_missing_routes = true + primary_resource_klass._warned_missing_route = false + _out, err = capture_subprocess_io do + link = builder.primary_resources_url + assert_nil link + end + assert_equal(err, "primary_resources_url for Api::Secret::PostResource could not be generated\n") + + # should only warn once + builder = JSONAPI::LinkBuilder.new(config) + _out, err = capture_subprocess_io do + link = builder.primary_resources_url + assert_nil link + end + assert_empty(err) + + ensure + JSONAPI.configuration.warn_on_missing_routes = true + end + + def test_relationships_self_link_not_routed + primary_resource_klass = Api::Secret::PostResource + + config = { + base_url: @base_url, + route_formatter: @route_formatter, + primary_resource_klass: primary_resource_klass, + url_helpers: TestApp.routes.url_helpers, + } + + builder = JSONAPI::LinkBuilder.new(config) + + source = primary_resource_klass.new(@great_post, nil) + + relationship = Api::Secret::PostResource._relationships[:author] + + # Should not warn if warn_on_missing_routes is false + JSONAPI.configuration.warn_on_missing_routes = false + relationship._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.relationships_self_link(source, relationship) + assert_nil link + end + assert_empty(err) + + # Test warn_on_missing_routes + JSONAPI.configuration.warn_on_missing_routes = true + relationship._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.relationships_self_link(source, relationship) + assert_nil link + end + assert_equal(err, "self_link for Api::Secret::PostResource.author(BelongsToOne) could not be generated\n") + + # should only warn once + builder = JSONAPI::LinkBuilder.new(config) + _out, err = capture_subprocess_io do + link = builder.relationships_self_link(source, relationship) + assert_nil link + end + assert_empty(err) + + ensure + JSONAPI.configuration.warn_on_missing_routes = true + end + + def test_relationships_related_link_not_routed + primary_resource_klass = Api::Secret::PostResource + + config = { + base_url: @base_url, + route_formatter: @route_formatter, + primary_resource_klass: primary_resource_klass, + url_helpers: TestApp.routes.url_helpers, + } + + builder = JSONAPI::LinkBuilder.new(config) + + source = primary_resource_klass.new(@great_post, nil) + + relationship = Api::Secret::PostResource._relationships[:author] + + # Should not warn if warn_on_missing_routes is false + JSONAPI.configuration.warn_on_missing_routes = false + relationship._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.relationships_related_link(source, relationship) + assert_nil link + end + assert_empty(err) + + # Test warn_on_missing_routes + JSONAPI.configuration.warn_on_missing_routes = true + relationship._warned_missing_route = false + + _out, err = capture_subprocess_io do + link = builder.relationships_related_link(source, relationship) + assert_nil link + end + assert_equal(err, "related_link for Api::Secret::PostResource.author(BelongsToOne) could not be generated\n") + + # should only warn once + builder = JSONAPI::LinkBuilder.new(config) + _out, err = capture_subprocess_io do + link = builder.relationships_related_link(source, relationship) + assert_nil link + end + assert_empty(err) + + ensure + JSONAPI.configuration.warn_on_missing_routes = true + end + def test_self_link_with_engine_app primary_resource_klass = ApiV2Engine::PersonResource + primary_resource_klass._warned_missing_route = false config = { - base_url: @base_url, + base_url: "#{ @base_url }", route_formatter: @route_formatter, primary_resource_klass: primary_resource_klass, + url_helpers: ApiV2Engine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -83,6 +287,7 @@ def test_self_link_with_engine_namespaced_app base_url: @base_url, route_formatter: @route_formatter, primary_resource_klass: primary_resource_klass, + url_helpers: MyEngine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -99,6 +304,7 @@ def test_self_link_with_engine_app_and_camel_case_scope base_url: @base_url, route_formatter: @route_formatter, primary_resource_klass: primary_resource_klass, + url_helpers: MyEngine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -113,6 +319,7 @@ def test_primary_resources_url_for_regular_app base_url: @base_url, route_formatter: @route_formatter, primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -125,7 +332,8 @@ def test_primary_resources_url_for_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: ApiV2Engine::PersonResource + primary_resource_klass: ApiV2Engine::PersonResource, + url_helpers: ApiV2Engine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -138,7 +346,8 @@ def test_primary_resources_url_for_namespaced_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: MyEngine::Api::V1::PersonResource + primary_resource_klass: MyEngine::Api::V1::PersonResource, + url_helpers: MyEngine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) @@ -151,28 +360,64 @@ def test_relationships_self_link_for_regular_app config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: Api::V1::PersonResource + primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = Api::V1::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: Api::V1::PersonResource}) + relationship = Api::V1::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/api/v1/people/#{ @steve.id }/relationships/posts" assert_equal expected_link, builder.relationships_self_link(source, relationship) end + def test_relationships_self_link_for_regular_app_singleton + config = { + base_url: @base_url, + route_formatter: @route_formatter, + primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, + } + + builder = JSONAPI::LinkBuilder.new(config) + source = Api::V1::PreferencesResource.new(@steves_prefs, nil) + relationship = Api::V1::PreferencesResource._relationships[:author] + expected_link = "#{ @base_url }/api/v1/preferences/relationships/author" + + assert_equal expected_link, + builder.relationships_self_link(source, relationship) + end + + def test_relationships_related_link_for_regular_app_singleton + config = { + base_url: @base_url, + route_formatter: @route_formatter, + primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, + } + + builder = JSONAPI::LinkBuilder.new(config) + source = Api::V1::PreferencesResource.new(@steves_prefs, nil) + relationship = Api::V1::PreferencesResource._relationships[:author] + expected_link = "#{ @base_url }/api/v1/preferences/author" + + assert_equal expected_link, + builder.relationships_related_link(source, relationship) + end + def test_relationships_self_link_for_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: ApiV2Engine::PersonResource + primary_resource_klass: ApiV2Engine::PersonResource, + url_helpers: ApiV2Engine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = ApiV2Engine::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: ApiV2Engine::PersonResource}) + relationship = ApiV2Engine::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/api_v2/people/#{ @steve.id }/relationships/posts" assert_equal expected_link, @@ -183,12 +428,13 @@ def test_relationships_self_link_for_namespaced_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: MyEngine::Api::V1::PersonResource + primary_resource_klass: MyEngine::Api::V1::PersonResource, + url_helpers: MyEngine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = MyEngine::Api::V1::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: MyEngine::Api::V1::PersonResource}) + relationship = MyEngine::Api::V1::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/boomshaka/api/v1/people/#{ @steve.id }/relationships/posts" assert_equal expected_link, @@ -199,12 +445,13 @@ def test_relationships_related_link_for_regular_app config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: Api::V1::PersonResource + primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = Api::V1::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: Api::V1::PersonResource}) + relationship = Api::V1::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/api/v1/people/#{ @steve.id }/posts" assert_equal expected_link, @@ -215,12 +462,13 @@ def test_relationships_related_link_for_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: ApiV2Engine::PersonResource + primary_resource_klass: ApiV2Engine::PersonResource, + url_helpers: ApiV2Engine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = ApiV2Engine::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: ApiV2Engine::PersonResource}) + relationship = ApiV2Engine::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/api_v2/people/#{ @steve.id }/posts" assert_equal expected_link, @@ -231,12 +479,13 @@ def test_relationships_related_link_for_namespaced_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: MyEngine::Api::V1::PersonResource + primary_resource_klass: MyEngine::Api::V1::PersonResource, + url_helpers: MyEngine::Engine.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = MyEngine::Api::V1::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: MyEngine::Api::V1::PersonResource}) + relationship = MyEngine::Api::V1::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/boomshaka/api/v1/people/#{ @steve.id }/posts" assert_equal expected_link, @@ -247,12 +496,13 @@ def test_relationships_related_link_with_query_params config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: Api::V1::PersonResource + primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } builder = JSONAPI::LinkBuilder.new(config) source = Api::V1::PersonResource.new(@steve, nil) - relationship = JSONAPI::Relationship::ToMany.new("posts", {parent_resource: Api::V1::PersonResource}) + relationship = Api::V1::PersonResource._relationships[:posts] expected_link = "#{ @base_url }/api/v1/people/#{ @steve.id }/posts?page%5Blimit%5D=12&page%5Boffset%5D=0" query = { page: { offset: 0, limit: 12 } } @@ -264,7 +514,8 @@ def test_query_link_for_regular_app config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: Api::V1::PersonResource + primary_resource_klass: Api::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } @@ -278,7 +529,8 @@ def test_query_link_for_regular_app_with_camel_case_scope config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: AdminApi::V1::PersonResource + primary_resource_klass: AdminApi::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } @@ -292,7 +544,8 @@ def test_query_link_for_regular_app_with_dasherized_scope config = { base_url: @base_url, route_formatter: DasherizedRouteFormatter, - primary_resource_klass: DasherizedNamespace::V1::PersonResource + primary_resource_klass: DasherizedNamespace::V1::PersonResource, + url_helpers: TestApp.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } @@ -306,7 +559,8 @@ def test_query_link_for_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: ApiV2Engine::PersonResource + primary_resource_klass: ApiV2Engine::PersonResource, + url_helpers: ApiV2Engine::Engine.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } @@ -320,7 +574,8 @@ def test_query_link_for_namespaced_engine config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: MyEngine::Api::V1::PersonResource + primary_resource_klass: MyEngine::Api::V1::PersonResource, + url_helpers: MyEngine::Engine.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } @@ -334,7 +589,8 @@ def test_query_link_for_engine_with_dasherized_scope config = { base_url: @base_url, route_formatter: DasherizedRouteFormatter, - primary_resource_klass: MyEngine::DasherizedNamespace::V1::PersonResource + primary_resource_klass: MyEngine::DasherizedNamespace::V1::PersonResource, + url_helpers: MyEngine::Engine.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } @@ -348,7 +604,8 @@ def test_query_link_for_engine_with_camel_case_scope config = { base_url: @base_url, route_formatter: @route_formatter, - primary_resource_klass: MyEngine::AdminApi::V1::PersonResource + primary_resource_klass: MyEngine::AdminApi::V1::PersonResource, + url_helpers: MyEngine::Engine.routes.url_helpers, } query = { page: { offset: 0, limit: 12 } } diff --git a/test/unit/serializer/polymorphic_serializer_test.rb b/test/unit/serializer/polymorphic_serializer_test.rb index eb9b14d83..163d2e29f 100644 --- a/test/unit/serializer/polymorphic_serializer_test.rb +++ b/test/unit/serializer/polymorphic_serializer_test.rb @@ -26,10 +26,11 @@ def test_polymorphic_relationship end def test_sti_polymorphic_to_many_serialization - serialized_data = JSONAPI::ResourceSerializer.new( - PersonResource, - include: %w(vehicles) - ).serialize_to_hash(PersonResource.new(@person, nil)) + serializer = JSONAPI::ResourceSerializer.new(PersonResource, + include: %w(vehicles), + url_helpers: TestApp.routes.url_helpers) + + serialized_data = serializer.serialize_to_hash(PersonResource.new(@person, nil)) assert_hash_equals( { @@ -75,8 +76,8 @@ def test_sti_polymorphic_to_many_serialization }, hairCut: { links: { - self: '/people/1/relationships/hair_cut', - related: '/people/1/hair_cut' + self: '/people/1/relationships/hairCut', + related: '/people/1/hairCut' } } } @@ -132,10 +133,9 @@ def test_sti_polymorphic_to_many_serialization def test_sti_polymorphic_to_many_serialization_with_custom_polymorphic_records person_resource = PersonResource.new(@person, nil) - serializer = JSONAPI::ResourceSerializer.new( - PersonResource, - include: %w(vehicles) - ) + serializer = JSONAPI::ResourceSerializer.new(PersonResource, + include: %w(vehicles), + url_helpers: TestApp.routes.url_helpers) def person_resource.records_for_vehicles(opts = {}) @model.vehicles.none @@ -184,8 +184,8 @@ def person_resource.records_for_vehicles(opts = {}) }, hairCut: { links: { - self: '/people/1/relationships/hair_cut', - related: '/people/1/hair_cut' + self: '/people/1/relationships/hairCut', + related: '/people/1/hairCut' } } } @@ -198,10 +198,11 @@ def person_resource.records_for_vehicles(opts = {}) def test_polymorphic_belongs_to_serialization - serialized_data = JSONAPI::ResourceSerializer.new( - PictureResource, - include: %w(imageable) - ).serialize_to_hash(@pictures.map { |p| PictureResource.new p, nil }) + serializer = JSONAPI::ResourceSerializer.new(PictureResource, + include: %w(imageable), + url_helpers: TestApp.routes.url_helpers) + + serialized_data = serializer.serialize_to_hash(@pictures.map {|p| PictureResource.new p, nil}) assert_hash_equals( { @@ -319,10 +320,12 @@ def test_polymorphic_belongs_to_serialization end def test_polymorphic_has_one_serialization - serialized_data = JSONAPI::ResourceSerializer.new( + serializer = JSONAPI::ResourceSerializer.new( QuestionResource, - include: %w(respondent) - ).serialize_to_hash(@questions.map { |p| QuestionResource.new p, nil }) + include: %w(respondent), + url_helpers: TestApp.routes.url_helpers) + + serialized_data = serializer.serialize_to_hash(@questions.map { |p| QuestionResource.new p, nil }) assert_hash_equals( { diff --git a/test/unit/serializer/response_document_test.rb b/test/unit/serializer/response_document_test.rb index a32727d3d..17d8c4b82 100644 --- a/test/unit/serializer/response_document_test.rb +++ b/test/unit/serializer/response_document_test.rb @@ -9,12 +9,13 @@ def setup end def create_response_document(operation_results, resource_klass) - JSONAPI::ResponseDocument.new( - operation_results, - JSONAPI::ResourceSerializer.new(resource_klass), - { - primary_resource_klass: resource_klass - } + serializer = JSONAPI::ResourceSerializer.new(resource_klass, url_helpers: TestApp.routes.url_helpers) + + JSONAPI::ResponseDocument.new(operation_results, + serializer, + { + primary_resource_klass: resource_klass + } ) end diff --git a/test/unit/serializer/serializer_test.rb b/test/unit/serializer/serializer_test.rb index 4cfe3fb6a..457ce2ebf 100644 --- a/test/unit/serializer/serializer_test.rb +++ b/test/unit/serializer/serializer_test.rb @@ -21,10 +21,11 @@ def after_teardown def test_serializer - serialized = JSONAPI::ResourceSerializer.new( - PostResource, - base_url: 'http://example.com').serialize_to_hash(PostResource.new(@post, nil) - ) + serializer = JSONAPI::ResourceSerializer.new(PostResource, + base_url: 'http://example.com', + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PostResource.new(@post, nil)) assert_hash_equals( { @@ -72,15 +73,22 @@ def test_serializer end def test_serializer_nil_handling + serializer = JSONAPI::ResourceSerializer.new(PostResource, + url_helpers: TestApp.routes.url_helpers) + assert_hash_equals( { data: nil }, - JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(nil) + serializer.serialize_to_hash(nil) ) end def test_serializer_namespaced_resource + serializer = JSONAPI::ResourceSerializer.new(Api::V1::PostResource, + base_url: 'http://example.com', + url_helpers: TestApp.routes.url_helpers) + assert_hash_equals( { data: { @@ -117,13 +125,14 @@ def test_serializer_namespaced_resource } } }, - JSONAPI::ResourceSerializer.new(Api::V1::PostResource, - base_url: 'http://example.com').serialize_to_hash( - Api::V1::PostResource.new(@post, nil)) + serializer.serialize_to_hash(Api::V1::PostResource.new(@post, nil)) ) end def test_serializer_limited_fieldset + serializer = JSONAPI::ResourceSerializer.new(PostResource, + fields: {posts: [:id, :title, :author]}, + url_helpers: TestApp.routes.url_helpers) assert_hash_equals( { @@ -146,16 +155,16 @@ def test_serializer_limited_fieldset } } }, - JSONAPI::ResourceSerializer.new(PostResource, - fields: {posts: [:id, :title, :author]}).serialize_to_hash(PostResource.new(@post, nil)) + serializer.serialize_to_hash(PostResource.new(@post, nil)) ) end def test_serializer_include - serialized = JSONAPI::ResourceSerializer.new( - PostResource, - include: ['author'] - ).serialize_to_hash(PostResource.new(@post, nil)) + serializer = JSONAPI::ResourceSerializer.new(PostResource, + include: ['author'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PostResource.new(@post, nil)) assert_hash_equals( { @@ -234,8 +243,8 @@ def test_serializer_include }, hairCut: { links: { - self: "/people/1/relationships/hair_cut", - related: "/people/1/hair_cut" + self: "/people/1/relationships/hairCut", + related: "/people/1/hairCut" } }, vehicles: { @@ -256,11 +265,13 @@ def test_serializer_filtered_include painter = Painter.find(1) include_directives = JSONAPI::IncludeDirectives.new(Api::V5::PainterResource, ['paintings']) include_directives.merge_filter('paintings', category: ['oil']) - serialized = JSONAPI::ResourceSerializer.new( - Api::V5::PainterResource, - include_directives: include_directives, - fields: {painters: [:id], paintings: [:id]} - ).serialize_to_hash(Api::V5::PainterResource.new(painter, nil)) + + serializer = JSONAPI::ResourceSerializer.new(Api::V5::PainterResource, + include_directives: include_directives, + fields: {painters: [:id], paintings: [:id]}, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(Api::V5::PainterResource.new(painter, nil)) assert_hash_equals( { @@ -293,11 +304,12 @@ def test_serializer_filtered_include end def test_serializer_key_format - serialized = JSONAPI::ResourceSerializer.new( - PostResource, - include: ['author'], - key_formatter: UnderscoredKeyFormatter - ).serialize_to_hash(PostResource.new(@post, nil)) + serializer = JSONAPI::ResourceSerializer.new(PostResource, + include: ['author'], + key_formatter: UnderscoredKeyFormatter, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PostResource.new(@post, nil)) assert_hash_equals( { @@ -376,8 +388,8 @@ def test_serializer_key_format }, hair_cut: { links: { - self: '/people/1/relationships/hair_cut', - related: '/people/1/hair_cut' + self: '/people/1/relationships/hairCut', + related: '/people/1/hairCut' } }, vehicles: { @@ -396,6 +408,12 @@ def test_serializer_key_format def test_serializer_include_sub_objects + serializer = JSONAPI::ResourceSerializer.new(PostResource, + include: ['comments', 'comments.tags'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PostResource.new(@post, nil)) + assert_hash_equals( { data: { @@ -565,8 +583,7 @@ def test_serializer_include_sub_objects } ] }, - JSONAPI::ResourceSerializer.new(PostResource, - include: ['comments', 'comments.tags']).serialize_to_hash(PostResource.new(@post, nil)) + serialized ) end @@ -574,11 +591,13 @@ def test_serializer_keeps_sorted_order_of_objects_with_self_referential_relation post1, post2, post3 = Post.find(1), Post.find(2), Post.find(3) post1.parent_post = post3 ordered_posts = [post1, post2, post3] - serialized_data = JSONAPI::ResourceSerializer.new( - ParentApi::PostResource, - include: ['parent_post'], - base_url: 'http://example.com').serialize_to_hash(ordered_posts.map {|p| ParentApi::PostResource.new(p, nil)} - )[:data] + + serializer = JSONAPI::ResourceSerializer.new(ParentApi::PostResource, + include: ['parent_post'], + base_url: 'http://example.com', + url_helpers: TestApp.routes.url_helpers) + + serialized_data = serializer.serialize_to_hash(ordered_posts.map {|p| ParentApi::PostResource.new(p, nil)})[:data] assert_equal(3, serialized_data.length) assert_equal("1", serialized_data[0]["id"]) @@ -588,10 +607,11 @@ def test_serializer_keeps_sorted_order_of_objects_with_self_referential_relation def test_serializer_different_foreign_key - serialized = JSONAPI::ResourceSerializer.new( - PersonResource, - include: ['comments'] - ).serialize_to_hash(PersonResource.new(@fred, nil)) + serializer = JSONAPI::ResourceSerializer.new(PersonResource, + include: ['comments'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PersonResource.new(@fred, nil)) assert_hash_equals( { @@ -631,8 +651,8 @@ def test_serializer_different_foreign_key }, hairCut: { links: { - self: "/people/2/relationships/hair_cut", - related: "/people/2/hair_cut" + self: "/people/2/relationships/hairCut", + related: "/people/2/hairCut" } }, vehicles: { @@ -719,6 +739,12 @@ def test_serializer_array_of_resources_always_include_to_one_linkage_data JSONAPI.configuration.always_include_to_one_linkage_data = true + serializer = JSONAPI::ResourceSerializer.new(PostResource, + include: ['comments', 'comments.tags'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(posts) + assert_hash_equals( { data: [ @@ -1019,8 +1045,7 @@ def test_serializer_array_of_resources_always_include_to_one_linkage_data } ] }, - JSONAPI::ResourceSerializer.new(PostResource, - include: ['comments', 'comments.tags']).serialize_to_hash(posts) + serialized ) ensure JSONAPI.configuration.always_include_to_one_linkage_data = false @@ -1033,6 +1058,12 @@ def test_serializer_array_of_resources posts.push PostResource.new(post, nil) end + serializer = JSONAPI::ResourceSerializer.new(PostResource, + include: ['comments', 'comments.tags'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(posts) + assert_hash_equals( { data: [ @@ -1296,8 +1327,7 @@ def test_serializer_array_of_resources } ] }, - JSONAPI::ResourceSerializer.new(PostResource, - include: ['comments', 'comments.tags']).serialize_to_hash(posts) + serialized ) end @@ -1308,6 +1338,18 @@ def test_serializer_array_of_resources_limited_fields posts.push PostResource.new(post, nil) end + serializer = JSONAPI::ResourceSerializer.new(PostResource, + include: ['comments', 'author', 'comments.tags', 'author.posts'], + fields: { + people: [:id, :email, :comments], + posts: [:id, :title], + tags: [:name], + comments: [:id, :body, :post] + }, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(posts) + assert_hash_equals( { data: [ @@ -1457,18 +1499,18 @@ def test_serializer_array_of_resources_limited_fields } ] }, - JSONAPI::ResourceSerializer.new(PostResource, - include: ['comments', 'author', 'comments.tags', 'author.posts'], - fields: { - people: [:id, :email, :comments], - posts: [:id, :title], - tags: [:name], - comments: [:id, :body, :post] - }).serialize_to_hash(posts) + serialized ) end def test_serializer_camelized_with_value_formatters + serializer = JSONAPI::ResourceSerializer.new(ExpenseEntryResource, + include: ['iso_currency', 'employee'], + fields: {people: [:id, :name, :email, :date_joined]}, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(ExpenseEntryResource.new(@expense_entry, nil)) + assert_hash_equals( { data: { @@ -1479,13 +1521,13 @@ def test_serializer_camelized_with_value_formatters cost: '12.05' }, links: { - self: '/expense_entries/1' + self: '/expenseEntries/1' }, relationships: { isoCurrency: { links: { - self: '/expense_entries/1/relationships/iso_currency', - related: '/expense_entries/1/iso_currency' + self: '/expenseEntries/1/relationships/isoCurrency', + related: '/expenseEntries/1/isoCurrency' }, data: { type: 'isoCurrencies', @@ -1494,8 +1536,8 @@ def test_serializer_camelized_with_value_formatters }, employee: { links: { - self: '/expense_entries/1/relationships/employee', - related: '/expense_entries/1/employee' + self: '/expenseEntries/1/relationships/employee', + related: '/expenseEntries/1/employee' }, data: { type: 'people', @@ -1514,7 +1556,7 @@ def test_serializer_camelized_with_value_formatters minorUnit: 'cent' }, links: { - self: '/iso_currencies/USD' + self: '/isoCurrencies/USD' } }, { @@ -1531,16 +1573,15 @@ def test_serializer_camelized_with_value_formatters } ] }, - JSONAPI::ResourceSerializer.new(ExpenseEntryResource, - include: ['iso_currency', 'employee'], - fields: {people: [:id, :name, :email, :date_joined]}).serialize_to_hash( - ExpenseEntryResource.new(@expense_entry, nil)) + serialized ) end def test_serializer_empty_links_null_and_array - planet_hash = JSONAPI::ResourceSerializer.new(PlanetResource).serialize_to_hash( - PlanetResource.new(Planet.find(8), nil)) + serializer = JSONAPI::ResourceSerializer.new(PlanetResource, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PlanetResource.new(Planet.find(8), nil)) assert_hash_equals( { @@ -1557,8 +1598,8 @@ def test_serializer_empty_links_null_and_array relationships: { planetType: { links: { - self: '/planets/8/relationships/planet_type', - related: '/planets/8/planet_type' + self: '/planets/8/relationships/planetType', + related: '/planets/8/planetType' } }, tags: { @@ -1575,7 +1616,9 @@ def test_serializer_empty_links_null_and_array } } } - }, planet_hash) + }, + serialized + ) end def test_serializer_include_with_empty_links_null_and_array @@ -1584,93 +1627,98 @@ def test_serializer_include_with_empty_links_null_and_array planets.push PlanetResource.new(planet, nil) end - planet_hash = JSONAPI::ResourceSerializer.new(PlanetResource, - include: ['planet_type'], - fields: { planet_types: [:id, :name] }).serialize_to_hash(planets) + serializer = JSONAPI::ResourceSerializer.new(PlanetResource, + include: ['planet_type'], + fields: {planet_types: [:id, :name]}, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(planets) assert_hash_equals( { data: [{ - type: 'planets', - id: '7', - attributes: { - name: 'Beta X', - description: 'Newly discovered Planet Z' - }, - links: { - self: '/planets/7' - }, - relationships: { - planetType: { - links: { - self: '/planets/7/relationships/planet_type', - related: '/planets/7/planet_type' - }, - data: { - type: 'planetTypes', - id: '5' - } - }, - tags: { - links: { - self: '/planets/7/relationships/tags', - related: '/planets/7/tags' - } - }, - moons: { - links: { - self: '/planets/7/relationships/moons', - related: '/planets/7/moons' - } - } - } - }, - { - type: 'planets', - id: '8', - attributes: { - name: 'Beta W', - description: 'Newly discovered Planet W' - }, - links: { - self: '/planets/8' - }, - relationships: { - planetType: { - links: { - self: '/planets/8/relationships/planet_type', - related: '/planets/8/planet_type' - }, - data: nil - }, - tags: { - links: { - self: '/planets/8/relationships/tags', - related: '/planets/8/tags' - } + type: 'planets', + id: '7', + attributes: { + name: 'Beta X', + description: 'Newly discovered Planet Z' + }, + links: { + self: '/planets/7' + }, + relationships: { + planetType: { + links: { + self: '/planets/7/relationships/planetType', + related: '/planets/7/planetType' + }, + data: { + type: 'planetTypes', + id: '5' + } + }, + tags: { + links: { + self: '/planets/7/relationships/tags', + related: '/planets/7/tags' + } + }, + moons: { + links: { + self: '/planets/7/relationships/moons', + related: '/planets/7/moons' + } + } + } + }, + { + type: 'planets', + id: '8', + attributes: { + name: 'Beta W', + description: 'Newly discovered Planet W' + }, + links: { + self: '/planets/8' + }, + relationships: { + planetType: { + links: { + self: '/planets/8/relationships/planetType', + related: '/planets/8/planetType' + }, + data: nil + }, + tags: { + links: { + self: '/planets/8/relationships/tags', + related: '/planets/8/tags' + } + }, + moons: { + links: { + self: '/planets/8/relationships/moons', + related: '/planets/8/moons' + } + } + } + } + ], + included: [ + { + type: 'planetTypes', + id: '5', + attributes: { + name: 'unknown' }, - moons: { - links: { - self: '/planets/8/relationships/moons', - related: '/planets/8/moons' - } + links: { + self: '/planetTypes/5' } } - } - ], - included: [ - { - type: 'planetTypes', - id: '5', - attributes: { - name: 'unknown' - }, - links: { - self: '/planet_types/5' - } - } - ] - }, planet_hash) + ] + }, + serialized + ) end def test_serializer_booleans @@ -1679,6 +1727,11 @@ def test_serializer_booleans preferences = PreferencesResource.new(Preferences.find(1), nil) + serializer = JSONAPI::ResourceSerializer.new(PreferencesResource, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(preferences) + assert_hash_equals( { data: { @@ -1700,7 +1753,7 @@ def test_serializer_booleans } } }, - JSONAPI::ResourceSerializer.new(PreferencesResource).serialize_to_hash(preferences) + serialized ) ensure JSONAPI.configuration = original_config @@ -1712,6 +1765,11 @@ def test_serializer_data_types facts = FactResource.new(Fact.find(1), nil) + serializer = JSONAPI::ResourceSerializer.new(FactResource, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(facts) + assert_hash_equals( { data: { @@ -1733,17 +1791,21 @@ def test_serializer_data_types } } }, - JSONAPI::ResourceSerializer.new(FactResource).serialize_to_hash(facts) + serialized ) ensure JSONAPI.configuration = original_config end def test_serializer_to_one - serialized = JSONAPI::ResourceSerializer.new( - Api::V5::AuthorResource, - include: ['author_detail'] - ).serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil)) + original_config = JSONAPI.configuration.dup + JSONAPI.configuration.route_format = :dasherized_route + + serializer = JSONAPI::ResourceSerializer.new(Api::V5::AuthorResource, + include: ['author_detail'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil)) assert_hash_equals( { @@ -1787,6 +1849,8 @@ def test_serializer_to_one }, serialized ) + ensure + JSONAPI.configuration = original_config end def test_serializer_resource_meta_fixed_value @@ -1799,10 +1863,12 @@ def meta(options) end end - serialized = JSONAPI::ResourceSerializer.new( - Api::V5::AuthorResource, - include: ['author_detail'] - ).serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil)) + serializer = JSONAPI::ResourceSerializer.new(Api::V5::AuthorResource, + include: ['author_detail'], + url_helpers: TestApp.routes.url_helpers + ) + + serialized = serializer.serialize_to_hash(Api::V5::AuthorResource.new(Person.find(1), nil)) assert_hash_equals( { @@ -1824,8 +1890,8 @@ def meta(options) }, authorDetail: { links: { - self: '/api/v5/authors/1/relationships/author-detail', - related: '/api/v5/authors/1/author-detail' + self: '/api/v5/authors/1/relationships/authorDetail', + related: '/api/v5/authors/1/authorDetail' }, data: {type: 'authorDetails', id: '1'} } @@ -1843,7 +1909,7 @@ def meta(options) authorStuff: 'blah blah' }, links: { - self: '/api/v5/author-details/1' + self: '/api/v5/authorDetails/1' } } ] @@ -1862,9 +1928,10 @@ def meta(options) def test_serialize_model_attr @make = Make.first - serialized = JSONAPI::ResourceSerializer.new( - MakeResource, - ).serialize_to_hash(MakeResource.new(@make, nil)) + serializer = JSONAPI::ResourceSerializer.new(MakeResource, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(MakeResource.new(@make, nil)) assert_hash_equals( { @@ -1876,9 +1943,10 @@ def test_serialize_model_attr def test_confusingly_named_attrs @wp = WebPage.first - serialized = JSONAPI::ResourceSerializer.new( - WebPageResource, - ).serialize_to_hash(WebPageResource.new(@wp, nil)) + serializer = JSONAPI::ResourceSerializer.new(WebPageResource, + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(WebPageResource.new(@wp, nil)) assert_hash_equals( { @@ -1886,7 +1954,7 @@ def test_confusingly_named_attrs "id"=>"#{@wp.id}", "type"=>"webPages", "links"=>{ - :self=>"/web_pages/#{@wp.id}" + :self=>"/webPages/#{@wp.id}" }, "attributes"=>{ "href"=>"http://example.com", @@ -1898,328 +1966,15 @@ def test_confusingly_named_attrs ) end - # def test_questionable_has_one - # # has_one - # out, err = capture_io do - # eval <<-CODE - # class ::Questionable < ActiveRecord::Base - # has_one :link - # has_one :href - # end - # class ::QuestionableResource < JSONAPI::Resource - # model_name '::Questionable' - # has_one :link - # has_one :href - # end - # cn = ::Questionable.new id: 1 - # puts JSONAPI::ResourceSerializer.new( - # ::QuestionableResource, - # ).serialize_to_hash(::QuestionableResource.new(cn, nil)) - # CODE - # end - # assert err.blank? - # assert_equal( - # { - # :data=>{ - # "id"=>"1", - # "type"=>"questionables", - # "links"=>{ - # :self=>"/questionables/1" - # }, - # "relationships"=>{ - # "link"=>{ - # :links=>{ - # :self=>"/questionables/1/relationships/link", - # :related=>"/questionables/1/link" - # } - # }, - # "href"=>{ - # :links=>{ - # :self=>"/questionables/1/relationships/href", - # :related=>"/questionables/1/href" - # } - # } - # } - # } - # }.to_s, - # out.strip - # ) - # end - # - # def test_questionable_has_many - # # has_one - # out, err = capture_io do - # eval <<-CODE - # class ::Questionable2 < ActiveRecord::Base - # self.table_name = 'questionables' - # has_many :links - # has_many :hrefs - # end - # class ::Questionable2Resource < JSONAPI::Resource - # model_name '::Questionable2' - # has_many :links - # has_many :hrefs - # end - # cn = ::Questionable2.new id: 1 - # puts JSONAPI::ResourceSerializer.new( - # ::Questionable2Resource, - # ).serialize_to_hash(::Questionable2Resource.new(cn, nil)) - # CODE - # end - # assert err.blank? - # assert_equal( - # { - # :data=>{ - # "id"=>"1", - # "type"=>"questionable2s", - # "links"=>{ - # :self=>"/questionable2s/1" - # }, - # "relationships"=>{ - # "links"=>{ - # :links=>{ - # :self=>"/questionable2s/1/relationships/links", - # :related=>"/questionable2s/1/links" - # } - # }, - # "hrefs"=>{ - # :links=>{ - # :self=>"/questionable2s/1/relationships/hrefs", - # :related=>"/questionable2s/1/hrefs" - # } - # } - # } - # } - # }.to_s, - # out.strip - # ) - # end - - # def test_simple_custom_links - # serialized_custom_link_resource = JSONAPI::ResourceSerializer.new(SimpleCustomLinkResource, base_url: 'http://example.com').serialize_to_hash(SimpleCustomLinkResource.new(Post.first, {})) - # - # custom_link_spec = { - # data: { - # type: 'simpleCustomLinks', - # id: '1', - # attributes: { - # title: "New post", - # body: "A body!!!", - # subject: "New post" - # }, - # links: { - # self: "http://example.com/simpleCustomLinks/1", - # raw: "http://example.com/simpleCustomLinks/1/raw" - # }, - # relationships: { - # writer: { - # links: { - # self: "http://example.com/simpleCustomLinks/1/relationships/writer", - # related: "http://example.com/simpleCustomLinks/1/writer" - # } - # }, - # section: { - # links: { - # self: "http://example.com/simpleCustomLinks/1/relationships/section", - # related: "http://example.com/simpleCustomLinks/1/section" - # } - # }, - # comments: { - # links: { - # self: "http://example.com/simpleCustomLinks/1/relationships/comments", - # related: "http://example.com/simpleCustomLinks/1/comments" - # } - # } - # } - # } - # } - # - # assert_hash_equals(custom_link_spec, serialized_custom_link_resource) - # end - - # def test_custom_links_with_custom_relative_paths - # serialized_custom_link_resource = JSONAPI::ResourceSerializer - # .new(CustomLinkWithRelativePathOptionResource, base_url: 'http://example.com') - # .serialize_to_hash(CustomLinkWithRelativePathOptionResource.new(Post.first, {})) - # - # custom_link_spec = { - # data: { - # type: 'customLinkWithRelativePathOptions', - # id: '1', - # attributes: { - # title: "New post", - # body: "A body!!!", - # subject: "New post" - # }, - # links: { - # self: "http://example.com/customLinkWithRelativePathOptions/1", - # raw: "http://example.com/customLinkWithRelativePathOptions/1/super/duper/path.xml" - # }, - # relationships: { - # writer: { - # links: { - # self: "http://example.com/customLinkWithRelativePathOptions/1/relationships/writer", - # related: "http://example.com/customLinkWithRelativePathOptions/1/writer" - # } - # }, - # section: { - # links: { - # self: "http://example.com/customLinkWithRelativePathOptions/1/relationships/section", - # related: "http://example.com/customLinkWithRelativePathOptions/1/section" - # } - # }, - # comments: { - # links: { - # self: "http://example.com/customLinkWithRelativePathOptions/1/relationships/comments", - # related: "http://example.com/customLinkWithRelativePathOptions/1/comments" - # } - # } - # } - # } - # } - # - # assert_hash_equals(custom_link_spec, serialized_custom_link_resource) - # end - # - # def test_custom_links_with_if_condition_equals_false - # serialized_custom_link_resource = JSONAPI::ResourceSerializer - # .new(CustomLinkWithIfCondition, base_url: 'http://example.com') - # .serialize_to_hash(CustomLinkWithIfCondition.new(Post.first, {})) - # - # custom_link_spec = { - # data: { - # type: 'customLinkWithIfConditions', - # id: '1', - # attributes: { - # title: "New post", - # body: "A body!!!", - # subject: "New post" - # }, - # links: { - # self: "http://example.com/customLinkWithIfConditions/1", - # }, - # relationships: { - # writer: { - # links: { - # self: "http://example.com/customLinkWithIfConditions/1/relationships/writer", - # related: "http://example.com/customLinkWithIfConditions/1/writer" - # } - # }, - # section: { - # links: { - # self: "http://example.com/customLinkWithIfConditions/1/relationships/section", - # related: "http://example.com/customLinkWithIfConditions/1/section" - # } - # }, - # comments: { - # links: { - # self: "http://example.com/customLinkWithIfConditions/1/relationships/comments", - # related: "http://example.com/customLinkWithIfConditions/1/comments" - # } - # } - # } - # } - # } - # - # assert_hash_equals(custom_link_spec, serialized_custom_link_resource) - # end - # - # def test_custom_links_with_if_condition_equals_true - # serialized_custom_link_resource = JSONAPI::ResourceSerializer - # .new(CustomLinkWithIfCondition, base_url: 'http://example.com') - # .serialize_to_hash(CustomLinkWithIfCondition.new(Post.find_by(title: "JR Solves your serialization woes!"), {})) - # - # custom_link_spec = { - # data: { - # type: 'customLinkWithIfConditions', - # id: '2', - # attributes: { - # title: "JR Solves your serialization woes!", - # body: "Use JR", - # subject: "JR Solves your serialization woes!" - # }, - # links: { - # self: "http://example.com/customLinkWithIfConditions/2", - # conditional_custom_link: "http://example.com/customLinkWithIfConditions/2/conditional/link.json" - # }, - # relationships: { - # writer: { - # links: { - # self: "http://example.com/customLinkWithIfConditions/2/relationships/writer", - # related: "http://example.com/customLinkWithIfConditions/2/writer" - # } - # }, - # section: { - # links: { - # self: "http://example.com/customLinkWithIfConditions/2/relationships/section", - # related: "http://example.com/customLinkWithIfConditions/2/section" - # } - # }, - # comments: { - # links: { - # self: "http://example.com/customLinkWithIfConditions/2/relationships/comments", - # related: "http://example.com/customLinkWithIfConditions/2/comments" - # } - # } - # } - # } - # } - # - # assert_hash_equals(custom_link_spec, serialized_custom_link_resource) - # end - # - # - # def test_custom_links_with_lambda - # # custom link is based on created_at timestamp of Post - # post_created_at = Post.first.created_at - # serialized_custom_link_resource = JSONAPI::ResourceSerializer - # .new(CustomLinkWithLambda, base_url: 'http://example.com') - # .serialize_to_hash(CustomLinkWithLambda.new(Post.first, {})) - # - # custom_link_spec = { - # data: { - # type: 'customLinkWithLambdas', - # id: '1', - # attributes: { - # title: "New post", - # body: "A body!!!", - # subject: "New post", - # createdAt: post_created_at.as_json - # }, - # links: { - # self: "http://example.com/customLinkWithLambdas/1", - # link_to_external_api: "http://external-api.com/posts/#{post_created_at.year}/#{post_created_at.month}/#{post_created_at.day}-New-post" - # }, - # relationships: { - # writer: { - # links: { - # self: "http://example.com/customLinkWithLambdas/1/relationships/writer", - # related: "http://example.com/customLinkWithLambdas/1/writer" - # } - # }, - # section: { - # links: { - # self: "http://example.com/customLinkWithLambdas/1/relationships/section", - # related: "http://example.com/customLinkWithLambdas/1/section" - # } - # }, - # comments: { - # links: { - # self: "http://example.com/customLinkWithLambdas/1/relationships/comments", - # related: "http://example.com/customLinkWithLambdas/1/comments" - # } - # } - # } - # } - # } - # - # assert_hash_equals(custom_link_spec, serialized_custom_link_resource) - # end - def test_includes_two_relationships_with_same_foreign_key - serialized_resource = JSONAPI::ResourceSerializer - .new(PersonWithEvenAndOddPostResource, include: ['even_posts','odd_posts']) - .serialize_to_hash(PersonWithEvenAndOddPostResource.new(Person.find(1), nil)) + original_config = JSONAPI.configuration.dup + JSONAPI.configuration.route_format = :underscored_route + + serializer = JSONAPI::ResourceSerializer.new(PersonWithEvenAndOddPostResource, + include: ['even_posts', 'odd_posts'], + url_helpers: TestApp.routes.url_helpers) + + serialized = serializer.serialize_to_hash(PersonWithEvenAndOddPostResource.new(Person.find(1), nil)) assert_hash_equals( { @@ -2377,62 +2132,60 @@ def test_includes_two_relationships_with_same_foreign_key } ] }, - serialized_resource + serialized ) + ensure + JSONAPI.configuration = original_config end def test_config_keys_stable (serializer_a, serializer_b) = 2.times.map do - JSONAPI::ResourceSerializer.new( - PostResource, - include: ['comments', 'author', 'comments.tags', 'author.posts'], - fields: { - people: [:email, :comments], - posts: [:title], - tags: [:name], - comments: [:body, :post] - } - ) + JSONAPI::ResourceSerializer.new(PostResource, + include: ['comments', 'author', 'comments.tags', 'author.posts'], + fields: { + people: [:email, :comments], + posts: [:title], + tags: [:name], + comments: [:body, :post] + }, + url_helpers: TestApp.routes.url_helpers) end assert_equal serializer_a.config_key(PostResource), serializer_b.config_key(PostResource) end def test_config_keys_vary_with_relevant_config_changes - serializer_a = JSONAPI::ResourceSerializer.new( - PostResource, - fields: { posts: [:title] } - ) - serializer_b = JSONAPI::ResourceSerializer.new( - PostResource, - fields: { posts: [:title, :body] } - ) + serializer_a = JSONAPI::ResourceSerializer.new(PostResource, + fields: {posts: [:title]}, + url_helpers: TestApp.routes.url_helpers) + + serializer_b = JSONAPI::ResourceSerializer.new(PostResource, + fields: {posts: [:title, :body]}, + url_helpers: TestApp.routes.url_helpers) assert_not_equal serializer_a.config_key(PostResource), serializer_b.config_key(PostResource) end def test_config_keys_stable_with_irrelevant_config_changes - serializer_a = JSONAPI::ResourceSerializer.new( - PostResource, - fields: { posts: [:title, :body], people: [:name, :email] } - ) - serializer_b = JSONAPI::ResourceSerializer.new( - PostResource, - fields: { posts: [:title, :body], people: [:name] } - ) + serializer_a = JSONAPI::ResourceSerializer.new(PostResource, + fields: {posts: [:title, :body], people: [:name, :email]}, + url_helpers: TestApp.routes.url_helpers) + + serializer_b = JSONAPI::ResourceSerializer.new(PostResource, + fields: {posts: [:title, :body], people: [:name]}, + url_helpers: TestApp.routes.url_helpers) assert_equal serializer_a.config_key(PostResource), serializer_b.config_key(PostResource) end def test_config_keys_stable_with_different_primary_resource - serializer_a = JSONAPI::ResourceSerializer.new( - PostResource, - fields: { posts: [:title, :body], people: [:name, :email] } - ) - serializer_b = JSONAPI::ResourceSerializer.new( - PersonResource, - fields: { posts: [:title, :body], people: [:name, :email] } - ) + serializer_a = JSONAPI::ResourceSerializer.new(PostResource, + fields: {posts: [:title, :body], people: [:name, :email]}, + url_helpers: TestApp.routes.url_helpers) + + serializer_b = JSONAPI::ResourceSerializer.new(PersonResource, + fields: {posts: [:title, :body], people: [:name, :email]}, + url_helpers: TestApp.routes.url_helpers) assert_equal serializer_a.config_key(PostResource), serializer_b.config_key(PostResource) end