diff --git a/Gemfile.lock b/Gemfile.lock index c0899cd..5e082ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -55,6 +55,8 @@ GEM jsonapi-renderer (0.2.0) jsonapi-serializable (0.3.0) jsonapi-renderer (~> 0.2.0) + jsonapi-serializers (1.0.0) + activesupport loofah (2.1.1) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -104,6 +106,7 @@ DEPENDENCIES byebug fast_jsonapi! jsonapi-rb (~> 0.5.0) + jsonapi-serializers (~> 1.0.0) oj (~> 3.3) rspec (~> 3.5.0) rspec-benchmark (~> 0.3.0) diff --git a/fast_jsonapi.gemspec b/fast_jsonapi.gemspec index ccae628..8ea08bc 100644 --- a/fast_jsonapi.gemspec +++ b/fast_jsonapi.gemspec @@ -31,4 +31,5 @@ Gem::Specification.new do |gem| gem.add_development_dependency(%q, ["~> 0.10.7"]) gem.add_development_dependency(%q, ["~> 1.3"]) gem.add_development_dependency(%q, ["~> 0.5.0"]) + gem.add_development_dependency(%q, ["~> 1.0.0"]) end diff --git a/spec/lib/object_serializer_performance_spec.rb b/spec/lib/object_serializer_performance_spec.rb index 03e1ddd..74ed293 100644 --- a/spec/lib/object_serializer_performance_spec.rb +++ b/spec/lib/object_serializer_performance_spec.rb @@ -4,10 +4,12 @@ describe FastJsonapi::ObjectSerializer, performance: true do include_context 'movie class' include_context 'ams movie class' include_context 'jsonapi movie class' + include_context 'jsonapi-serializers movie class' include_context 'group class' include_context 'ams group class' include_context 'jsonapi group class' + include_context 'jsonapi-serializers group class' before(:all) { GC.disable } after(:all) { GC.enable } @@ -42,34 +44,38 @@ describe FastJsonapi::ObjectSerializer, performance: true do end end - def print_stats(message, count, ams_time, jsonapi_time, our_time) + def print_stats(message, count, ams_time, jsonapi_time, jsonapis_time, our_time) format = '%-15s %-10s %s' puts '' puts message puts format(format, 'Serializer', 'Records', 'Time') puts format(format, 'AMS serializer', count, ams_time.round(2).to_s + ' ms') puts format(format, 'jsonapi-rb serializer', count, jsonapi_time.round(2).to_s + ' ms') + puts format(format, 'jsonapi-serializers', count, jsonapis_time.round(2).to_s + ' ms') puts format(format, 'Fast serializer', count, our_time.round(2).to_s + ' ms') end - def run_hash_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer) - our_time = Benchmark.measure { our_hash = our_serializer.serializable_hash }.real * 1000 - ams_time = Benchmark.measure { ams_hash = ams_serializer.as_json }.real * 1000 - jsonapi_time = Benchmark.measure { ams_hash = jsonapi_serializer.to_hash }.real * 1000 + def run_hash_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) + our_time = Benchmark.measure { our_serializer.serializable_hash }.real * 1000 + ams_time = Benchmark.measure { ams_serializer.as_json }.real * 1000 + jsonapi_time = Benchmark.measure { jsonapi_serializer.to_hash }.real * 1000 + jsonapis_time = Benchmark.measure { jsonapis_serializer.to_hash }.real * 1000 - print_stats(message, movie_count, ams_time, jsonapi_time, our_time) + print_stats(message, movie_count, ams_time, jsonapi_time, jsonapis_time, our_time) end - def run_json_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer) + def run_json_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) our_json = nil ams_json = nil jsonapi_json = nil + jsonapis_json = nil our_time = Benchmark.measure { our_json = our_serializer.serialized_json }.real * 1000 ams_time = Benchmark.measure { ams_json = ams_serializer.to_json }.real * 1000 jsonapi_time = Benchmark.measure { jsonapi_json = jsonapi_serializer.to_json }.real * 1000 + jsonapis_time = Benchmark.measure { jsonapis_json = jsonapis_serializer.to_json }.real * 1000 - print_stats(message, movie_count, ams_time, jsonapi_time, our_time) - return our_json, ams_json, jsonapi_json + print_stats(message, movie_count, ams_time, jsonapi_time, jsonapis_time, our_time) + return our_json, ams_json, jsonapi_json, jsonapis_json end context 'when comparing with AMS 0.10.x' do @@ -79,15 +85,17 @@ describe FastJsonapi::ObjectSerializer, performance: true do ams_movies = build_ams_movies(movie_count) movies = build_movies(movie_count) jsonapi_movies = build_jsonapi_movies(movie_count) + jsonapis_movies = build_js_movies(movie_count) our_serializer = MovieSerializer.new(movies) ams_serializer = ActiveModelSerializers::SerializableResource.new(ams_movies) jsonapi_serializer = JSONAPISerializer.new(jsonapi_movies) + jsonapis_serializer = JSONAPISSerializer.new(jsonapis_movies) message = "Serialize to JSON string #{movie_count} records" - our_json, ams_json, jsonapi_json = run_json_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer) + our_json, ams_json, _, _ = run_json_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) message = "Serialize to Ruby Hash #{movie_count} records" - run_hash_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer) + run_hash_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) expect(our_json.length).to eq ams_json.length expect { our_serializer.serialized_json }.to perform_faster_than { ams_serializer.to_json }.at_least(speed_factor).times @@ -103,18 +111,20 @@ describe FastJsonapi::ObjectSerializer, performance: true do ams_movies = build_ams_movies(movie_count) movies = build_movies(movie_count) jsonapi_movies = build_jsonapi_movies(movie_count) + jsonapis_movies = build_js_movies(movie_count) options = {} options[:meta] = { total: movie_count } options[:include] = [:actors, :movie_type] our_serializer = MovieSerializer.new(movies, options) ams_serializer = ActiveModelSerializers::SerializableResource.new(ams_movies, include: options[:include], meta: options[:meta]) jsonapi_serializer = JSONAPISerializer.new(jsonapi_movies, include: options[:include], meta: options[:meta]) + jsonapis_serializer = JSONAPISSerializer.new(jsonapis_movies, include: options[:include].map{|i| i.to_s.dasherize}, meta: options[:meta]) message = "Serialize to JSON string #{movie_count} with includes and meta" - our_json, ams_json = run_json_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer) + our_json, ams_json = run_json_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) message = "Serialize to Ruby Hash #{movie_count} with includes and meta" - run_hash_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer) + run_hash_benchmark(message, movie_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) expect(our_json.length).to eq ams_json.length expect { our_serializer.serialized_json }.to perform_faster_than { ams_serializer.to_json }.at_least(speed_factor).times @@ -129,16 +139,19 @@ describe FastJsonapi::ObjectSerializer, performance: true do it "should serialize #{group_count} records at least #{speed_factor} times faster than AMS" do ams_groups = build_ams_groups(group_count) groups = build_groups(group_count) + jsonapi_groups = build_jsonapi_groups(group_count) + jsonapis_groups = build_jsonapis_groups(group_count) options = {} our_serializer = GroupSerializer.new(groups, options) ams_serializer = ActiveModelSerializers::SerializableResource.new(ams_groups) jsonapi_serializer = JSONAPISerializerB.new(jsonapi_groups) + jsonapis_serializer = JSONAPISSerializerB.new(jsonapis_groups) message = "Serialize to JSON string #{group_count} with polymorphic has_many" - our_json, ams_json, jsonapi_json = run_json_benchmark(message, group_count, our_serializer, ams_serializer, jsonapi_serializer) + our_json, ams_json, _, _ = run_json_benchmark(message, group_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) message = "Serialize to Ruby Hash #{group_count} with polymorphic has_many" - run_hash_benchmark(message, group_count, our_serializer, ams_serializer, jsonapi_serializer) + run_hash_benchmark(message, group_count, our_serializer, ams_serializer, jsonapi_serializer, jsonapis_serializer) expect(our_json.length).to eq ams_json.length expect { our_serializer.serialized_json }.to perform_faster_than { ams_serializer.to_json }.at_least(speed_factor).times diff --git a/spec/shared/contexts/js_context.rb b/spec/shared/contexts/js_context.rb new file mode 100644 index 0000000..b6d5def --- /dev/null +++ b/spec/shared/contexts/js_context.rb @@ -0,0 +1,123 @@ +RSpec.shared_context 'jsonapi-serializers movie class' do + before(:context) do + # models + class JSMovie + attr_accessor :id, :name, :release_year, :actors, :owner, :movie_type + end + + class JSActor + attr_accessor :id, :name, :email + end + + class JSUser + attr_accessor :id, :name + end + + class JSMovieType + attr_accessor :id, :name + end + + # serializers + class JSActorSerializer + include JSONAPI::Serializer + attributes :name, :email + + def type + 'actor' + end + end + class JSUserSerializer + include JSONAPI::Serializer + attributes :name + + def type + 'user' + end + end + class JSMovieTypeSerializer + include JSONAPI::Serializer + attributes :name + + def type + 'movie_type' + end + end + class JSMovieSerializer + include JSONAPI::Serializer + attributes :name, :release_year + has_many :actors + has_one :owner + has_one :movie_type + + def type + 'movie' + end + end + + class JSONAPISSerializer + def initialize(data, options = {}) + @options = options.merge(is_collection: true) + @data = data + end + + def to_json + JSONAPI::Serializer.serialize(@data, @options).to_json + end + + def to_hash + JSONAPI::Serializer.serialize(@data, @options) + end + end + end + + after(:context) do + classes_to_remove = %i[ + JSMovie + JSActor + JSUser + JSMovieType + JSONAPISSerializer + JSActorSerializer + JSUserSerializer + JSMovieTypeSerializer + JSMovieSerializer] + classes_to_remove.each do |klass_name| + Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name) + end + end + + let(:js_actors) do + 3.times.map do |i| + a = JSActor.new + a.id = i + 1 + a.name = "Test #{a.id}" + a.email = "test#{a.id}@test.com" + a + end + end + + let(:js_user) do + ams_user = JSUser.new + ams_user.id = 3 + ams_user + end + + let(:js_movie_type) do + ams_movie_type = JSMovieType.new + ams_movie_type.id = 1 + ams_movie_type.name = 'episode' + ams_movie_type + end + + def build_js_movies(count) + count.times.map do |i| + m = JSMovie.new + m.id = i + 1 + m.name = 'test movie' + m.actors = js_actors + m.owner = js_user + m.movie_type = js_movie_type + m + end + end +end diff --git a/spec/shared/contexts/js_group_context.rb b/spec/shared/contexts/js_group_context.rb new file mode 100644 index 0000000..fe4b427 --- /dev/null +++ b/spec/shared/contexts/js_group_context.rb @@ -0,0 +1,116 @@ +RSpec.shared_context 'jsonapi-serializers group class' do + + # Person, Group Classes and serializers + before(:context) do + # models + class JSPerson + attr_accessor :id, :first_name, :last_name + end + + class JSGroup + attr_accessor :id, :name, :groupees # Let's assume groupees can be Person or Group objects + end + + # serializers + class JSPersonSerializer + include JSONAPI::Serializer + attributes :first_name, :last_name + + def type + 'person' + end + end + + class JSGroupSerializer + include JSONAPI::Serializer + attributes :name + has_many :groupees + + def type + 'group' + end + end + + class JSONAPISSerializerB + def initialize(data, options = {}) + @options = options.merge(is_collection: true) + @data = data + end + + def to_json + JSON.fast_generate(to_hash) + end + + def to_hash + JSONAPI::Serializer.serialize(@data, @options) + end + end + end + + after :context do + classes_to_remove = %i[ + JSPerson + JSGroup + JSPersonSerializer + JSGroupSerializer] + classes_to_remove.each do |klass_name| + Object.send(:remove_const, klass_name) if Object.constants.include?(klass_name) + end + end + + let(:jsonapi_groups) do + group_count = 0 + person_count = 0 + 3.times.map do |i| + group = JSGroup.new + group.id = group_count + 1 + group.name = "Test Group #{group.id}" + group_count = group.id + + person = JSPerson.new + person.id = person_count + 1 + person.last_name = "Last Name #{person.id}" + person.first_name = "First Name #{person.id}" + person_count = person.id + + child_group = JSGroup.new + child_group.id = group_count + 1 + child_group.name = "Test Group #{child_group.id}" + group_count = child_group.id + + group.groupees = [person, child_group] + group + end + end + + let(:jsonapis_person) do + person = JSPerson.new + person.id = 3 + person + end + + def build_jsonapis_groups(count) + group_count = 0 + person_count = 0 + count.times.map do |i| + group = JSGroup.new + group.id = group_count + 1 + group.name = "Test Group #{group.id}" + group_count = group.id + + person = JSPerson.new + person.id = person_count + 1 + person.last_name = "Last Name #{person.id}" + person.first_name = "First Name #{person.id}" + person_count = person.id + + child_group = JSGroup.new + child_group.id = group_count + 1 + child_group.name = "Test Group #{child_group.id}" + group_count = child_group.id + + group.groupees = [person, child_group] + group + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 77f9226..d7aac0c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,7 @@ require 'byebug' require 'active_model_serializers' require 'oj' require 'jsonapi/serializable' +require 'jsonapi-serializers' Dir[File.dirname(__FILE__) + '/shared/contexts/*.rb'].each {|file| require file }