diff --git a/README.md b/README.md index bac08b48..7b23755e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Fast JSON API serialized 250 records in 3.01 ms * [Serializer Definition](#serializer-definition) * [Object Serialization](#object-serialization) * [Compound Document](#compound-document) + * [Key Transforms](#key-transforms) * [Collection Serialization](#collection-serialization) * [Caching](#caching) * [Contributing](#contributing) @@ -134,6 +135,26 @@ 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 + +```ruby +class MovieSerializer + include FastJsonapi::ObjectSerializer + # Available options :camel, :camel_lower, :dash, :underscore(default) + set_key_transform :camel +end +``` +Here are examples of how these options transform the keys + +```ruby +set_key_transform :camel # "some_key" => "SomeKey" +set_key_transform :camel_lower # "some_key" => "someKey" +set_key_transform :dash # "some_key" => "some-key" +set_key_transform :underscore # "some_key" => "some_key" +``` + ### Compound Document Support for top-level included member through ` options[:include] `. diff --git a/lib/fast_jsonapi/object_serializer.rb b/lib/fast_jsonapi/object_serializer.rb index 4c77dae5..da858f5d 100644 --- a/lib/fast_jsonapi/object_serializer.rb +++ b/lib/fast_jsonapi/object_serializer.rb @@ -104,19 +104,6 @@ def is_collection?(resource) end class_methods do - def use_hyphen - @hyphenated = true - end - - def set_type(type) - return unless type - - type = type.to_s.underscore - type = type.dasherize if @hyphenated - - self.record_type = type.to_sym - end - def reflected_record_type return @reflected_record_type if defined?(@reflected_record_type) @@ -127,6 +114,28 @@ def reflected_record_type end end + def set_key_transform(transform_name) + mapping = { + camel: :camelize, + camel_lower: [:camelize, :lower], + dash: :dasherize, + underscore: :underscore + } + @transform_method = mapping[transform_name.to_sym] + end + + def run_key_transform(input) + if @transform_method.present? + input.to_s.send(*@transform_method).to_sym + else + input.to_sym + end + end + + def set_type(type_name) + self.record_type = run_key_transform(type_name) + end + def cache_options(cache_options) self.cached = cache_options[:enabled] || false self.cache_length = cache_options[:cache_length] || 5.minutes @@ -137,10 +146,7 @@ def attributes(*attributes_list) self.attributes_to_serialize = {} if self.attributes_to_serialize.nil? attributes_list.each do |attr_name| method_name = attr_name - key = method_name - if @hyphenated - key = attr_name.to_s.dasherize.to_sym - end + key = run_key_transform(method_name) attributes_to_serialize[key] = method_name end end @@ -159,15 +165,11 @@ def add_relationship(name, relationship) end def has_many(relationship_name, options = {}) - singular_name = relationship_name.to_s.singularize - record_type = options[:record_type] || singular_name.to_sym name = relationship_name.to_sym - key = options[:key] || name - if @hyphenated - key = options[:key] || relationship_name.to_s.dasherize.to_sym - record_type = options[:record_type] || singular_name.to_s.dasherize.to_sym - end - serializer_key = options[:serializer] || record_type + singular_name = relationship_name.to_s.singularize + serializer_key = options[:serializer] || singular_name.to_sym + key = options[:key] || run_key_transform(relationship_name) + record_type = options[:record_type] || run_key_transform(singular_name) relationship = { key: key, name: name, @@ -183,13 +185,9 @@ def has_many(relationship_name, options = {}) def belongs_to(relationship_name, options = {}) name = relationship_name.to_sym - key = options[:key] || name - record_type = options[:record_type] || name - serializer_key = options[:serializer] || record_type - if @hyphenated - key = options[:key] || relationship_name.to_s.dasherize.to_sym - record_type = options[:record_type] || relationship_name.to_s.dasherize.to_sym - end + serializer_key = options[:serializer] || relationship_name.to_sym + key = options[:key] || run_key_transform(relationship_name) + record_type = options[:record_type] || run_key_transform(relationship_name) add_relationship(name, { key: key, name: name, @@ -204,13 +202,9 @@ def belongs_to(relationship_name, options = {}) def has_one(relationship_name, options = {}) name = relationship_name.to_sym - key = options[:key] || name - record_type = options[:record_type] || name - serializer_key = options[:serializer] || record_type - if @hyphenated - key = options[:key] || relationship_name.to_s.dasherize.to_sym - record_type = options[:record_type] || relationship_name.to_s.dasherize.to_sym - end + serializer_key = options[:serializer] || name + key = options[:key] || run_key_transform(relationship_name) + record_type = options[:record_type] || run_key_transform(relationship_name) add_relationship(name, { key: key, name: name, diff --git a/spec/lib/object_serializer_hyphen_spec.rb b/spec/lib/object_serializer_hyphen_spec.rb deleted file mode 100644 index fc529792..00000000 --- a/spec/lib/object_serializer_hyphen_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'spec_helper' - -describe FastJsonapi::ObjectSerializer do - include_context 'movie class' - include_context 'ams movie class' - - context 'when using hyphens for word separation in the JSON API members' do - it 'returns correct hash when serializable_hash is called' do - serializable_hash = HyphenMovieSerializer.new([movie, movie]).serializable_hash - expect(serializable_hash[:data].length).to eq 2 - expect(serializable_hash[:data][0][:relationships].length).to eq 3 - expect(serializable_hash[:data][0][:relationships]).to have_key('movie-type'.to_sym) - expect(serializable_hash[:data][0][:attributes].length).to eq 2 - expect(serializable_hash[:data][0][:attributes]).to have_key("release-year".to_sym) - - serializable_hash = HyphenMovieSerializer.new(movie_struct).serializable_hash - expect(serializable_hash[:data][:relationships].length).to eq 3 - expect(serializable_hash[:data][:relationships]).to have_key('movie-type'.to_sym) - expect(serializable_hash[:data][:attributes].length).to eq 2 - expect(serializable_hash[:data][:attributes]).to have_key('release-year'.to_sym) - expect(serializable_hash[:data][:id]).to eq movie_struct.id.to_s - end - - it 'returns same thing as ams' do - ams_movie = build_ams_movies(1).first - movie = build_movies(1).first - our_json = HyphenMovieSerializer.new([movie]).serialized_json - ams_json = ActiveModelSerializers::SerializableResource.new([ams_movie], key_transform: :dash).to_json - expect(our_json.length).to eq (ams_json.length) - end - - it 'returns type hypenated when trying to serializing a class with multiple words' do - movie_type = MovieType.new - movie_type.id = 3 - movie_type.name = "x" - serializable_hash = HyphenMovieTypeSerializer.new(movie_type).serializable_hash - expect(serializable_hash[:data][:type].to_sym).to eq 'movie-type'.to_sym - end - end -end diff --git a/spec/lib/object_serializer_key_transform_spec.rb b/spec/lib/object_serializer_key_transform_spec.rb new file mode 100644 index 00000000..77338b14 --- /dev/null +++ b/spec/lib/object_serializer_key_transform_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe FastJsonapi::ObjectSerializer do + include_context 'movie class' + include_context 'ams movie class' + + before(:context) do + [:dash, :camel, :camel_lower, :underscore].each do |transform_type| + movie_serializer_name = "#{transform_type}_movie_serializer".classify + movie_type_serializer_name = "#{transform_type}_movie_type_serializer".classify + # https://stackoverflow.com/questions/4113479/dynamic-class-definition-with-a-class-name + movie_serializer_class = Object.const_set( + movie_serializer_name, + Class.new { + } + ) + # https://rubymonk.com/learning/books/5-metaprogramming-ruby-ascent/chapters/24-eval/lessons/67-instance-eval + movie_serializer_class.instance_eval do + include FastJsonapi::ObjectSerializer + set_type :movie + set_key_transform transform_type + attributes :name, :release_year + has_many :actors + belongs_to :owner, record_type: :user + belongs_to :movie_type + end + movie_type_serializer_class = Object.const_set( + movie_type_serializer_name, + Class.new { + } + ) + movie_type_serializer_class.instance_eval do + include FastJsonapi::ObjectSerializer + set_key_transform transform_type + set_type :movie_type + attributes :name + end + end + end + + context 'when using dashes for word separation in the JSON API members' do + it 'returns correct hash when serializable_hash is called' do + serializable_hash = DashMovieSerializer.new([movie, movie]).serializable_hash + expect(serializable_hash[:data].length).to eq 2 + expect(serializable_hash[:data][0][:relationships].length).to eq 3 + expect(serializable_hash[:data][0][:relationships]).to have_key('movie-type'.to_sym) + expect(serializable_hash[:data][0][:attributes].length).to eq 2 + expect(serializable_hash[:data][0][:attributes]).to have_key("release-year".to_sym) + + serializable_hash = DashMovieSerializer.new(movie_struct).serializable_hash + expect(serializable_hash[:data][:relationships].length).to eq 3 + expect(serializable_hash[:data][:relationships]).to have_key('movie-type'.to_sym) + expect(serializable_hash[:data][:attributes].length).to eq 2 + expect(serializable_hash[:data][:attributes]).to have_key('release-year'.to_sym) + expect(serializable_hash[:data][:id]).to eq movie_struct.id.to_s + end + + it 'returns type hypenated when trying to serializing a class with multiple words' do + movie_type = MovieType.new + movie_type.id = 3 + movie_type.name = "x" + serializable_hash = DashMovieTypeSerializer.new(movie_type).serializable_hash + expect(serializable_hash[:data][:type].to_sym).to eq 'movie-type'.to_sym + end + end + + context 'when using other key transforms' do + [:camel, :camel_lower, :underscore, :dash].each do |transform_type| + it "returns same thing as ams when using #{transform_type}" do + ams_movie = build_ams_movies(1).first + movie = build_movies(1).first + movie_serializer_class = "#{transform_type}_movie_serializer".classify.constantize + our_json = movie_serializer_class.new([movie]).serialized_json + ams_json = ActiveModelSerializers::SerializableResource.new([ams_movie], key_transform: transform_type).to_json + expect(our_json.length).to eq (ams_json.length) + end + end + end + +end diff --git a/spec/shared/contexts/movie_context.rb b/spec/shared/contexts/movie_context.rb index 1564f2b0..6ffe9a81 100644 --- a/spec/shared/contexts/movie_context.rb +++ b/spec/shared/contexts/movie_context.rb @@ -95,28 +95,6 @@ class MovieSerializer end end - - # Hyphenated keys for the serializer - before(:context) do - class HyphenMovieSerializer - include FastJsonapi::ObjectSerializer - use_hyphen - set_type :movie - attributes :name, :release_year - has_many :actors - belongs_to :owner, record_type: :user - belongs_to :movie_type - end - - class HyphenMovieTypeSerializer - include FastJsonapi::ObjectSerializer - use_hyphen - set_type :movie_type - attributes :name - end - end - - # Movie and Actor struct before(:context) do MovieStruct = Struct.new(