Associations refactoring

* Move all associations related code from Serializer class to Associations module
* Introduce Reflection class hierarchy
* Introduce Association class
* Rid off Serializer#each_association
* Introduce Serializer#associations enumerator
This commit is contained in:
Артём Большаков
2015-07-06 10:55:18 +03:00
parent 438d8f411a
commit 2952a332e0
13 changed files with 381 additions and 175 deletions

View File

@@ -3,16 +3,18 @@ require 'thread_safe'
module ActiveModel
class Serializer
extend ActiveSupport::Autoload
autoload :Configuration
autoload :ArraySerializer
autoload :Adapter
autoload :Lint
autoload :Associations
include Configuration
include Associations
class << self
attr_accessor :_attributes
attr_accessor :_attributes_keys
attr_accessor :_associations
attr_accessor :_urls
attr_accessor :_cache
attr_accessor :_fragmented
@@ -24,12 +26,12 @@ module ActiveModel
end
def self.inherited(base)
base._attributes = self._attributes.try(:dup) || []
base._attributes = self._attributes.try(:dup) || []
base._attributes_keys = self._attributes_keys.try(:dup) || {}
base._associations = self._associations.try(:dup) || {}
base._urls = []
serializer_file = File.open(caller.first[/^[^:]+/])
base._cache_digest = Digest::MD5.hexdigest(serializer_file.read)
super
end
def self.attributes(*attrs)
@@ -46,7 +48,7 @@ module ActiveModel
def self.attribute(attr, options = {})
key = options.fetch(:key, attr)
@_attributes_keys[attr] = {key: key} if key != attr
@_attributes_keys[attr] = { key: key } if key != attr
@_attributes << key unless @_attributes.include?(key)
define_method key do
object.read_attribute_for_serialization(attr)
@@ -59,58 +61,13 @@ module ActiveModel
# Enables a serializer to be automatically cached
def self.cache(options = {})
@_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
@_cache_key = options.delete(:key)
@_cache_only = options.delete(:only)
@_cache_except = options.delete(:except)
@_cache = ActionController::Base.cache_store if Rails.configuration.action_controller.perform_caching
@_cache_key = options.delete(:key)
@_cache_only = options.delete(:only)
@_cache_except = options.delete(:except)
@_cache_options = (options.empty?) ? nil : options
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 that 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
# 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.has_one(*attrs)
associate(:has_one, 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, association_options: options}
end
end
def self.url(attr)
@_urls.push attr
end
@@ -125,19 +82,17 @@ module ActiveModel
elsif resource.respond_to?(:to_ary)
config.array_serializer
else
options
.fetch(:association_options, {})
.fetch(:serializer, get_serializer_for(resource.class))
options.fetch(:serializer, get_serializer_for(resource.class))
end
end
def self.adapter
adapter_class = case config.adapter
when Symbol
ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
when Class
config.adapter
end
when Symbol
ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
when Class
config.adapter
end
unless adapter_class
valid_adapters = Adapter.constants.map { |klass| ":#{klass.to_s.downcase}" }
raise ArgumentError, "Unknown adapter: #{config.adapter}. Valid adapters are: #{valid_adapters}"
@@ -153,12 +108,12 @@ module ActiveModel
attr_accessor :object, :root, :meta, :meta_key, :scope
def initialize(object, options = {})
@object = object
@options = options
@root = options[:root]
@meta = options[:meta]
@meta_key = options[:meta_key]
@scope = options[:scope]
@object = object
@options = options
@root = options[:root]
@meta = options[:meta]
@meta_key = options[:meta_key]
@scope = options[:scope]
scope_name = options[:scope_name]
if scope_name && !respond_to?(scope_name)
@@ -199,48 +154,10 @@ module ActiveModel
end
end
def each_association(&block)
self.class._associations.dup.each do |name, association_options|
next unless object
association_value = send(name)
serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
if serializer_class
begin
serializer = serializer_class.new(
association_value,
options.except(:serializer).merge(serializer_from_options(association_options))
)
rescue ActiveModel::Serializer::ArraySerializer::NoSerializerError
virtual_value = association_value
virtual_value = virtual_value.as_json if virtual_value.respond_to?(:as_json)
association_options[:association_options][:virtual_value] = virtual_value
end
elsif !association_value.nil? && !association_value.instance_of?(Object)
association_options[:association_options][:virtual_value] = association_value
end
association_key = association_options[:association_options][:key] || name
if block_given?
block.call(association_key, serializer, association_options[:association_options])
end
end
end
def serializer_from_options(options)
opts = {}
serializer = options.fetch(:association_options, {}).fetch(:serializer, nil)
opts[:serializer] = serializer if serializer
opts
end
def self.serializers_cache
@serializers_cache ||= ThreadSafe::Cache.new
end
private
attr_reader :options
def self.get_serializer_for(klass)
@@ -255,6 +172,5 @@ module ActiveModel
end
end
end
end
end