Skip to content

Commit

Permalink
Merge pull request formtastic#784 from SaschaKonietzke/cache-i18n-loo…
Browse files Browse the repository at this point in the history
…kups

Performance Improvement formtastic#2: Cache I18n lookups in Formtastic::Localizer (formtastic#744)
  • Loading branch information
justinfrench committed Feb 2, 2012
2 parents cb4002d + d24cbbc commit 6bbec6c
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 7 deletions.
1 change: 1 addition & 0 deletions lib/formtastic/form_builder.rb
Expand Up @@ -23,6 +23,7 @@ def self.configure(name, value = nil)
configure :file_metadata_suffixes, ['content_type', 'file_name', 'file_size']
configure :priority_countries, ["Australia", "Canada", "United Kingdom", "United States"]
configure :i18n_lookups_by_default, true
configure :i18n_cache_lookups, true
configure :i18n_localizer, Formtastic::Localizer
configure :escape_html_entities_in_hints_and_labels, true
configure :default_commit_button_accesskey
Expand Down
52 changes: 46 additions & 6 deletions lib/formtastic/localizer.rb
Expand Up @@ -24,27 +24,59 @@ module Formtastic
# 'formtastic.labels.post.title'
# 'formtastic.labels.title'
class Localizer

class Cache
def get(key)
cache[key]
end

def has_key?(key)
cache.has_key?(key)
end

def set(key, result)
cache[key] = result
end

def cache
@cache ||= {}
end

def clear!
cache.clear
end
end

attr_accessor :builder


def self.cache
@cache ||= Cache.new
end

def initialize(current_builder)
self.builder = current_builder
end

def localize(key, value, type, options = {}) #:nodoc:
key = value if value.is_a?(::Symbol)

if value.is_a?(::String)
escape_html_entities(value)
else
use_i18n = value.nil? ? i18n_lookups_by_default : (value != false)

use_cache = i18n_cache_lookups
cache = self.class.cache

if use_i18n
model_name, nested_model_name = normalize_model_name(builder.model_name.underscore)

action_name = builder.template.params[:action].to_s rescue ''
attribute_name = key.to_s


# look in the cache first
if use_cache
cache_key = [::I18n.locale, action_name, model_name, nested_model_name, attribute_name, key, value, type, options]
return cache.get(cache_key) if cache.has_key?(cache_key)
end

defaults = Formtastic::I18n::SCOPES.reject do |i18n_scope|
nested_model_name.nil? && i18n_scope.match(/nested_model/)
Expand All @@ -71,7 +103,11 @@ def localize(key, value, type, options = {}) #:nodoc:
options[:default] = defaults
i18n_value = ::I18n.t(default_key, options)
end
(i18n_value.is_a?(::String) && i18n_value.present?) ? escape_html_entities(i18n_value) : nil

# save the result to the cache
result = (i18n_value.is_a?(::String) && i18n_value.present?) ? escape_html_entities(i18n_value) : nil
cache.set(cache_key, result) if use_cache
result
end
end
end
Expand Down Expand Up @@ -105,6 +141,10 @@ def escape_html_entities(string) #:nodoc:
def i18n_lookups_by_default
builder.i18n_lookups_by_default
end

def i18n_cache_lookups
builder.i18n_cache_lookups
end

end
end
4 changes: 4 additions & 0 deletions lib/generators/templates/formtastic.rb
Expand Up @@ -68,6 +68,10 @@
# i.e. :label => true, or :hint => true (or opposite depending on initialized value)
# Formtastic::FormBuilder.i18n_lookups_by_default = false

# Specifies if I18n lookups of the default I18n Localizer should be cached to improve performance.
# Defaults to false.
# Formtastic::FormBuilder.i18n_cache_lookups = true

# Specifies the class to use for localization lookups. You can create your own
# class and use it instead by subclassing Formtastic::Localizer (which is the default).
# Formtastic::FormBuilder.i18n_localizer = MyOwnLocalizer
Expand Down
1 change: 1 addition & 0 deletions spec/actions/generic_action_spec.rb
Expand Up @@ -213,6 +213,7 @@ def to_html
}
}
}

concat(semantic_form_for(:post, :url => 'http://example.com') do |builder|
concat(builder.action(:submit, :as => :generic))
concat(builder.action(:reset, :as => :generic))
Expand Down
108 changes: 108 additions & 0 deletions spec/localizer_spec.rb
@@ -0,0 +1,108 @@
# encoding: utf-8
require 'spec_helper'

describe 'Formtastic::Localizer' do
describe "Cache" do
before do
@cache = Formtastic::Localizer::Cache.new
@key = ['model', 'name']
@undefined_key = ['model', 'undefined']
@cache.set(@key, 'value')
end

it "should get value" do
@cache.get(@key).should == 'value'
@cache.get(@undefined_key).should be_nil
end

it "should check if key exists?" do
@cache.has_key?(@key).should be_true
@cache.has_key?(@undefined_key).should be_false
end

it "should set a key" do
@cache.set(['model', 'name2'], 'value2')
@cache.get(['model', 'name2']).should == 'value2'
end

it "should return hash" do
@cache.cache.should be_an_instance_of(Hash)
end

it "should clear the cache" do
@cache.clear!
@cache.get(@key).should be_nil
end
end

describe "Localizer" do
include FormtasticSpecHelper

before do
mock_everything

with_config :i18n_lookups_by_default, true do
semantic_form_for(@new_post) do |builder|
@localizer = Formtastic::Localizer.new(builder)
end
end
end

after do
::I18n.backend.reload!
end

it "should be defined" do
lambda { Formtastic::Localizer }.should_not raise_error(::NameError)
end

it "should have a cache" do
Formtastic::Localizer.cache.should be_an_instance_of(Formtastic::Localizer::Cache)
end

describe "localize" do
def store_post_translations(value)
::I18n.backend.store_translations :en, {:formtastic => {
:labels => {
:post => { :name => value }
}
}
}
end

before do
store_post_translations('POST.NAME')
end

it "should translate key with i18n" do
@localizer.localize(:name, :name, :label).should == 'POST.NAME'
end

describe "with caching" do
it "should not update translation when stored translations change" do
with_config :i18n_cache_lookups, true do
@localizer.localize(:name, :name, :label).should == 'POST.NAME'
store_post_translations('POST.NEW_NAME')

@localizer.localize(:name, :name, :label).should == 'POST.NAME'

Formtastic::Localizer.cache.clear!
@localizer.localize(:name, :name, :label).should == 'POST.NEW_NAME'
end
end
end

describe "without caching" do
it "should update translation when stored translations change" do
with_config :i18n_cache_lookups, false do
@localizer.localize(:name, :name, :label).should == 'POST.NAME'
store_post_translations('POST.NEW_NAME')
@localizer.localize(:name, :name, :label).should == 'POST.NEW_NAME'
end
end
end
end

end

end
6 changes: 5 additions & 1 deletion spec/spec_helper.rb
Expand Up @@ -419,10 +419,14 @@ def with_config(config_method_name, value, &block)
::ActiveSupport::Deprecation.silenced = false

RSpec.configure do |config|
config.before(:each) do
Formtastic::Localizer.cache.clear!
end

config.before(:all) do
DeferredGarbageCollection.start unless ENV["DEFER_GC"] == "false"
end
config.after(:all) do
DeferredGarbageCollection.reconsider unless ENV["DEFER_GC"] == "false"
DeferredGarbageCollection.reconsider unless ENV["DEFER_GC"] == "false"
end
end

0 comments on commit 6bbec6c

Please sign in to comment.