diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 72c9065f..6e3ea6cb 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -158,6 +158,58 @@ module ActiveModel associate(Associations::HasOne, attrs) end + # Return a schema hash for the current serializer. This information + # can be used to generate clients for the serialized output. + # + # The schema hash has two keys: +attributes+ and +associations+. + # + # The +attributes+ hash looks like this: + # + # { :name => :string, :age => :integer } + # + # The +associations+ hash looks like this: + # + # { :posts => { :has_many => :posts } } + # + # If :as is used: + # + # class PostsSerializer < ActiveModel::Serializer + # has_many :my_posts, :as => :posts + # end + # + # the hash looks like this: + # + # { :my_posts => { :has_many => :posts } + # + # This information is extracted from the serializer's model class, + # which is provided by +SerializerClass.model_class+. + # + # The schema method uses the +columns_hash+ and +reflect_on_association+ + # methods, provided by default by ActiveRecord. You can implement these + # methods on your custom models if you want the serializer's schema method + # to work. + def schema + klass = model_class + columns = klass.columns_hash + + attrs = _attributes.inject({}) do |hash, name| + column = columns[name] + hash.merge name => column[:type] + end + + associations = _associations.inject({}) do |hash, association| + model_association = klass.reflect_on_association(association.key) + hash.merge association.key => { model_association.macro => model_association.name } + end + + { :attributes => attrs, :associations => associations } + end + + # The model class associated with this serializer. + def model_class + name.sub(/Serializer$/, '') + end + # Define how associations should be embedded. # # embed :objects # Embed associations as full objects diff --git a/test/serializer_test.rb b/test/serializer_test.rb index b09e0e4c..0ceed941 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -508,4 +508,67 @@ class SerializerTest < ActiveModel::TestCase } }, serializer.as_json) end + + def setup_model + Class.new do + class << self + def columns_hash + { :name => { :type => :string }, :age => { :type => :integer } } + end + + def reflect_on_association(name) + case name + when :posts + Struct.new(:macro, :name).new(:has_many, :posts) + when :parent + Struct.new(:macro, :name).new(:belongs_to, :parent) + end + end + end + end + end + + def test_schema + model = setup_model + + serializer = Class.new(ActiveModel::Serializer) do + class << self; self; end.class_eval do + define_method(:model_class) do model end + end + + attributes :name, :age + has_many :posts, :serializer => Class.new + has_one :parent, :serializer => Class.new + end + + assert_equal serializer.schema, { + :attributes => { :name => :string, :age => :integer }, + :associations => { + :posts => { :has_many => :posts }, + :parent => { :belongs_to => :parent } + } + } + end + + def test_schema_with_as + model = setup_model + + serializer = Class.new(ActiveModel::Serializer) do + class << self; self; end.class_eval do + define_method(:model_class) do model end + end + + attributes :name, :age + has_many :my_posts, :as => :posts, :serializer => Class.new + has_one :my_parent, :as => :parent, :serializer => Class.new + end + + assert_equal serializer.schema, { + :attributes => { :name => :string, :age => :integer }, + :associations => { + :my_posts => { :has_many => :posts }, + :my_parent => { :belongs_to => :parent } + } + } + end end