diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 610bc5963..ff1c6a300 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -2,16 +2,18 @@ module ActiveModel class Serializer class << self attr_accessor :_attributes + attr_accessor :_associations end def self.inherited(base) base._attributes = [] + base._associations = {} end def self.attributes(*attrs) @_attributes.concat attrs - + attrs.each do |attr| define_method attr do object.read_attribute_for_serialization(attr) @@ -19,6 +21,41 @@ def self.attributes(*attrs) end end + # Defines an association in the object should be rendered. + # + # The serializer object should implement the association name + # as a method which should return an array when invoked. If a method + # with the association name does not exist, the association name is + # dispatched to the serialized object. + def self.has_many(*attrs) + associate(:has_many, attrs) + end + + # Defines an association in the object should be rendered. + # + # The serializer object should implement the association name + # as a method which should return an object when invoked. If a method + # with the association name does not exist, the association name is + # dispatched to the serialized object. + def self.belongs_to(*attrs) + associate(:belongs_to, attrs) + end + + def self.associate(type, attrs) #:nodoc: + options = attrs.extract_options! + self._associations = _associations.dup + + attrs.each do |attr| + unless method_defined?(attr) + define_method attr do + object.send attr + end + end + + self._associations[attr] = {type: type, options: options} + end + end + if RUBY_VERSION >= '2.0' def self.serializer_for(resource) if resource.respond_to?(:to_ary) diff --git a/test/serializers/associations_test.rb b/test/serializers/associations_test.rb new file mode 100644 index 000000000..e019c0734 --- /dev/null +++ b/test/serializers/associations_test.rb @@ -0,0 +1,65 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class AssocationsTest < Minitest::Test + def def_serializer(&block) + Class.new(ActiveModel::Serializer, &block) + end + + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + + def method_missing(meth, *args) + if meth.to_s =~ /^(.*)=$/ + @attributes[$1.to_sym] = args[0] + elsif @attributes.key?(meth) + @attributes[meth] + else + super + end + end + end + + def setup + @post = Model.new({ title: 'New Post', body: 'Body' }) + @comment = Model.new({ id: 1, body: 'ZOMG A COMMENT' }) + @post.comments = [@comment] + @comment.post = @post + + @post_serializer_class = def_serializer do + attributes :title, :body + end + + @comment_serializer_class = def_serializer do + attributes :id, :body + end + + @post_serializer = @post_serializer_class.new(@post) + @comment_serializer = @comment_serializer_class.new(@comment) + end + + def test_has_many + @post_serializer_class.class_eval do + has_many :comments + end + + assert_equal({comments: {type: :has_many, options: {}}}, @post_serializer.class._associations) + end + + def test_has_one + @comment_serializer_class.class_eval do + belongs_to :post + end + + assert_equal({post: {type: :belongs_to, options: {}}}, @comment_serializer.class._associations) + end + end + end +end