Skip to content
Closed
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
23 changes: 3 additions & 20 deletions lib/active_model/serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ class Serializer
autoload :Configuration
autoload :ArraySerializer
autoload :Adapter
autoload :AttributeMethods
include Configuration
include Serializer::AttributeMethods

class << self
attr_accessor :_attributes
attr_accessor :_associations
attr_accessor :_urls
attr_accessor :_cache
Expand All @@ -18,27 +19,9 @@ class << self
end

def self.inherited(base)
base._attributes = []
base._associations = {}
base._urls = []
end

def self.attributes(*attrs)
@_attributes.concat attrs

attrs.each do |attr|
define_method attr do
object && object.read_attribute_for_serialization(attr)
end unless method_defined?(attr)
end
end

def self.attribute(attr, options = {})
key = options.fetch(:key, attr)
@_attributes.concat [key]
define_method key do
object.read_attribute_for_serialization(attr)
end unless method_defined?(key)
super
end

# Enables a serializer to be automatically cached
Expand Down
86 changes: 86 additions & 0 deletions lib/active_model/serializer/attribute_methods.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
module ActiveModel
class Serializer
module AttributeMethods
extend ActiveSupport::Concern

class Cache
def initialize
@module = Module.new
@method_cache = ThreadSafe::Cache.new
end

def [](name)
@method_cache.compute_if_absent(name) do
safe_name = name.to_s.unpack('h*').first
temp_method = "__temp__#{safe_name}"
ActiveModel::Serializer::AttributeMethods::AttrNames.set_name_cache safe_name, name
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
@module.instance_method temp_method
end
end

private

def method_body(method_name, const_name)
<<-EOMETHOD
def #{method_name}
name = ::ActiveModel::Serializer::AttributeMethods::AttrNames::ATTR_#{const_name}
object && object.read_attribute_for_serialization(name)
end
EOMETHOD
end
end

MethodCache = Cache.new

module AttrNames
def self.set_name_cache(name, value)
const_name = "ATTR_#{name}"
unless const_defined? const_name
const_set const_name, value.duplicable? ? value.dup.freeze : value
end
end
end

module ClassMethods
attr_accessor :_attributes

def inherited(base)
base._attributes = []
end

def attributes(*attrs)
@_attributes.concat attrs

attrs.each do |attr|
unless generated_attribute_methods.method_defined?(attr)
generated_attribute_methods.module_exec do
define_method(attr, MethodCache[attr])
end
end
end
end

def attribute(attr, options = {})
key = options.fetch(:key, attr)
@_attributes << key
unless generated_attribute_methods.method_defined?(key)
generated_attribute_methods.module_exec do
define_method(key, MethodCache[attr])
end
end
end

protected

private

def generated_attribute_methods #:nodoc:
@generated_attribute_methods ||= Module.new {
extend Mutex_m
}.tap { |mod| include mod }
end
end
end
end
end