Skip to content

Commit 6369b04

Browse files
committed
Implement a method cache for faster dispatch and allowing super calls
1 parent 9cf45e7 commit 6369b04

File tree

3 files changed

+90
-20
lines changed

3 files changed

+90
-20
lines changed

lib/active_model/serializer.rb

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@ class Serializer
66
autoload :Configuration
77
autoload :ArraySerializer
88
autoload :Adapter
9+
autoload :AttributeMethods
910
include Configuration
11+
include Serializer::AttributeMethods
1012

1113
class << self
12-
attr_accessor :_attributes
1314
attr_accessor :_associations
1415
attr_accessor :_urls
1516
attr_accessor :_cache
@@ -18,27 +19,9 @@ class << self
1819
end
1920

2021
def self.inherited(base)
21-
base._attributes = []
2222
base._associations = {}
2323
base._urls = []
24-
end
25-
26-
def self.attributes(*attrs)
27-
@_attributes.concat attrs
28-
29-
attrs.each do |attr|
30-
define_method attr do
31-
object && object.read_attribute_for_serialization(attr)
32-
end unless method_defined?(attr)
33-
end
34-
end
35-
36-
def self.attribute(attr, options = {})
37-
key = options.fetch(:key, attr)
38-
@_attributes.concat [key]
39-
define_method key do
40-
object.read_attribute_for_serialization(attr)
41-
end unless method_defined?(key)
24+
super
4225
end
4326

4427
# Enables a serializer to be automatically cached
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
module ActiveModel
2+
class Serializer
3+
module AttributeMethods
4+
extend ActiveSupport::Concern
5+
6+
class Cache
7+
def initialize
8+
@module = Module.new
9+
@method_cache = ThreadSafe::Cache.new
10+
end
11+
12+
def [](name)
13+
@method_cache.compute_if_absent(name) do
14+
safe_name = name.to_s.unpack('h*').first
15+
temp_method = "__temp__#{safe_name}"
16+
ActiveModel::Serializer::AttributeMethods::AttrNames.set_name_cache safe_name, name
17+
@module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__
18+
@module.instance_method temp_method
19+
end
20+
end
21+
22+
private
23+
24+
def method_body(method_name, const_name)
25+
<<-EOMETHOD
26+
def #{method_name}
27+
name = ::ActiveModel::Serializer::AttributeMethods::AttrNames::ATTR_#{const_name}
28+
object && object.read_attribute_for_serialization(name)
29+
end
30+
EOMETHOD
31+
end
32+
end
33+
34+
MethodCache = Cache.new
35+
36+
module AttrNames
37+
def self.set_name_cache(name, value)
38+
const_name = "ATTR_#{name}"
39+
unless const_defined? const_name
40+
const_set const_name, value.duplicable? ? value.dup.freeze : value
41+
end
42+
end
43+
end
44+
45+
module ClassMethods
46+
attr_accessor :_attributes
47+
48+
def inherited(base)
49+
base._attributes = []
50+
end
51+
52+
def attributes(*attrs)
53+
@_attributes.concat attrs
54+
55+
attrs.each do |attr|
56+
unless generated_attribute_methods.method_defined?(attr)
57+
generated_attribute_methods.module_exec do
58+
define_method(attr, MethodCache[attr])
59+
end
60+
end
61+
end
62+
end
63+
64+
def attribute(attr, options = {})
65+
key = options.fetch(:key, attr)
66+
@_attributes.concat [key]
67+
unless generated_attribute_methods.method_defined?(key)
68+
generated_attribute_methods.module_exec do
69+
define_method(key, MethodCache[attr])
70+
end
71+
end
72+
end
73+
74+
protected
75+
76+
private
77+
78+
def generated_attribute_methods #:nodoc:
79+
@generated_attribute_methods ||= Module.new {
80+
extend Mutex_m
81+
}.tap { |mod| include mod }
82+
end
83+
end
84+
end
85+
end
86+
end

lib/active_model_serializers.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'active_model'
22
require 'active_model/serializer/version'
3+
require 'active_model/serializer/attribute_methods'
34
require 'active_model/serializer'
45
require 'active_model/serializer/fieldset'
56

0 commit comments

Comments
 (0)