lib/active_model/csverizer.rb in csverizer-0.0.6 vs lib/active_model/csverizer.rb in csverizer-0.0.7
- old
+ new
@@ -1,49 +1,321 @@
require 'csv'
module ActiveModel
class Csverizer
- @_attributes = []
- @associations = []
- @root = true
+ @mutex = Mutex.new
class << self
- attr_accessor :_attributes, :associations, :root
+ def inherited(base)
+ base._root = _root
+ base._attributes = (_attributes || []).dup
+ base._associations = (_associations || {}).dup
+ end
+
+ def setup
+ @mutex.synchronize do
+ yield CONFIG
+ end
+ end
+
+ EMBED_IN_ROOT_OPTIONS = [
+ :include,
+ :embed_in_root,
+ :embed_in_root_key,
+ :embed_namespace
+ ].freeze
+
+ def embed(type, options={})
+ CONFIG.embed = type
+ if EMBED_IN_ROOT_OPTIONS.any? { |opt| options[opt].present? }
+ CONFIG.embed_in_root = true
+ end
+ if options[:embed_in_root_key].present?
+ CONFIG.embed_in_root_key = options[:embed_in_root_key]
+ end
+ ActiveSupport::Deprecation.warn <<-WARN
+** Notice: embed is deprecated. **
+The use of .embed method on a Serializer will be soon removed, as this should have a global scope and not a class scope.
+Please use the global .setup method instead:
+ActiveModel::Serializer.setup do |config|
+ config.embed = :#{type}
+ config.embed_in_root = #{CONFIG.embed_in_root || false}
+end
+ WARN
+ end
+
+ def format_keys(format)
+ @key_format = format
+ end
+ attr_reader :key_format
+
+ def serializer_for(resource, options = {})
+ if resource.respond_to?(:serializer_class)
+ resource.serializer_class
+ elsif resource.respond_to?(:to_ary)
+ if Object.constants.include?(:ArraySerializer)
+ ::ArraySerializer
+ else
+ ArraySerializer
+ end
+ else
+ klass_name = build_serializer_class(resource, options)
+ Serializer.serializers_cache.fetch_or_store(klass_name) do
+ _const_get(klass_name)
+ end
+ end
+ end
+
+ attr_accessor :_root, :_attributes, :_associations
+ alias root _root=
+ alias root= _root=
+
+ def root_name
+ if name
+ root_name = name.demodulize.underscore.sub(/_serializer$/, '')
+ CONFIG.plural_default_root ? root_name.pluralize : root_name
+ end
+ end
+
+ def attributes(*attrs)
+ attrs.each do |attr|
+ striped_attr = strip_attribute attr
+
+ @_attributes << striped_attr
+
+ define_method striped_attr do
+ object.read_attribute_for_serialization attr
+ end unless method_defined?(attr)
+ end
+ end
+
+ def has_one(*attrs)
+ associate(Association::HasOne, *attrs)
+ end
+
+ def has_many(*attrs)
+ associate(Association::HasMany, *attrs)
+ end
+
+ def serializers_cache
+ @serializers_cache ||= Concurrent::Map.new
+ end
+
+ private
+
+ def strip_attribute(attr)
+ symbolized = attr.is_a?(Symbol)
+
+ attr = attr.to_s.gsub(/\?\Z/, '')
+ attr = attr.to_sym if symbolized
+ attr
+ end
+
+ def build_serializer_class(resource, options)
+ "".tap do |klass_name|
+ klass_name << "#{options[:namespace]}::" if options[:namespace]
+ klass_name << options[:prefix].to_s.classify if options[:prefix]
+ klass_name << "#{resource.class.name}Serializer"
+ end
+ end
+
+ def associate(klass, *attrs)
+ options = attrs.extract_options!
+
+ attrs.each do |attr|
+ define_method attr do
+ object.send attr
+ end unless method_defined?(attr)
+
+ @_associations[attr] = klass.new(attr, options)
+ end
+ end
end
- def self.inherited(base)
- base._attributes = []
- base.associations = []
- base.root = true
+ def initialize(object, options={})
+ @object = object
+ @scope = options[:scope]
+ @root = options.fetch(:root, self.class._root)
+ @polymorphic = options.fetch(:polymorphic, false)
+ @meta_key = options[:meta_key] || :meta
+ @meta = options[@meta_key]
+ @wrap_in_array = options[:_wrap_in_array]
+ @only = options[:only] ? Array(options[:only]) : nil
+ @except = options[:except] ? Array(options[:except]) : nil
+ @key_format = options[:key_format]
+ @context = options[:context]
+ @namespace = options[:namespace]
+ @prefix = options.fetch(:prefix, '')
end
+ attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
- def self.attributes(*attributes)
- @_attributes = @_attributes.concat(attributes)
+ def json_key
+ key = if root == true || root.nil?
+ self.class.root_name
+ else
+ root
+ end
+
+ key_format == :lower_camel && key.present? ? key.camelize(:lower) : key
end
- def self.has_one(associated)
- @associations << {
- associated: associated,
- serializer: ActiveModel::CsverizerFactory
- }
+ def attributes
+ filter(self.class._attributes.dup).each_with_object({}) do |name, hash|
+ hash[name] = send(name)
+ end
end
- def self.has_many(associated)
- @associations << {
- associated: associated,
- serializer: ActiveModel::CsvArraySerializer
- }
+ def associations(options={})
+ associations = self.class._associations
+ included_associations = filter(associations.keys)
+ associations.each_with_object({}) do |(name, association), hash|
+ if included_associations.include? name
+ if association.embed_ids?
+ ids = serialize_ids association
+ if association.embed_namespace?
+ hash = hash[association.embed_namespace] ||= {}
+ hash[association.key] = ids
+ else
+ hash[association.key] = ids
+ end
+ elsif association.embed_objects?
+ if association.embed_namespace?
+ hash = hash[association.embed_namespace] ||= {}
+ end
+ hash[association.embedded_key] = serialize association, options
+ end
+ end
+ end
end
- attr_reader :object
+ def filter(keys)
+ if @only
+ keys & @only
+ elsif @except
+ keys - @except
+ else
+ keys
+ end
+ end
- def initialize(object, options = {})
- @object = object
- @root = options.fetch(:root, self.class.root)
- @prefix = options.fetch(:prefix, '')
+ def embedded_in_root_associations
+ associations = self.class._associations
+ included_associations = filter(associations.keys)
+ associations.each_with_object({}) do |(name, association), hash|
+ if included_associations.include? name
+ association_serializer = build_serializer(association)
+ # we must do this always because even if the current association is not
+ # embedded in root, it might have its own associations that are embedded in root
+ hash.merge!(association_serializer.embedded_in_root_associations) do |key, oldval, newval|
+ if oldval.respond_to?(:to_ary)
+ [oldval, newval].flatten.uniq
+ else
+ oldval.merge(newval) { |_, oldval, newval| [oldval, newval].flatten.uniq }
+ end
+ end
+
+ if association.embed_in_root?
+ if association.embed_in_root_key?
+ hash = hash[association.embed_in_root_key] ||= {}
+ end
+
+ serialized_data = association_serializer.serializable_object
+ key = association.root_key
+ if hash.has_key?(key)
+ hash[key].concat(serialized_data).uniq!
+ else
+ hash[key] = serialized_data
+ end
+ end
+ end
+ end
end
+ def build_serializer(association)
+ object = send(association.name)
+ association.build_serializer(object, association_options_for_serializer(association))
+ end
+
+ def association_options_for_serializer(association)
+ prefix = association.options[:prefix]
+ namespace = association.options[:namespace] || @namespace || self.namespace
+
+ { scope: scope }.tap do |opts|
+ opts[:namespace] = namespace if namespace
+ opts[:prefix] = prefix if prefix
+ end
+ end
+
+ def serialize(association,options={})
+ build_serializer(association).serializable_object(options)
+ end
+
+ def serialize_ids(association)
+ associated_data = send(association.name)
+ if associated_data.respond_to?(:to_ary)
+ associated_data.map { |elem| serialize_id(elem, association) }
+ else
+ serialize_id(associated_data, association) if associated_data
+ end
+ end
+
+ def key_format
+ @key_format || self.class.key_format || CONFIG.key_format
+ end
+
+ def format_key(key)
+ if key_format == :lower_camel
+ key.to_s.camelize(:lower)
+ else
+ key
+ end
+ end
+
+ def convert_keys(hash)
+ Hash[hash.map do |k,v|
+ key = if k.is_a?(Symbol)
+ format_key(k).to_sym
+ else
+ format_key(k)
+ end
+
+ [key ,v]
+ end]
+ end
+
+ attr_writer :serialization_options
+ def serialization_options
+ @serialization_options || {}
+ end
+
+ def serializable_object(options={})
+ self.serialization_options = options
+ return @wrap_in_array ? [] : nil if @object.nil?
+ hash = attributes
+ hash.merge! associations(options)
+ hash = convert_keys(hash) if key_format.present?
+ hash = { :type => type_name(@object), type_name(@object) => hash } if @polymorphic
+ @wrap_in_array ? [hash] : hash
+ end
+ alias_method :serializable_hash, :serializable_object
+
+ def serialize_id(elem, association)
+ id = elem.read_attribute_for_serialization(association.embed_key)
+ association.polymorphic? ? { id: id, type: type_name(elem) } : id
+ end
+
+ def type_name(elem)
+ elem.class.to_s.demodulize.underscore.to_sym
+ end
+
+ def to_csv
+ CSV.generate do |csv|
+ csv << attribute_names
+ to_a.each { |record| csv << record }
+ end
+ end
+
def to_a
return [[]] unless @object
values = []
values << self.class._attributes.collect { |name| read_attribute(name) }
@@ -53,38 +325,29 @@
end
values
end
- def to_csv
- CSV.generate do |csv|
- csv << attribute_names if @root
- to_a.each { |record| csv << record }
- end
- end
-
def attribute_names
names = self.class._attributes.collect do |attribute|
@prefix + attribute.to_s
end
associated_serializers.reduce(names) do |names, serializer|
names.concat serializer.attribute_names
end
end
- private
-
def read_attribute(name)
return send(name) if respond_to?(name)
object.read_attribute_for_serialization(name)
end
def read_association(name)
respond_to?(name) ? send(name) : object.send(name)
end
def associated_serializers
- @associated_serializers ||= self.class.associations.collect do |hash|
+ @associated_serializers ||= self.class._associations.collect do |hash|
object = read_attribute(hash[:associated])
hash[:serializer].new(object, prefix: hash[:associated].to_s + '_')
end
end
end