Skip to content

Commit

Permalink
Associations refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmitriy Myaskovskiy committed Jul 6, 2015
1 parent c50b355 commit 7b27cc2
Show file tree
Hide file tree
Showing 9 changed files with 108 additions and 53 deletions.
76 changes: 43 additions & 33 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
module ActiveModel
class Serializer
extend ActiveSupport::Autoload

autoload :Configuration
autoload :ArraySerializer
autoload :Adapter
include Configuration

class Association < Struct.new(:name, :serializer, :options)
autoload :Association
autoload :Reflection
autoload :SingularReflection
autoload :CollectionReflection
autoload :BelongsToReflection
autoload :HasOneReflection
autoload :HasManyReflection

end
include Configuration

class << self
attr_accessor :_attributes
attr_accessor :_attributes_keys
attr_accessor :_associations
attr_accessor :_reflections
attr_accessor :_urls
attr_accessor :_cache
attr_accessor :_fragmented
Expand All @@ -29,7 +34,7 @@ class << self
def self.inherited(base)
base._attributes = self._attributes.try(:dup) || []
base._attributes_keys = self._attributes_keys.try(:dup) || {}
base._associations = self._associations.try(:dup) || {}
base._reflections = self._reflections.try(:dup) || []
base._urls = []
serializer_file = File.open(caller.first[/^[^:]+/])
base._cache_digest = Digest::MD5.hexdigest(serializer_file.read)
Expand Down Expand Up @@ -76,7 +81,9 @@ def self.cache(options = {})
# 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)
associate attrs do |name, options|
HasManyReflection.new(name, options)
end
end

# Defines an association in the object that should be rendered.
Expand All @@ -86,7 +93,9 @@ def self.has_many(*attrs)
# 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)
associate attrs do |name, options|
BelongsToReflection.new(name, options)
end
end

# Defines an association in the object should be rendered.
Expand All @@ -96,21 +105,24 @@ def self.belongs_to(*attrs)
# 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)
associate attrs do |name, options|
HasOneReflection.new(name, options)
end
end

def self.associate(type, attrs) #:nodoc:
# has_one :author, :image, only: :object
def self.associate(attrs, &block) #:nodoc:
options = attrs.extract_options!
self._associations = _associations.dup
self._reflections = _reflections.dup

attrs.each do |attr|
unless method_defined?(attr)
define_method attr do
object.send attr
attrs.each do |name|
unless method_defined?(name)
define_method name do
object.send name
end
end

self._associations[attr] = { type: type, association_options: options }
self._reflections << block.call(name, options)
end
end

Expand All @@ -128,9 +140,7 @@ 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

Expand Down Expand Up @@ -207,44 +217,44 @@ def associations
return unless object

Enumerator.new do |y|
self.class._associations.dup.each do |name, association_options|
self.class._reflections.dup.each do |reflection|
name = reflection.name
association_value = send(name)

serializer_class = ActiveModel::Serializer.serializer_for(association_value, association_options)
serializer_class = ActiveModel::Serializer.serializer_for(association_value, reflection.options)

if serializer_class
begin
serializer = serializer_class.new(
association_value,
options.except(:serializer).merge(serializer_from_options(association_options))
options.except(:serializer).merge(serializer_from_options(reflection.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
reflection.options[:virtual_value] = virtual_value
end
elsif !association_value.nil? && !association_value.instance_of?(Object)
association_options[:association_options][:virtual_value] = association_value
reflection.options[:virtual_value] = association_value
end

y.yield Association.new(name, serializer, association_options[:association_options])
y.yield Association.new(name, serializer, reflection.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

def serializer_from_options(options)
opts = {}
serializer = options.fetch(:serializer, nil)
opts[:serializer] = serializer if serializer
opts
end

attr_reader :options

def self.get_serializer_for(klass)
Expand Down
5 changes: 5 additions & 0 deletions lib/active_model/serializer/association.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module ActiveModel
class Serializer
Association = Struct.new(:name, :serializer, :options)
end
end
9 changes: 9 additions & 0 deletions lib/active_model/serializer/belongs_to_reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ActiveModel
class Serializer
class BelongsToReflection < SingularReflection
def macro
:belongs_to
end
end
end
end
6 changes: 6 additions & 0 deletions lib/active_model/serializer/collection_reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module ActiveModel
class Serializer
class CollectionReflection < Reflection
end
end
end
9 changes: 9 additions & 0 deletions lib/active_model/serializer/has_many_reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ActiveModel
class Serializer
class HasManyReflection < CollectionReflection
def macro
:has_many
end
end
end
end
9 changes: 9 additions & 0 deletions lib/active_model/serializer/has_one_reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module ActiveModel
class Serializer
class HasOneReflection < SingularReflection
def macro
:has_one
end
end
end
end
7 changes: 7 additions & 0 deletions lib/active_model/serializer/reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module ActiveModel
class Serializer
# Holds all the meta-data about an association as it was specified in the
# ActiveModel::Serializer class.
Reflection = Struct.new(:name, :options)
end
end
6 changes: 6 additions & 0 deletions lib/active_model/serializer/singular_reflection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module ActiveModel
class Serializer
class SingularReflection < Reflection
end
end
end
34 changes: 14 additions & 20 deletions test/serializers/associations_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,6 @@ def setup
end

def test_has_many_and_has_one
assert_equal(
{ posts: { type: :has_many, association_options: { embed: :ids } },
roles: { type: :has_many, association_options: { embed: :ids } },
bio: { type: :has_one, association_options: {} } },
@author_serializer.class._associations
)
@author_serializer.associations.each do |association|
name = association.name
serializer = association.serializer
Expand Down Expand Up @@ -90,17 +84,14 @@ def test_serializer_options_are_passed_into_associations_serializers
serializer = association.serializer

if name == :comments
puts serializer.first

assert serializer.first.custom_options[:custom_options]
end
end
end

def test_belongs_to
assert_equal(
{ post: { type: :belongs_to, association_options: {} },
author: { type: :belongs_to, association_options: {} } },
@comment_serializer.class._associations
)
@comment_serializer.associations.each do |association|
name = association.name
serializer = association.serializer
Expand Down Expand Up @@ -130,22 +121,25 @@ def test_belongs_to_with_custom_method
def test_associations_inheritance
inherited_klass = Class.new(PostSerializer)

assert_equal(PostSerializer._associations, inherited_klass._associations)
assert_equal(PostSerializer._reflections, inherited_klass._reflections)
end

def test_associations_inheritance_with_new_association
inherited_klass = Class.new(PostSerializer) do
has_many :top_comments, serializer: CommentSerializer
end
expected_associations = PostSerializer._associations.merge(
top_comments: {
type: :has_many,
association_options: {
serializer: CommentSerializer
}
}

assert(
PostSerializer._reflections.all? do |reflection|
inherited_klass._reflections.include?(reflection)
end
)

assert(
inherited_klass._reflections.any? do |reflection|
reflection.name == :top_comments
end
)
assert_equal(inherited_klass._associations, expected_associations)
end
end
end
Expand Down

0 comments on commit 7b27cc2

Please sign in to comment.