diff options
Diffstat (limited to 'activemodel/lib/active_model/serializer.rb')
-rw-r--r-- | activemodel/lib/active_model/serializer.rb | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb new file mode 100644 index 0000000000..0e23df2f2b --- /dev/null +++ b/activemodel/lib/active_model/serializer.rb @@ -0,0 +1,253 @@ +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/module/anonymous" +require "set" + +module ActiveModel + # Active Model Array Serializer + # + # It serializes an array checking if each element that implements + # the +active_model_serializer+ method passing down the current scope. + class ArraySerializer + attr_reader :object, :scope + + def initialize(object, scope) + @object, @scope = object, scope + end + + def serializable_array + @object.map do |item| + if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer) + serializer.new(item, scope) + else + item + end + end + end + + def as_json(*args) + serializable_array.as_json(*args) + end + end + + # Active Model Serializer + # + # Provides a basic serializer implementation that allows you to easily + # control how a given object is going to be serialized. On initialization, + # it expects to object as arguments, a resource and a scope. For example, + # one may do in a controller: + # + # PostSerializer.new(@post, current_user).to_json + # + # The object to be serialized is the +@post+ and the scope is +current_user+. + # + # We use the scope to check if a given attribute should be serialized or not. + # For example, some attributes maybe only be returned if +current_user+ is the + # author of the post: + # + # class PostSerializer < ActiveModel::Serializer + # attributes :title, :body + # has_many :comments + # + # private + # + # def attributes + # hash = super + # hash.merge!(:email => post.email) if author? + # hash + # end + # + # def author? + # post.author == scope + # end + # end + # + class Serializer + module Associations #:nodoc: + class Config < Struct.new(:name, :options) #:nodoc: + def serializer + options[:serializer] + end + end + + class HasMany < Config #:nodoc: + def serialize(collection, scope) + collection.map do |item| + serializer.new(item, scope).serializable_hash + end + end + + def serialize_ids(collection, scope) + # use named scopes if they are present + # return collection.ids if collection.respond_to?(:ids) + + collection.map do |item| + item.read_attribute_for_serialization(:id) + end + end + end + + class HasOne < Config #:nodoc: + def serialize(object, scope) + object && serializer.new(object, scope).serializable_hash + end + + def serialize_ids(object, scope) + object && object.read_attribute_for_serialization(:id) + end + end + end + + class_attribute :_attributes + self._attributes = Set.new + + class_attribute :_associations + self._associations = [] + + class_attribute :_root + class_attribute :_embed + self._embed = :objects + class_attribute :_root_embed + + class << self + # Define attributes to be used in the serialization. + def attributes(*attrs) + self._attributes += attrs + end + + def associate(klass, attrs) #:nodoc: + options = attrs.extract_options! + self._associations += attrs.map do |attr| + unless method_defined?(attr) + class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__ + end + + options[:serializer] ||= const_get("#{attr.to_s.camelize}Serializer") + klass.new(attr, options) + 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 has_many(*attrs) + associate(Associations::HasMany, 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 has_one(*attrs) + associate(Associations::HasOne, attrs) + end + + # Define how associations should be embedded. + # + # embed :objects # Embed associations as full objects + # embed :ids # Embed only the association ids + # embed :ids, :include => true # Embed the association ids and include objects in the root + # + def embed(type, options={}) + self._embed = type + self._root_embed = true if options[:include] + end + + # Defines the root used on serialization. If false, disables the root. + def root(name) + self._root = name + end + + def inherited(klass) #:nodoc: + return if klass.anonymous? + + name = klass.name.demodulize.underscore.sub(/_serializer$/, '') + + klass.class_eval do + alias_method name.to_sym, :object + root name.to_sym unless self._root == false + end + end + end + + attr_reader :object, :scope + + def initialize(object, scope) + @object, @scope = object, scope + end + + # Returns a json representation of the serializable + # object including the root. + def as_json(*) + if _root + hash = { _root => serializable_hash } + hash.merge!(associations) if _root_embed + hash + else + serializable_hash + end + end + + # Returns a hash representation of the serializable + # object without the root. + def serializable_hash + if _embed == :ids + attributes.merge(association_ids) + elsif _embed == :objects + attributes.merge(associations) + else + attributes + end + end + + # Returns a hash representation of the serializable + # object associations. + def associations + hash = {} + + _associations.each do |association| + associated_object = send(association.name) + hash[association.name] = association.serialize(associated_object, scope) + end + + hash + end + + # Returns a hash representation of the serializable + # object associations ids. + def association_ids + hash = {} + + _associations.each do |association| + associated_object = send(association.name) + hash[association.name] = association.serialize_ids(associated_object, scope) + end + + hash + end + + # Returns a hash representation of the serializable + # object attributes. + def attributes + hash = {} + + _attributes.each do |name| + hash[name] = @object.read_attribute_for_serialization(name) + end + + hash + end + end +end + +class Array + # Array uses ActiveModel::ArraySerializer. + def active_model_serializer + ActiveModel::ArraySerializer + end +end
\ No newline at end of file |