diff --git a/README.md b/README.md index 53c8bfa7..97af42fe 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,14 @@ Fast JSON API serialized 250 records in 3.01 ms * [Object Serialization](#object-serialization) * [Compound Document](#compound-document) * [Key Transforms](#key-transforms) + * [Pluralize Type](#pluralize-type) * [Collection Serialization](#collection-serialization) * [Caching](#caching) * [Params](#params) * [Conditional Attributes](#conditional-attributes) * [Conditional Relationships](#conditional-relationships) * [Sparse Fieldsets](#sparse-fieldsets) + * [Using helper methods](#using-helper-methods) * [Contributing](#contributing) @@ -151,7 +153,7 @@ json_string = MovieSerializer.new(movie).serialized_json ``` ### Key Transforms -By default fast_jsonapi underscores the key names. It supports the same key transforms that are supported by AMS. Here is the syntax of specifying a key transform +By default fast_jsonapi underscores the key names. It supports the same key transforms that are supported by AMS. Here is the syntax of specifying a key transform: ```ruby class MovieSerializer @@ -160,7 +162,7 @@ class MovieSerializer set_key_transform :camel end ``` -Here are examples of how these options transform the keys +Here are examples of how these options transform the keys. ```ruby set_key_transform :camel # "some_key" => "SomeKey" @@ -169,6 +171,29 @@ set_key_transform :dash # "some_key" => "some-key" set_key_transform :underscore # "some_key" => "some_key" ``` +### Pluralize Type + +By default fast_jsonapi does not pluralize type names. You can turn pluralization on using this syntax: + +```ruby +class AwardSerializer + include FastJsonapi::ObjectSerializer + belongs_to :actor + pluralize_type true # "award" => "awards" +end +``` + +Relationship types are not automatically pluralized, even when their base types have `pluralize_type` set. Pluralization can be enabled in the relationship definition. + +```ruby +class ActorSerializer + include FastJsonapi::ObjectSerializer + has_many :awards, pluralize_type: true # "award" => "awards" +end +``` + +The most common use case for this feature is to easily migrate from serialization engines that pluralize by default, such as AMS. + ### Attributes Attributes are defined in FastJsonapi using the `attributes` method. This method is also aliased as `attribute`, which is useful when defining a single attribute. @@ -276,7 +301,12 @@ This will create a `self` reference for the relationship, and a `related` link f ### 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 +class MovieSerializer + include FastJsonapi::ObjectSerializer + meta do |movie| { years_since_release: Date.current.year - movie.year @@ -444,6 +474,68 @@ serializer = MovieSerializer.new(movie, { fields: { movie: [:name] } }) serializer.serializable_hash ``` +### Using helper methods + +You can mix-in code from another ruby module into your serializer class to reuse functions across your app. + +Since a serializer is evaluated in a the context of a `class` rather than an `instance` of a class, you need to make sure that your methods act as `class` methods when mixed in. + + +##### Using ActiveSupport::Concern + +``` ruby + +module AvatarHelper + extend ActiveSupport::Concern + + class_methods do + def avatar_url(user) + user.image.url + end + end +end + +class UserSerializer + include FastJsonapi::ObjectSerializer + + include AvatarHelper # mixes in your helper method as class method + + set_type :user + + attributes :name, :email + + attribute :avatar do |user| + avatar_url(user) + end +end + +``` + +##### Using Plain Old Ruby + +``` ruby +module AvatarHelper + def avatar_url(user) + user.image.url + end +end + +class UserSerializer + include FastJsonapi::ObjectSerializer + + extend AvatarHelper # mixes in your helper method as class method + + set_type :user + + attributes :name, :email + + attribute :avatar do |user| + avatar_url(user) + end +end + +``` + ### Customizable Options Option | Purpose | Example diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index b8a24183..fd9184e8 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -118,6 +118,7 @@ def inherited(subclass) subclass.cachable_relationships_to_serialize = cachable_relationships_to_serialize.dup if cachable_relationships_to_serialize.present? subclass.uncachable_relationships_to_serialize = uncachable_relationships_to_serialize.dup if uncachable_relationships_to_serialize.present? subclass.transform_method = transform_method + subclass.pluralized_type = pluralized_type subclass.cache_length = cache_length subclass.race_condition_ttl = race_condition_ttl subclass.data_links = data_links.dup if data_links.present? @@ -153,6 +154,17 @@ def set_key_transform(transform_name) end end + def pluralize_type(pluralize) + self.pluralized_type = pluralize + + # ensure that the record type is correctly transformed + if record_type + set_type(record_type) + elsif reflected_record_type + set_type(reflected_record_type) + end + end + def run_key_transform(input) if self.transform_method.present? input.to_s.send(*@transform_method).to_sym @@ -161,13 +173,21 @@ def run_key_transform(input) end end + def run_key_pluralization(input) + if self.pluralized_type + input.to_s.pluralize.to_sym + else + input.to_sym + end + end + def use_hyphen warn('DEPRECATION WARNING: use_hyphen is deprecated and will be removed from fast_jsonapi 2.0 use (set_key_transform :dash) instead') set_key_transform :dash end def set_type(type_name) - self.record_type = run_key_transform(type_name) + self.record_type = run_key_transform(run_key_pluralization(type_name)) end def set_id(id_name = nil, &block) @@ -250,6 +270,7 @@ def create_relationship(base_key, relationship_type, options, block) block ), record_type: options[:record_type] || run_key_transform(base_key_sym), + pluralize_type: options[:pluralize_type], object_method_name: options[:object_method_name] || name, object_block: block, serializer: compute_serializer_name(options[:serializer] || base_key_sym), diff --git a/lib/fast_jsonapi/relationship.rb b/lib/fast_jsonapi/relationship.rb index 7a038de7..89e4d1af 100644 --- a/lib/fast_jsonapi/relationship.rb +++ b/lib/fast_jsonapi/relationship.rb @@ -1,12 +1,13 @@ 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, :links, :lazy_load_data + attr_reader :key, :name, :id_method_name, :record_type, :pluralized_type, :object_method_name, :object_block, :serializer, :relationship_type, :cached, :polymorphic, :conditional_proc, :transform_method, :links, :lazy_load_data def initialize( key:, name:, id_method_name:, record_type:, + pluralize_type:, object_method_name:, object_block:, serializer:, @@ -21,7 +22,8 @@ def initialize( @key = key @name = name @id_method_name = id_method_name - @record_type = record_type + @pluralized_type = pluralize_type + @record_type = run_key_pluralization(record_type) @object_method_name = object_method_name @object_block = object_block @serializer = serializer @@ -77,7 +79,7 @@ def ids_hash_from_record_and_relationship(record, params = {}) def id_hash_from_record(record, record_types) # memoize the record type within the record_types dictionary, then assigning to record_type: - associated_record_type = record_types[record.class] ||= run_key_transform(record.class.name.demodulize.underscore) + associated_record_type = record_types[record.class] ||= run_key_transform(run_key_pluralization(record.class.name.demodulize.underscore)) id_hash(record.id, associated_record_type) end @@ -116,5 +118,13 @@ def run_key_transform(input) input.to_sym end end + + def run_key_pluralization(input) + if self.pluralized_type + input.to_s.pluralize.to_sym + else + input.to_sym + end + end end end diff --git a/lib/fast_jsonapi/serialization_core.rb b/lib/fast_jsonapi/serialization_core.rb index 200af9b3..7d2bcf29 100644 --- a/lib/fast_jsonapi/serialization_core.rb +++ b/lib/fast_jsonapi/serialization_core.rb @@ -16,6 +16,7 @@ class << self :cachable_relationships_to_serialize, :uncachable_relationships_to_serialize, :transform_method, + :pluralized_type, :record_type, :record_id, :cache_length, diff --git a/lib/fast_jsonapi/version.rb b/lib/fast_jsonapi/version.rb index c9c4c7ab..12442c14 100644 --- a/lib/fast_jsonapi/version.rb +++ b/lib/fast_jsonapi/version.rb @@ -1,3 +1,3 @@ module FastJsonapi - VERSION = "1.4" + VERSION = "1.5" end diff --git a/spec/lib/object_serializer_class_methods_spec.rb b/spec/lib/object_serializer_class_methods_spec.rb index 205f6452..97c92418 100644 --- a/spec/lib/object_serializer_class_methods_spec.rb +++ b/spec/lib/object_serializer_class_methods_spec.rb @@ -486,4 +486,63 @@ def year_since_release_calculator(release_year) end end end + + describe '#pluralize_type' do + subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash } + + before do + MovieSerializer.pluralize_type pluralize + end + + after do + MovieSerializer.pluralize_type nil + MovieSerializer.set_type :movie + end + + context 'when pluralize is true' do + let(:pluralize) { true } + + it 'returns correct hash which type equals pluralized value' do + expect(serializable_hash[:data][:type]).to eq :movies + end + end + + context 'when pluralize is false' do + let(:pluralize) { false } + + it 'returns correct hash which type equals non-pluralized value' do + expect(serializable_hash[:data][:type]).to eq :movie + end + end + end + + describe '#pluralize_type after #set_type' do + subject(:serializable_hash) { MovieSerializer.new(movie).serializable_hash } + + before do + MovieSerializer.set_type type_name + MovieSerializer.pluralize_type true + end + + after do + MovieSerializer.pluralize_type nil + MovieSerializer.set_type :movie + end + + context 'when sets singular type name' do + let(:type_name) { :film } + + it 'returns correct hash which type equals transformed set_type value' do + expect(serializable_hash[:data][:type]).to eq :films + end + end + + context 'when sets plural type name' do + let(:type_name) { :films } + + it 'returns correct hash which type equals transformed set_type value' do + expect(serializable_hash[:data][:type]).to eq :films + end + end + end end diff --git a/spec/lib/object_serializer_inheritance_spec.rb b/spec/lib/object_serializer_inheritance_spec.rb index beb74872..a26abdad 100644 --- a/spec/lib/object_serializer_inheritance_spec.rb +++ b/spec/lib/object_serializer_inheritance_spec.rb @@ -95,13 +95,23 @@ class EmployeeSerializer < UserSerializer has_one :account end + class LegacyUserSerializer < UserSerializer + pluralize_type true + end + + class LegacyEmployeeSerializer < LegacyUserSerializer + attributes :location + attributes :compensation + + has_one :account + end + it 'sets the correct record type' do expect(EmployeeSerializer.reflected_record_type).to eq :employee expect(EmployeeSerializer.record_type).to eq :employee end context 'when testing inheritance of attributes' do - it 'includes parent attributes' do subclass_attributes = EmployeeSerializer.attributes_to_serialize superclass_attributes = UserSerializer.attributes_to_serialize @@ -157,12 +167,15 @@ class EmployeeSerializer < UserSerializer end end - context 'when test inheritence of other attributes' do - - it 'inherits the tranform method' do + context 'when testing inheritence of other attributes' do + it 'inherits the transform method' do EmployeeSerializer expect(UserSerializer.transform_method).to eq EmployeeSerializer.transform_method end + it 'inherits pluralized_type' do + LegacyEmployeeSerializer + expect(LegacyUserSerializer.pluralized_type).to eq LegacyEmployeeSerializer.pluralized_type + end end end diff --git a/spec/lib/object_serializer_pluralization_spec.rb b/spec/lib/object_serializer_pluralization_spec.rb new file mode 100644 index 00000000..f2c19944 --- /dev/null +++ b/spec/lib/object_serializer_pluralization_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe FastJsonapi::ObjectSerializer do + class Author + attr_accessor :id, :name + end + + class Book + attr_accessor :id, :name, :authors, :references + + def author_ids + authors.map(&:id) + end + end + + class Song + attr_accessor :id, :name, :artist + end + + class BookSerializer + include FastJsonapi::ObjectSerializer + attributes :name + set_key_transform :dash + has_many :authors, pluralize_type: true + has_many :references, polymorphic: true, pluralize_type: true + pluralize_type true + end + + let(:book) do + book = Book.new + book.id = 1 + book.name = 'Monstrous Regiment' + book + end + + let(:author) do + author = Author.new + author.id = 1 + author.name = 'Terry Pratchett' + author + end + + let(:song) do + song = Song.new + song.id = 1 + song.name = 'Sweet Polly Oliver' + song + end + + context 'when serializing id and type of polymorphic relationships' do + it 'should return correct type when transform_method is specified' do + book.authors = [author] + book.references = [song] + book_hash = BookSerializer.new(book).to_hash + record_type = book_hash[:data][:relationships][:authors][:data][0][:type] + expect(record_type).to eq 'authors'.to_sym + record_type = book_hash[:data][:relationships][:references][:data][0][:type] + expect(record_type).to eq 'songs'.to_sym + end + end +end