diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index aece7a47..ea3b4b6c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,15 +1,18 @@ +require 'active_model/serializer/associations' + module ActiveModel class Serializer class << self def inherited(base) - base._attributes = {} + base._attributes = [] + base._associations = [] end def serializer_for(resource) "#{resource.class.name}Serializer".safe_constantize end - attr_accessor :_root, :_attributes + attr_accessor :_root, :_attributes, :_associations def root(root) @_root = root @@ -21,7 +24,7 @@ module ActiveModel end def attributes(*attrs) - @_attributes = attrs.map(&:to_s) + @_attributes.concat attrs.map(&:to_s) attrs.each do |attr| unless method_defined?(attr) @@ -31,6 +34,22 @@ module ActiveModel end end end + + def has_one(*attrs) + options = attrs.extract_options! + + attrs.each do |attr| + attr = attr.to_s + + unless method_defined?(attr) + define_method attr do + object.send attr + end + end + + @_associations << Association::HasOne.new(attr, options) + end + end end def initialize(object, options={}) @@ -51,9 +70,30 @@ module ActiveModel end end + def associations + self.class._associations.each_with_object({}) do |association, hash| + if association.embed_ids? + hash[association.key] = serialize_ids association + elsif association.embed_objects? + # TODO + hash + end + end + end + + def serialize_ids(association) + associated_data = send(association.name) + if associated_data.respond_to?(:to_ary) + associated_data.map { |elem| elem.send(association.embed_key) } + else + associated_data.send(association.embed_key) + end + end + def serializable_hash(options={}) return nil if object.nil? - attributes + hash = attributes + hash.merge! associations end def as_json(options={}) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb new file mode 100644 index 00000000..a8bb4167 --- /dev/null +++ b/lib/active_model/serializer/associations.rb @@ -0,0 +1,43 @@ +module ActiveModel + class Serializer + class Association + def initialize(name, options={}) + @name = name + @options = options + + self.embed = options[:embed] + @embed_key = options[:embed_key] || :id + @include = options[:include] + + serializer = @options[:serializer] + @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer + end + + attr_reader :name, :embed_ids, :embed_objects, :embed_key, :include + alias embed_ids? embed_ids + alias embed_objects? embed_objects + alias include? include + + def embed=(embed) + @embed_ids = embed == :id || embed == :ids + @embed_objects = embed == :object || embed == :objects + end + + def build_serializer(object) + @serializer_class ||= Serializer.serializer_for(object) + + if @serializer_class + @serializer_class.new(object, @options) + else + object + end + end + + class HasOne < Association + def key + "#{name}_id" + end + end + end + end +end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 4d5850be..10586934 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -6,6 +6,14 @@ class Model def read_attribute_for_serialization(name) @attributes[name] end + + def model + @model ||= Model.new(attr1: 'v1', attr2: 'v2') + end + + def id + object_id + end end class ModelSerializer < ActiveModel::Serializer @@ -20,3 +28,9 @@ class ModelSerializer < ActiveModel::Serializer attributes :attr1, :attr2 end + +class AnotherSerializer < ActiveModel::Serializer + attributes :attr2, :attr3 + + has_one :model +end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb new file mode 100644 index 00000000..652ed3e4 --- /dev/null +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -0,0 +1,35 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class Serializer + class HasOneTest < ActiveModel::TestCase + def setup + @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @model_serializer = AnotherSerializer.new(@model) + end + + def test_associations_definition + associations = @model_serializer.class._associations + + assert_equal 1, associations.length + assert_kind_of Association::HasOne, associations[0] + assert_equal 'model', associations[0].name + end + + def test_associations_serialization_using_serializable_hash + @model_serializer.class._associations[0].embed = :ids + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id + }, @model_serializer.serializable_hash) + end + + def test_associations_serialization_using_as_json + @model_serializer.class._associations[0].embed = :ids + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id + }, @model_serializer.as_json) + end + end + end +end