From eaedcefa4e186f60907f50b0980d9cc07fb3a1d3 Mon Sep 17 00:00:00 2001 From: Tema Bolshakov and Dmitry Myaskovskiy Date: Mon, 18 Aug 2014 18:04:08 +0400 Subject: [PATCH 1/5] Test::Unit assert_serializer implemented So you can assert specific serializer to be used. --- .../serialization_test_case.rb | 72 +++++++++++++++++++ lib/active_model/array_serializer.rb | 5 ++ lib/active_model/default_serializer.rb | 13 +++- lib/active_model/serializable.rb | 27 +++++-- lib/active_model_serializers.rb | 2 + .../action_controller/test_case_test.rb | 55 ++++++++++++++ 6 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 lib/action_controller/serialization_test_case.rb create mode 100644 test/integration/action_controller/test_case_test.rb diff --git a/lib/action_controller/serialization_test_case.rb b/lib/action_controller/serialization_test_case.rb new file mode 100644 index 00000000..31a612ad --- /dev/null +++ b/lib/action_controller/serialization_test_case.rb @@ -0,0 +1,72 @@ +module ActionController + module SerializationAssertions + extend ActiveSupport::Concern + + included do + setup :setup_subscriptions + teardown :teardown_subscriptions + end + + def setup_subscriptions + @serializers = Hash.new(0) + + ActiveSupport::Notifications.subscribe("!serialize.active_model_serializers") do |name, start, finish, id, payload| + serializer = payload[:serializer] + @serializers[serializer] += 1 + end + end + + def teardown_subscriptions + ActiveSupport::Notifications.unsubscribe("!serialize.active_model_serializers") + end + + def process(*args) + @serializers = Hash.new(0) + super + end + + # Asserts that the request was rendered with the appropriate serializers. + # + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer "PostSerializer" + # + # # assert that the exact serializer "Post" was rendered + # assert_serializer %r{\APost.+\Z} + # + # # assert that no serializer was rendered + # assert_serializer nil + # + def assert_serializer(options = {}, message = nil) + # Force body to be read in case the template is being streamed. + response.body + + case options + when NilClass, Regexp, String, Symbol + rendered = @serializers + msg = message || "expecting <#{options.inspect}> but rendering with <#{rendered.keys}>" + + matches_serializer = + case options + when Symbol + options = options.to_s.camelize + rendered.any? do |serializer, count| + serializer == options + end + when String + !options.empty? && rendered.any? do |serializer, count| + serializer == options + end + when Regexp + rendered.any? do |serializer, count| + serializer.match(options) + end + when NilClass + rendered.blank? + end + assert matches_serializer, msg + else + raise ArgumentError, "assert_serializer only accepts a String, Symbol, Regexp, or nil" + end + end + end +end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 8a42e18a..10d7d5d4 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -53,5 +53,10 @@ module ActiveModel end end end + + private + def instrumentation_keys + [:object, :scope, :root, :meta_key, :meta, :each_serializer, :resource_name, :key_format] + end end end diff --git a/lib/active_model/default_serializer.rb b/lib/active_model/default_serializer.rb index 5ab78c3b..c2db6b1d 100644 --- a/lib/active_model/default_serializer.rb +++ b/lib/active_model/default_serializer.rb @@ -15,11 +15,18 @@ module ActiveModel end def as_json(options={}) - return [] if @object.nil? && @wrap_in_array - hash = @object.as_json - @wrap_in_array ? [hash] : hash + instrument('!serialize') do + return [] if @object.nil? && @wrap_in_array + hash = @object.as_json + @wrap_in_array ? [hash] : hash + end end alias serializable_hash as_json alias serializable_object as_json + + private + def instrumentation_keys + [:object, :wrap_in_array] + end end end diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb index 7a34bc04..e70a9afc 100644 --- a/lib/active_model/serializable.rb +++ b/lib/active_model/serializable.rb @@ -1,12 +1,14 @@ module ActiveModel module Serializable def as_json(options={}) - if root = options.fetch(:root, json_key) - hash = { root => serializable_object } - hash.merge!(serializable_data) - hash - else - serializable_object + instrument('!serialize') do + if root = options.fetch(:root, json_key) + hash = { root => serializable_object } + hash.merge!(serializable_data) + hash + else + serializable_object + end end end @@ -21,5 +23,18 @@ module ActiveModel def embedded_in_root_associations {} end + + private + def instrument(action, &block) + payload = instrumentation_keys.inject({ serializer: self.class.name }) do |payload, key| + payload[:payload] = self.instance_variable_get(:"@#{key}") + payload + end + ActiveSupport::Notifications.instrument("#{action}.active_model_serializers", payload, &block) + end + + def instrumentation_keys + [:object, :scope, :root, :meta_key, :meta, :wrap_in_array, :only, :except, :key_format] + end end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 80ce3470..01b2ed83 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -7,10 +7,12 @@ require 'active_model/serializer/railtie' if defined?(Rails) begin require 'action_controller' require 'action_controller/serialization' + require 'action_controller/serialization_test_case' ActiveSupport.on_load(:after_initialize) do if ::ActionController::Serialization.enabled ActionController::Base.send(:include, ::ActionController::Serialization) + ActionController::TestCase.send(:include, ::ActionController::SerializationAssertions) end end rescue LoadError diff --git a/test/integration/action_controller/test_case_test.rb b/test/integration/action_controller/test_case_test.rb new file mode 100644 index 00000000..abfefc95 --- /dev/null +++ b/test/integration/action_controller/test_case_test.rb @@ -0,0 +1,55 @@ +require 'test_helper' + +module ActionController + module SerializationsAssertions + class RenderSerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_serializer + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + def render_text + render text: 'ok' + end + end + + tests MyController + + def test_supports_specifying_serializers_with_a_regexp + get :render_using_serializer + assert_serializer %r{\AProfile.+\Z} + end + + def test_supports_specifying_serializers_with_a_string + get :render_using_serializer + assert_serializer 'ProfileSerializer' + end + + def test_supports_specifying_serializers_with_a_symbol + get :render_using_serializer + assert_serializer :profile_serializer + end + + def test_supports_specifying_serializers_with_a_nil + get :render_text + assert_serializer nil + end + + def test_raises_descriptive_error_message_when_serializer_was_not_rendered + get :render_using_serializer + e = assert_raise ActiveSupport::TestCase::Assertion do + assert_serializer 'PostSerializer' + end + assert_match 'expecting <"PostSerializer"> but rendering with <["ProfileSerializer"]>', e.message + end + + def test_raises_argument_error_when_asserting_with_invalid_object + get :render_using_serializer + e = assert_raise ArgumentError do + assert_serializer OpenStruct.new + end + assert_match 'assert_serializer only accepts a String, Symbol, Regexp, or nil', e.message + end + end + end +end From aa66592a8009d5bb22100e359d6db9357f809a1c Mon Sep 17 00:00:00 2001 From: Tema Bolshakov and Dmitry Myaskovskiy Date: Mon, 18 Aug 2014 18:24:07 +0400 Subject: [PATCH 2/5] Update docs --- lib/action_controller/serialization_test_case.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/action_controller/serialization_test_case.rb b/lib/action_controller/serialization_test_case.rb index 31a612ad..b6a57d03 100644 --- a/lib/action_controller/serialization_test_case.rb +++ b/lib/action_controller/serialization_test_case.rb @@ -27,14 +27,17 @@ module ActionController # Asserts that the request was rendered with the appropriate serializers. # - # # assert that the "PostSerializer" serializer was rendered - # assert_serializer "PostSerializer" + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer "PostSerializer" # - # # assert that the exact serializer "Post" was rendered - # assert_serializer %r{\APost.+\Z} + # # assert that the "PostSerializer" serializer was rendered + # assert_serializer :post_serializer # - # # assert that no serializer was rendered - # assert_serializer nil + # # assert that the rendered serializer starts with "Post" + # assert_serializer %r{\APost.+\Z} + # + # # assert that no serializer was rendered + # assert_serializer nil # def assert_serializer(options = {}, message = nil) # Force body to be read in case the template is being streamed. From 865ab64025b9fab3129f2ec20ff3e60c74c73235 Mon Sep 17 00:00:00 2001 From: Tema Bolshakov Date: Wed, 20 Aug 2014 11:32:55 +0400 Subject: [PATCH 3/5] rename spec file --- .../{test_case_test.rb => serialization_test_case_test.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/integration/action_controller/{test_case_test.rb => serialization_test_case_test.rb} (100%) diff --git a/test/integration/action_controller/test_case_test.rb b/test/integration/action_controller/serialization_test_case_test.rb similarity index 100% rename from test/integration/action_controller/test_case_test.rb rename to test/integration/action_controller/serialization_test_case_test.rb From c5d9d97f26dd7c4d58372f6ef7fa7398084a5396 Mon Sep 17 00:00:00 2001 From: Tema Bolshakov Date: Wed, 20 Aug 2014 12:34:34 +0400 Subject: [PATCH 4/5] Assert with serializer class --- .../serialization_test_case.rb | 59 +++++++++++-------- .../serialization_test_case_test.rb | 10 +++- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/lib/action_controller/serialization_test_case.rb b/lib/action_controller/serialization_test_case.rb index b6a57d03..ee75fc99 100644 --- a/lib/action_controller/serialization_test_case.rb +++ b/lib/action_controller/serialization_test_case.rb @@ -30,6 +30,9 @@ module ActionController # # assert that the "PostSerializer" serializer was rendered # assert_serializer "PostSerializer" # + # # assert that the instance of PostSerializer was rendered + # assert_serializer PostSerializer + # # # assert that the "PostSerializer" serializer was rendered # assert_serializer :post_serializer # @@ -39,37 +42,41 @@ module ActionController # # assert that no serializer was rendered # assert_serializer nil # + # def assert_serializer(options = {}, message = nil) # Force body to be read in case the template is being streamed. response.body - case options - when NilClass, Regexp, String, Symbol - rendered = @serializers - msg = message || "expecting <#{options.inspect}> but rendering with <#{rendered.keys}>" + rendered = @serializers + msg = message || "expecting <#{options.inspect}> but rendering with <#{rendered.keys}>" - matches_serializer = - case options - when Symbol - options = options.to_s.camelize - rendered.any? do |serializer, count| - serializer == options - end - when String - !options.empty? && rendered.any? do |serializer, count| - serializer == options - end - when Regexp - rendered.any? do |serializer, count| - serializer.match(options) - end - when NilClass - rendered.blank? - end - assert matches_serializer, msg - else - raise ArgumentError, "assert_serializer only accepts a String, Symbol, Regexp, or nil" - end + matches_serializer = case options + when lambda { |options| options.kind_of?(Class) && options < ActiveModel::Serializer } + rendered.any? do |serializer, count| + options.name == serializer + end + when NilClass, Regexp, String, Symbol + case options + when Symbol + options = options.to_s.camelize + rendered.any? do |serializer, count| + serializer == options + end + when String + !options.empty? && rendered.any? do |serializer, count| + serializer == options + end + when Regexp + rendered.any? do |serializer, count| + serializer.match(options) + end + when NilClass + rendered.blank? + end + else + raise ArgumentError, "assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil" + end + assert matches_serializer, msg end end end diff --git a/test/integration/action_controller/serialization_test_case_test.rb b/test/integration/action_controller/serialization_test_case_test.rb index abfefc95..9532a88b 100644 --- a/test/integration/action_controller/serialization_test_case_test.rb +++ b/test/integration/action_controller/serialization_test_case_test.rb @@ -15,6 +15,11 @@ module ActionController tests MyController + def test_supports_specifying_serializers_with_a_serializer_class + get :render_using_serializer + assert_serializer ProfileSerializer + end + def test_supports_specifying_serializers_with_a_regexp get :render_using_serializer assert_serializer %r{\AProfile.+\Z} @@ -43,12 +48,13 @@ module ActionController assert_match 'expecting <"PostSerializer"> but rendering with <["ProfileSerializer"]>', e.message end + def test_raises_argument_error_when_asserting_with_invalid_object get :render_using_serializer e = assert_raise ArgumentError do - assert_serializer OpenStruct.new + assert_serializer Hash end - assert_match 'assert_serializer only accepts a String, Symbol, Regexp, or nil', e.message + assert_match 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil', e.message end end end From 699b378984fbf0d7156f34bb09a258ba1acd1a5c Mon Sep 17 00:00:00 2001 From: Tema Bolshakov Date: Wed, 20 Aug 2014 12:35:23 +0400 Subject: [PATCH 5/5] remove nested case --- .../serialization_test_case.rb | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/action_controller/serialization_test_case.rb b/lib/action_controller/serialization_test_case.rb index ee75fc99..37c882b5 100644 --- a/lib/action_controller/serialization_test_case.rb +++ b/lib/action_controller/serialization_test_case.rb @@ -55,24 +55,21 @@ module ActionController rendered.any? do |serializer, count| options.name == serializer end - when NilClass, Regexp, String, Symbol - case options - when Symbol - options = options.to_s.camelize - rendered.any? do |serializer, count| - serializer == options - end - when String - !options.empty? && rendered.any? do |serializer, count| - serializer == options - end - when Regexp - rendered.any? do |serializer, count| - serializer.match(options) - end - when NilClass - rendered.blank? + when Symbol + options = options.to_s.camelize + rendered.any? do |serializer, count| + serializer == options end + when String + !options.empty? && rendered.any? do |serializer, count| + serializer == options + end + when Regexp + rendered.any? do |serializer, count| + serializer.match(options) + end + when NilClass + rendered.blank? else raise ArgumentError, "assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil" end