Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Associations refactoring #985

Merged
merged 1 commit into from
Jul 31, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 22 additions & 106 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
module ActiveModel
class Serializer
extend ActiveSupport::Autoload

autoload :Configuration
autoload :ArraySerializer
autoload :Adapter
autoload :Lint
autoload :Associations
include Configuration
include Associations
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


class << self
attr_accessor :_attributes
attr_accessor :_attributes_keys
attr_accessor :_associations
attr_accessor :_urls
attr_accessor :_cache
attr_accessor :_fragmented
Expand All @@ -24,12 +26,12 @@ class << self
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)
Expand All @@ -46,7 +48,7 @@ def self.attributes(*attrs)

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)
Expand All @@ -59,58 +61,13 @@ def self.fragmented(serializer)

# 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
Expand All @@ -125,19 +82,17 @@ def self.serializer_for(resource, options = {})
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}"
Expand All @@ -153,12 +108,12 @@ def self.root_name
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)
Expand Down Expand Up @@ -199,48 +154,10 @@ def attributes(options = {})
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)
Expand All @@ -255,6 +172,5 @@ def self.get_serializer_for(klass)
end
end
end

end
end
28 changes: 15 additions & 13 deletions lib/active_model/serializer/adapter/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,34 @@ class Json < Adapter
def serializable_hash(options = nil)
options ||= {}
if serializer.respond_to?(:each)
@result = serializer.map{|s| FlattenJson.new(s).serializable_hash(options) }
@result = serializer.map { |s| FlattenJson.new(s).serializable_hash(options) }
else
@hash = {}

@core = cache_check(serializer) do
serializer.attributes(options)
end

serializer.each_association do |key, association, opts|
if association.respond_to?(:each)
array_serializer = association
@hash[key] = array_serializer.map do |item|
serializer.associations.each do |association|
serializer = association.serializer
opts = association.options

if serializer.respond_to?(:each)
array_serializer = serializer
@hash[association.key] = array_serializer.map do |item|
cache_check(item) do
item.attributes(opts)
end
end
else
if association && association.object
@hash[key] = cache_check(association) do
association.attributes(options)
@hash[association.key] =
if serializer && serializer.object
cache_check(serializer) do
serializer.attributes(options)
end
elsif opts[:virtual_value]
opts[:virtual_value]
end
elsif opts[:virtual_value]
@hash[key] = opts[:virtual_value]
else
@hash[key] = nil
end
end
end
@result = @core.merge @hash
Expand Down
22 changes: 14 additions & 8 deletions lib/active_model/serializer/adapter/json_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ def add_included(resource_name, serializers, parent = nil)
end

serializers.each do |serializer|
serializer.each_association do |key, association, opts|
add_included(key, association, resource_path) if association
serializer.associations.each do |association|
serializer = association.serializer

add_included(association.key, serializer, resource_path) if serializer
end if include_nested_assoc? resource_path
end
end
Expand Down Expand Up @@ -131,22 +133,26 @@ def check_assoc(assoc)
def add_resource_relationships(attrs, serializer, options = {})
options[:add_included] = options.fetch(:add_included, true)

serializer.each_association do |key, association, opts|
serializer.associations.each do |association|
key = association.key
serializer = association.serializer
opts = association.options

attrs[:relationships] ||= {}

if association.respond_to?(:each)
add_relationships(attrs, key, association)
if serializer.respond_to?(:each)
add_relationships(attrs, key, serializer)
else
if opts[:virtual_value]
add_relationship(attrs, key, nil, opts[:virtual_value])
else
add_relationship(attrs, key, association)
add_relationship(attrs, key, serializer)
end
end

if options[:add_included]
Array(association).each do |association|
add_included(key, association)
Array(serializer).each do |serializer|
add_included(key, serializer)
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions lib/active_model/serializer/association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module ActiveModel
class Serializer
# This class hold all information about serializer's association.
#
# @param [Symbol] name
# @param [ActiveModel::Serializer] serializer
# @param [Hash{Symbol => Object}] options
#
# @example
# Association.new(:comments, CommentSummarySerializer, embed: :ids)
#
Association = Struct.new(:name, :serializer, :options) do

# @return [Symbol]
#
def key
options.fetch(:key, name)
end
end
end
end
Loading