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

Caching improvements #298

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ source "https://rubygems.org"
gemspec

gem "rake"
gem "rails"

Choose a reason for hiding this comment

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

Is this an oversight? We shouldn't need to bring in the entire Rails gem into jbuilder, right?

gem "mocha", require: false
gem "appraisal"
gem "pry"
24 changes: 12 additions & 12 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,22 @@ require "bundler/setup"
require "bundler/gem_tasks"
require "rake/testtask"

Rake::TestTask.new do |test|
require "rails/version"

test.libs << "test"

if Rails::VERSION::MAJOR == 3
test.test_files = %w[test/jbuilder_template_test.rb test/jbuilder_test.rb]
else
test.test_files = FileList["test/*_test.rb"]
end
end

if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
require "appraisal/task"
Appraisal::Task.new
task default: :appraisal
else
Rake::TestTask.new do |test|
require "rails/version"

test.libs << "test"

if Rails::VERSION::MAJOR == 3
test.test_files = %w[test/jbuilder_template_test.rb test/jbuilder_test.rb]
else
test.test_files = FileList["test/*_test.rb"]
end
end

task default: :test
end
52 changes: 49 additions & 3 deletions lib/jbuilder/jbuilder_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class << self

def initialize(context, *args)
@context = context
@deferred_caches = {}

super(*args)
end

Expand All @@ -32,11 +34,22 @@ def partial!(*args)
# end
def cache!(key=nil, options={})
if @context.controller.perform_caching
value = ::Rails.cache.fetch(_cache_key(key, options), options) do
_scope { yield self }
token = "jbuilder-#{::SecureRandom.hex(8)}"
key = _cache_key key, options

# Convert yielded block to a Proc we can call later if needed.
not_found = ::Proc.new

fetcher = ::Proc.new do
::Rails.cache.fetch(key, options) do
value = _scope { not_found.call self }
::MultiJson.dump value
end
end

merge! value
@deferred_caches[token] = fetcher

merge! token => nil
else
yield
end
Expand Down Expand Up @@ -75,6 +88,39 @@ def set!(name, object = BLANK, *args)
end
end

def target!
# Call the superclass implementation to get the output JSON as a string.
output = super

@deferred_caches.each do |token, fetch_block|
value = fetch_block.call
search = "\"#{token}\":null"

if value == "{}".freeze
# Special case to handle empty objects: we remove the search key
# entirely from the output.
search = ::Regexp.new ',?' + ::Regexp.escape(search)
value = "".freeze
end

if value.start_with? "[".freeze
# Special case to handle arrays: we actually have to replace the
# object with the array.
search = "{#{search}}"
end

if value.start_with? "{".freeze
# Remove leading and trailing braces so it'll merge seamlessly into
# the surrounding object.
value = value.slice 1...-1
end

output.sub! search, value
end

output
end

private

def _render_partial_with_options(options)
Expand Down