diff --git a/README.md b/README.md index 9f882fd..a27481d 100644 --- a/README.md +++ b/README.md @@ -245,15 +245,38 @@ class MovieSerializer end ``` -### Meta Per Resource - -For every resource in the collection, you can include a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. +#### Links on a Relationship +You can specify [relationship links](http://jsonapi.org/format/#document-resource-object-relationships) by using the `links:` option on the serializer. Relationship links in JSON API are useful if you want to load a parent document and then load associated documents later due to size constraints (see [related resource links](http://jsonapi.org/format/#document-resource-object-related-resource-links)) ```ruby class MovieSerializer include FastJsonapi::ObjectSerializer + has_many :actors, links: { + self: :url, + related: -> (object) { + "https://movies.com/#{object.id}/actors" + } + } +end +``` + +This will create a `self` reference for the relationship, and a `related` link for loading the actors relationship later. NB: This will not automatically disable loading the data in the relationship, you'll need to do that using the `lazy_load_data` option: + +```ruby + has_many :actors, lazy_load_data: true, links: { + self: :url, + related: -> (object) { + "https://movies.com/#{object.id}/actors" + } + } +``` + +### Meta Per Resource + +For every resource in the collection, you can include a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. +```ruby meta do |movie| { years_since_release: Date.current.year - movie.year diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index c72b22e..7f85965 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -257,7 +257,9 @@ module FastJsonapi cached: options[:cached], polymorphic: fetch_polymorphic_option(options), conditional_proc: options[:if], - transform_method: @transform_method + transform_method: @transform_method, + links: options[:links], + lazy_load_data: options[:lazy_load_data] ) end diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index e06b07f..7a038de 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -1,6 +1,6 @@ module FastJsonapi class Relationship - attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method + attr_reader :key, :name, :id_method_name, :record_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data def initialize( key:, @@ -14,7 +14,9 @@ module FastJsonapi cached: false, polymorphic:, conditional_proc:, - transform_method: + transform_method:, + links:, + lazy_load_data: false ) @key = key @name = name @@ -28,14 +30,19 @@ module FastJsonapi @polymorphic = polymorphic @conditional_proc = conditional_proc @transform_method = transform_method + @links = links || {} + @lazy_load_data = lazy_load_data end def serialize(record, serialization_params, output_hash) if include_relationship?(record, serialization_params) empty_case = relationship_type == :has_many ? [] : nil - output_hash[key] = { - data: ids_hash_from_record_and_relationship(record, serialization_params) || empty_case - } + + output_hash[key] = {} + unless lazy_load_data + output_hash[key][:data] = ids_hash_from_record_and_relationship(record, serialization_params) || empty_case + end + add_links_hash(record, serialization_params, output_hash) if links.present? end end @@ -96,6 +103,12 @@ module FastJsonapi record.public_send(id_method_name) end + def add_links_hash(record, params, output_hash) + output_hash[key][:links] = links.each_with_object({}) do |(key, method), hash| + Link.new(key: key, method: method).serialize(record, params, hash)\ + end + end + def run_key_transform(input) if self.transform_method.present? input.to_s.send(*self.transform_method).to_sym diff --git a/spec/lib/object_serializer_relationship_links_spec.rb b/spec/lib/object_serializer_relationship_links_spec.rb new file mode 100644 index 0000000..8c2f272 --- /dev/null +++ b/spec/lib/object_serializer_relationship_links_spec.rb @@ -0,0 +1,71 @@ +require 'spec_helper' + +describe FastJsonapi::ObjectSerializer do + include_context 'movie class' + + context "params option" do + let(:hash) { serializer.serializable_hash } + + context "generating links for a serializer relationship" do + let(:params) { { } } + let(:options_with_params) { { params: params } } + let(:relationship_url) { "http://movies.com/#{movie.id}/relationships/actors" } + let(:related_url) { "http://movies.com/movies/#{movie.name.parameterize}/actors/" } + + before(:context) do + class MovieSerializer + has_many :actors, lazy_load_data: false, links: { + self: :actors_relationship_url, + related: -> (object, params = {}) { + "#{params.has_key?(:secure) ? "https" : "http"}://movies.com/movies/#{object.name.parameterize}/actors/" + } + } + end + end + + context "with a single record" do + let(:serializer) { MovieSerializer.new(movie, options_with_params) } + let(:links) { hash[:data][:relationships][:actors][:links] } + + it "handles relationship links that call a method" do + expect(links).to be_present + expect(links[:self]).to eq(relationship_url) + end + + it "handles relationship links that call a proc" do + expect(links).to be_present + expect(links[:related]).to eq(related_url) + end + + context "with serializer params" do + let(:params) { { secure: true } } + let(:secure_related_url) { related_url.gsub("http", "https") } + + it "passes the params to the link serializer correctly" do + expect(links).to be_present + expect(links[:related]).to eq(secure_related_url) + end + end + end + + end + + context "lazy loading relationship data" do + before(:context) do + class LazyLoadingMovieSerializer < MovieSerializer + has_many :actors, lazy_load_data: true, links: { + related: :actors_relationship_url + } + end + end + + let(:serializer) { LazyLoadingMovieSerializer.new(movie) } + let(:actor_hash) { hash[:data][:relationships][:actors] } + + it "does not include the :data key" do + expect(actor_hash).to be_present + expect(actor_hash).not_to have_key(:data) + end + end + end +end diff --git a/spec/shared/contexts/movie_context.rb b/spec/shared/contexts/movie_context.rb index 4d71119..df0c395 100644 --- a/spec/shared/contexts/movie_context.rb +++ b/spec/shared/contexts/movie_context.rb @@ -61,6 +61,10 @@ RSpec.shared_context 'movie class' do def url "http://movies.com/#{id}" end + + def actors_relationship_url + "#{url}/relationships/actors" + end end class Actor