Permalink
Browse files

When rendering a collection, attempt to use read_multi to fetch the c…

…ached items in parallel, potentially decreasing time to response.
  • Loading branch information...
ahlatimer committed Mar 4, 2013
1 parent a10c9ec commit 95875ee7cb872d107a090f053b8e77d9e98719e1
Showing with 83 additions and 1 deletion.
  1. +7 −0 lib/rabl/cache_engine.rb
  2. +61 −1 lib/rabl/engine.rb
  3. +15 −0 lib/rabl/partials.rb
View
@@ -21,5 +21,12 @@ def fetch(key, cache_options, &block)
end
end
+ def read_multi(keys, cache_options = {})
+ if defined?(Rails)
+ Rails.cache.read_multi(keys, cache_options)
+ else
+ keys.inject({}) { |hash, key| hash[key] = nil; hash }
+ end
+ end
end
end
View
@@ -37,6 +37,23 @@ def render(scope, locals, &block)
cache_results { self.send("to_" + @_options[:format].to_s) }
end
+ def apply_without_rendering(scope, locals, &block)
+ reset_options!
+ @_locals, @_scope = locals, scope
+ self.copy_instance_variables_from(@_scope, [:@assigns, :@helpers])
+ @_options[:scope] = @_scope
+ @_options[:format] ||= self.request_format
+ data = locals[:object].nil? ? self.default_object : locals[:object]
+ @_data_object, @_data_name = data_object(data), data_name(data)
+ if @_options[:source_location]
+ instance_eval(@_source, @_options[:source_location]) if @_source.present?
+ else # without source location
+ instance_eval(@_source) if @_source.present?
+ end
+ instance_exec(data_object(@_data), &block) if block_given?
+ self
+ end
+
# Returns a hash representation of the data object
# to_hash(:root => true, :child_root => true)
def to_hash(options={})
@@ -48,7 +65,45 @@ def to_hash(options={})
if is_object?(data) || !data # object @user
builder.build(data, options)
elsif is_collection?(data) # collection @users
- data.map { |object| builder.build(object, options) }
+ if options[:read_multi] && template_cache_configured?
+ fetch_results_from_cache(builder, data, options)
+ else
+ data.map { |object| builder.build(object, options) }
+ end
+ end
+ end
+
+ def fetch_results_from_cache(builder, data, options)
+ key_to_object = data.inject({}) do |hash, object|
+ cache_key = if options.has_key?(:extends)
+ settings = options[:extends].last
+ settings[:options] = options.slice(:child_root).merge(:object => object).merge(settings[:options])
+ engine = self.partial_without_rendering(settings[:file], settings[:options], &settings[:block])
+ cache_key, _ = *engine.instance_variable_get(:"@_cache")
+ Array(cache_key) + [options[:root_name], options[:format]]
+ else
+ [object, options[:root_name], options[:format]]
+ end
+ result_cache_key = ActiveSupport::Cache.expand_cache_key(cache_key, :rabl)
+ hash[result_cache_key] = object
+ hash
+ end
+
+ mutable_keys = key_to_object.keys.map { |k| k.dup }
+ result_hash = Rabl.configuration.cache_engine.read_multi(mutable_keys)
+
+ result_hash.each do |key ,value|
+ if value
+ key_to_object[key] = value
+ end
+ end
+
+ key_to_object.map do |key, object|
+ if object.is_a?(Hash)
+ object
+ else
+ builder.build(object, options)
+ end
end
end
@@ -192,6 +247,10 @@ def extends(file, options={}, &block)
@_options[:extends].push({ :file => file, :options => extend_ops, :block => block })
end
+ def read_multi(val)
+ @_options[:read_multi] = val
+ end
+
# Includes a helper module with a RABL template
# helper ExampleHelper
def helper(*klazzes)
@@ -263,6 +322,7 @@ def reset_options!
@_options[:glue] = []
@_options[:extends] = []
@_options[:root_name] = nil
+ @_options[:read_multi] = false
end
# Caches the results of the block based on object cache_key
View
@@ -14,6 +14,14 @@ def partial(file, options={}, &block)
self.object_to_hash(object, engine_options, &block)
end
+ def partial_without_rendering(file, options={}, &block)
+ raise ArgumentError, "Must provide an :object option to render a partial" unless options.has_key?(:object)
+ object, view_path = options.delete(:object), options[:view_path] || @_view_path
+ source, location = self.fetch_source(file, :view_path => view_path)
+ engine_options = options.merge(:source => source, :source_location => location)
+ self.object_to_engine(object, engine_options, &block)
+ end
+
# Returns a hash based representation of any data object given ejs template block
# object_to_hash(@user) { attribute :full_name } => { ... }
# object_to_hash(@user, :source => "...") { attribute :full_name } => { ... }
@@ -27,6 +35,13 @@ def object_to_hash(object, options={}, &block)
Rabl::Engine.new(options[:source], engine_options).render(@_scope, :object => object, &block)
end
+ def object_to_engine(object, options={}, &block)
+ return object unless is_object?(object) || is_collection?(object)
+ return [] if is_collection?(object) && object.blank? # empty collection
+ engine_options = options.reverse_merge(:format => "hash", :view_path => @_view_path, :root => (options[:root] || false))
+ Rabl::Engine.new(options[:source], engine_options).apply_without_rendering(@_scope, :object => object, &block)
+ end
+
# Returns source for a given relative file
# fetch_source("show", :view_path => "...") => "...contents..."
def fetch_source(file, options={})

0 comments on commit 95875ee

Please sign in to comment.