diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 2d0d08b40..bbe2b508a 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -59,4 +59,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'grape', ['>= 0.13', '< 1.0'] spec.add_development_dependency 'json_schema' spec.add_development_dependency 'rake', ['>= 10.0', '< 12.0'] + spec.add_development_dependency 'pry-byebug' + spec.add_development_dependency 'm' end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7d499b909..de71f5e3f 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -191,10 +191,24 @@ def json_key end def read_attribute_for_serialization(attr) + # Start by favoring serializers method override if respond_to?(attr) send(attr) else - object.read_attribute_for_serialization(attr) + # Otherwise, check read_attribute_for_serialization + if object.respond_to?(:read_attribute_for_serialization) + object.read_attribute_for_serialization(attr) + else + # Fall back to object property/method + if object.respond_to?(attr) + object.send(attr) + else + # Special logic for hashes + if object.is_a?(Hash) + object[attr] || object[attr.to_s] + end + end + end end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 76a32c497..02bef8015 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -3,6 +3,11 @@ require 'active_support/core_ext/object/with_options' require 'active_support/core_ext/string/inflections' require 'active_support/json' + +require 'active_model_serializers/model_mixin' + +require 'pry-byebug' + module ActiveModelSerializers extend ActiveSupport::Autoload autoload :Model diff --git a/lib/active_model_serializers/model.rb b/lib/active_model_serializers/model.rb index b9937cb5c..b3ccaf9f7 100644 --- a/lib/active_model_serializers/model.rb +++ b/lib/active_model_serializers/model.rb @@ -3,23 +3,20 @@ # serializable non-activerecord objects. module ActiveModelSerializers class Model - include ActiveModel::Model - include ActiveModel::Serializers::JSON + include ActiveModelSerializers::ModelMixin - attr_reader :attributes, :errors + attr_reader :attributes - def initialize(attributes = {}) - @attributes = attributes && attributes.symbolize_keys - @errors = ActiveModel::Errors.new(self) - super - end + def initialize(attrs = {}) + @attributes = attrs && attrs.symbolize_keys - # Defaults to the downcased model name. - def id - attributes.fetch(:id) { self.class.name.downcase } + @attributes.each_pair do |key, value| + if respond_to?("#{key}=", value) + send("#{key}=", value) + end + end end - # Defaults to the downcased model name and updated_at def cache_key attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" } end @@ -29,23 +26,8 @@ def updated_at attributes.fetch(:updated_at) { File.mtime(__FILE__) } end - def read_attribute_for_serialization(key) - if key == :id || key == 'id' - attributes.fetch(key) { id } - else - attributes[key] - end - end - - # The following methods are needed to be minimally implemented for ActiveModel::Errors - # :nocov: - def self.human_attribute_name(attr, _options = {}) - attr - end - - def self.lookup_ancestors - [self] + def id + attributes.fetch(:id) { self.class.name.downcase } end - # :nocov: end end diff --git a/lib/active_model_serializers/model_mixin.rb b/lib/active_model_serializers/model_mixin.rb new file mode 100644 index 000000000..66af7d64e --- /dev/null +++ b/lib/active_model_serializers/model_mixin.rb @@ -0,0 +1,57 @@ +# ActiveModelSerializers::Model is a convenient +# serializable class to inherit from when making +# serializable non-activerecord objects. +module ActiveModelSerializers + module ModelMixin + def self.included(klass) + klass.class_eval do + include ActiveModel::Model + include ActiveModel::Serializers::JSON + + def read_attribute_for_serialization(key) + if key == :id || key == 'id' + send(key) + else + if is_a?(ActiveModelSerializers::Model) + # Support legacy behavior + attributes[key] || (send(key) if respond_to?(key)) + else + send(key) if respond_to?(key) + end + end + end + end + end + + #def id + #self.class.name.downcase + #end + + #def cache_key + #end + + #def errors + #@errors ||= ActiveModel::Errors.new(self) + #end + + #def updated_at + #end + + # This is just to make lint pass + # We shouldnt have this... + #def attributes + #{} + #end + + # The following methods are needed to be minimally implemented for ActiveModel::Errors + # :nocov: + #def self.human_attribute_name(attr, _options = {}) + #attr + #end + + #def self.lookup_ancestors + #[self] + #end + # :nocov: + end +end diff --git a/test/action_controller/json_api/fields_test.rb b/test/action_controller/json_api/fields_test.rb index 4bf08c7e9..988c4815f 100644 --- a/test/action_controller/json_api/fields_test.rb +++ b/test/action_controller/json_api/fields_test.rb @@ -1,5 +1,5 @@ require 'test_helper' - +require 'pry-byebug' module ActionController module Serialization class JsonApi diff --git a/test/active_model_serializers/model_mixin_test.rb b/test/active_model_serializers/model_mixin_test.rb new file mode 100644 index 000000000..ccbe5c1f4 --- /dev/null +++ b/test/active_model_serializers/model_mixin_test.rb @@ -0,0 +1,26 @@ +require 'test_helper' + +module ActiveModelSerializers + class ModelMixinTest < ActiveSupport::TestCase + def setup + @klass = Class.new do + #include ActiveModelSerializers::ModelMixin + attr_accessor :key, :id + def self.name + 'TestModel' + end + end + end + + def test_poro_serialize + serializer = Class.new(ActiveModel::Serializer) do + attributes :key + end + model_instance = @klass.new + model_instance.key = 'value' + + json = serializer.new(model_instance, {}).as_json + assert_equal({ key: 'value' }, json) + end + end +end diff --git a/test/active_model_serializers/model_test.rb b/test/active_model_serializers/model_test.rb index 7bfb2edf4..dc53c16cf 100644 --- a/test/active_model_serializers/model_test.rb +++ b/test/active_model_serializers/model_test.rb @@ -6,17 +6,73 @@ class ModelTest < ActiveSupport::TestCase def setup @resource = ActiveModelSerializers::Model.new + + @klass = Class.new(ActiveModelSerializers::Model) do + attr_accessor :key + + def self.name + 'TestModel' + end + end end def test_initialization_with_string_keys - klass = Class.new(ActiveModelSerializers::Model) do - attr_accessor :key + model_instance = @klass.new('key' => 'value') + + assert_equal 'value', model_instance.read_attribute_for_serialization(:key) + end + + def test_direct_accessor_assignment + model_instance = @klass.new + model_instance.key = 'value' + assert_equal 'value', model_instance.read_attribute_for_serialization(:key) + end + + def test_fetch_id_from_attributes + model_instance = @klass.new(id: 1) + assert_equal 1, model_instance.id + end + + # Note: 'id' defined by attr_accessor + # is mutually exclusive from default id behavior + # IOW, defined attr_accessors 'win' + def test_id_from_accessor + @klass.class_eval do + attr_accessor :id end - value = 'value' + model_instance = @klass.new + model_instance.id = 1 + assert_equal 1, model_instance.id + end - model_instance = klass.new('key' => value) + # not needed, out superclass + #def test_id_as_defined_method + #@klass.class_eval do + #def id + #@id + #end + + #def id=(val) + #@id = val + #end + #end + + #model_instance = @klass.new + #model_instance.id = 1 + #assert_equal 1, model_instance.id + #end + + # Note: This does not work if @klass defines + # attr_accessor :id + def test_default_id + model_instance = @klass.new + assert_equal 'testmodel', model_instance.id + end - assert_equal model_instance.read_attribute_for_serialization(:key), value + def test_errors + model_instance = @klass.new + model_instance.errors.add(:key, 'is blank') + assert_equal true, model_instance.errors[:key].present? end end end diff --git a/test/adapter/json_api/resource_identifier_test.rb b/test/adapter/json_api/resource_identifier_test.rb index 62b7d93b3..a1458ca25 100644 --- a/test/adapter/json_api/resource_identifier_test.rb +++ b/test/adapter/json_api/resource_identifier_test.rb @@ -1,5 +1,7 @@ require 'test_helper' +require 'pry-byebug' + module ActiveModelSerializers module Adapter class JsonApi diff --git a/test/serializers/read_attribute_for_serialization_test.rb b/test/serializers/read_attribute_for_serialization_test.rb index 21677e657..baaf0d584 100644 --- a/test/serializers/read_attribute_for_serialization_test.rb +++ b/test/serializers/read_attribute_for_serialization_test.rb @@ -1,4 +1,5 @@ require 'test_helper' +require 'pry-byebug' module ActiveModel class Serializer @@ -51,7 +52,8 @@ def success end def test_child_serializer_with_error_attribute - error = ErrorResponse.new(error: 'i have an error') + error = ErrorResponse.new + error.error = 'i have an error' serializer = ErrorResponseSerializer.new(error) serializer_with_super = ErrorResponseWithSuperSerializer.new(error) assert_equal false, serializer.read_attribute_for_serialization(:status)