aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/CHANGELOG.md38
-rw-r--r--actionpack/lib/action_controller/test_case.rb15
-rw-r--r--actionpack/test/controller/action_pack_assertions_test.rb22
-rw-r--r--actionpack/test/fixtures/test/hello/hello.erb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb8
-rw-r--r--activesupport/CHANGELOG.md2
-rw-r--r--activesupport/lib/active_support/cache.rb148
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb1
-rw-r--r--activesupport/test/caching_test.rb91
-rw-r--r--activesupport/test/test_test.rb4
10 files changed, 215 insertions, 115 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 248677688f..7fd6cfa859 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,5 +1,38 @@
## Rails 4.0.0 (unreleased) ##
+* Precompiled assets include aliases from foo.js to foo/index.js and vice versa.
+
+ # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css.
+ config.assets.precompile = [ 'phone.css' ]
+
+ # Precompiles phone/index-<digest>.css and aliases phone.css to phone/index.css.
+ config.assets.precompile = [ 'phone/index.css' ]
+
+ # Both of these work with either precompile thanks to their aliases.
+ <%= stylesheet_link_tag 'phone', media: 'all' %>
+ <%= stylesheet_link_tag 'phone/index', media: 'all' %>
+
+ *Jeremy Kemper*
+
+* `assert_template` is no more passing with what ever string that matches
+ with the template name.
+
+ Before when we have a template `/layout/hello.html.erb`, `assert_template`
+ was passing with any string that matches. This behavior allowed false
+ positive like:
+
+ assert_template "layout"
+ assert_template "out/hello"
+
+ Now it only passes with:
+
+ assert_template "layout/hello"
+ assert_template "hello"
+
+ Fixes #3849.
+
+ *Hugolnx*
+
* `image_tag` will set the same width and height for image if numerical value
passed to `size` option.
@@ -44,9 +77,8 @@
*Luiz Felipe Garcia Pereira*
-* Sprockets integration has been extracted from Action Pack and the `sprockets-rails`
- gem should be added to Gemfile (under the assets group) in order to use Rails asset
- pipeline in future versions of Rails.
+* Sprockets integration has been extracted from Action Pack to the `sprockets-rails`
+ gem. `rails` gem is depending on `sprockets-rails` by default.
*Guillermo Iguaran*
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index 0caeef3192..20c6e2bf53 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -86,16 +86,23 @@ module ActionController
response.body
case options
- when NilClass, String, Symbol, Regexp
+ when NilClass, Regexp, String, Symbol
options = options.to_s if Symbol === options
rendered = @templates
msg = message || sprintf("expecting <%s> but rendering with <%s>",
options.inspect, rendered.keys)
matches_template =
- if options
+ case options
+ when String
+ rendered.any? do |t, num|
+ options_splited = options.split(File::SEPARATOR)
+ t_splited = t.split(File::SEPARATOR)
+ t_splited.last(options_splited.size) == options_splited
+ end
+ when Regexp
rendered.any? { |t,num| t.match(options) }
- else
- @templates.blank?
+ when NilClass
+ rendered.blank?
end
assert matches_template, msg
when Hash
diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb
index 4ab5d92a2b..ca542eb7e2 100644
--- a/actionpack/test/controller/action_pack_assertions_test.rb
+++ b/actionpack/test/controller/action_pack_assertions_test.rb
@@ -7,6 +7,7 @@ class ActionPackAssertionsController < ActionController::Base
def nothing() head :ok end
def hello_world() render :template => "test/hello_world"; end
+ def hello_repeating_in_path() render :template => "test/hello/hello"; end
def hello_xml_world() render :template => "test/hello_xml_world"; end
@@ -464,6 +465,20 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_fails_with_incorrect_string_that_matches
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template 'est/he'
+ end
+ end
+
+ def test_fails_with_repeated_name_in_path
+ get :hello_repeating_in_path
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template 'test/hello'
+ end
+ end
+
def test_fails_with_incorrect_symbol
get :hello_world
assert_raise(ActiveSupport::TestCase::Assertion) do
@@ -471,6 +486,13 @@ class AssertTemplateTest < ActionController::TestCase
end
end
+ def test_fails_with_incorrect_symbol_that_matches
+ get :hello_world
+ assert_raise(ActiveSupport::TestCase::Assertion) do
+ assert_template :"est/he"
+ end
+ end
+
def test_fails_with_wrong_layout
get :render_with_layout
assert_raise(ActiveSupport::TestCase::Assertion) do
diff --git a/actionpack/test/fixtures/test/hello/hello.erb b/actionpack/test/fixtures/test/hello/hello.erb
new file mode 100644
index 0000000000..6769dd60bd
--- /dev/null
+++ b/actionpack/test/fixtures/test/hello/hello.erb
@@ -0,0 +1 @@
+Hello world! \ No newline at end of file
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index f3620c1324..5dece1cb36 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -64,14 +64,12 @@ module ActiveRecord
end
def build_relation(klass, table, attribute, value) #:nodoc:
- reflection = klass.reflect_on_association(attribute)
- if reflection
- column = klass.columns_hash[reflection.foreign_key]
+ if reflection = klass.reflect_on_association(attribute)
attribute = reflection.foreign_key
value = value.attributes[reflection.primary_key_column.name]
- else
- column = klass.columns_hash[attribute.to_s]
end
+
+ column = klass.columns_hash[attribute.to_s]
value = column.limit ? value.to_s[0, column.limit] : value.to_s if !value.nil? && column.text?
if !options[:case_sensitive] && value && column.text?
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 2df3d1f69b..70d9ec5613 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -237,4 +237,6 @@
* Remove deprecated ActiveSupport::JSON::Variable. *Erich Menge*
+* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. *Brian Durand*
+
Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index f98ba16cdd..a681a8d6a9 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -284,7 +284,9 @@ module ActiveSupport
end
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_i
- if race_ttl and Time.now.to_f - entry.expires_at <= race_ttl
+ if race_ttl && (Time.now - entry.expires_at <= race_ttl)
+ # When an entry has :race_condition_ttl defined, put the stale entry back into the cache
+ # for a brief period while the entry is begin recalculated.
entry.expires_at = Time.now + race_ttl
write_entry(key, entry, :expires_in => race_ttl * 2)
else
@@ -532,102 +534,122 @@ module ActiveSupport
end
end
- # Entry that is put into caches. It supports expiration time on entries and
- # can compress values to save space in the cache.
- class Entry
- attr_reader :created_at, :expires_in
-
+ # This class is used to represent cache entries. Cache entries have a value and an optional
+ # expiration time. The expiration time is used to support the :race_condition_ttl option
+ # on the cache.
+ #
+ # Since cache entries in most instances will be serialized, the internals of this class are highly optimized
+ # using short instance variable names that are lazily defined.
+ class Entry # :nodoc:
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
- class << self
- # Create an entry with internal attributes set. This method is intended
- # to be used by implementations that store cache entries in a native
- # format instead of as serialized Ruby objects.
- def create(raw_value, created_at, options = {})
- entry = new(nil)
- entry.instance_variable_set(:@value, raw_value)
- entry.instance_variable_set(:@created_at, created_at.to_f)
- entry.instance_variable_set(:@compressed, options[:compressed])
- entry.instance_variable_set(:@expires_in, options[:expires_in])
- entry
- end
- end
-
# Create a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
- @compressed = false
- @expires_in = options[:expires_in]
- @expires_in = @expires_in.to_f if @expires_in
- @created_at = Time.now.to_f
- if value.nil?
- @value = nil
+ if should_compress?(value, options)
+ @v = compress(value)
+ @c = true
else
- @value = Marshal.dump(value)
- if should_compress?(@value, options)
- @value = Zlib::Deflate.deflate(@value)
- @compressed = true
- end
+ @v = value
end
- end
-
- # Get the raw value. This value may be serialized and compressed.
- def raw_value
- @value
- end
-
- # Get the value stored in the cache.
- def value
- # If the original value was exactly false @value is still true because
- # it is marshalled and eventually compressed. Both operations yield
- # strings.
- if @value
- Marshal.load(compressed? ? Zlib::Inflate.inflate(@value) : @value)
+ if expires_in = options[:expires_in]
+ @x = (Time.now + expires_in).to_i
end
end
- def compressed?
- @compressed
+ def value
+ convert_version_3_entry! if defined?(@value)
+ compressed? ? uncompress(@v) : @v
end
# Check if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
- @expires_in && @created_at + @expires_in <= Time.now.to_f
- end
-
- # Set a new time when the entry will expire.
- def expires_at=(time)
- if time
- @expires_in = time.to_f - @created_at
+ convert_version_3_entry! if defined?(@value)
+ if defined?(@x)
+ @x && @x < Time.now.to_i
else
- @expires_in = nil
+ false
end
end
- # Seconds since the epoch when the entry will expire.
def expires_at
- @expires_in ? @created_at + @expires_in : nil
+ Time.at(@x) if defined?(@x)
+ end
+
+ def expires_at=(value)
+ @x = value.to_i
end
# Returns the size of the cached value. This could be less than
# <tt>value.size</tt> if the data is compressed.
def size
- if @value.nil?
- 0
+ if defined?(@s)
+ @s
else
- @value.bytesize
+ case value
+ when NilClass
+ 0
+ when String
+ value.bytesize
+ else
+ @s = Marshal.dump(value).bytesize
+ end
+ end
+ end
+
+ # Duplicate the value in a class. This is used by cache implementations that don't natively
+ # serialize entries to protect against accidental cache modifications.
+ def dup_value!
+ convert_version_3_entry! if defined?(@value)
+ if @v && !compressed? && !(@v.is_a?(Numeric) || @v == true || @v == false)
+ if @v.is_a?(String)
+ @v = @v.dup
+ else
+ @v = Marshal.load(Marshal.dump(@v))
+ end
end
end
private
- def should_compress?(serialized_value, options)
- if options[:compress]
+ def should_compress?(value, options)
+ if value && options[:compress]
compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT
- return true if serialized_value.size >= compress_threshold
+ serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize
+ return true if serialized_value_size >= compress_threshold
end
false
end
+
+ def compressed?
+ defined?(@c) ? @c : false
+ end
+
+ def compress(value)
+ Zlib::Deflate.deflate(Marshal.dump(value))
+ end
+
+ def uncompress(value)
+ Marshal.load(Zlib::Inflate.inflate(value))
+ end
+
+ # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue
+ # to ensure that cache entries created under the old version still work with the new class definition.
+ def convert_version_3_entry!
+ if defined?(@value)
+ @v = @value
+ remove_instance_variable(:@value)
+ end
+ if defined?(@compressed)
+ @c = @compressed
+ remove_instance_variable(:@compressed)
+ end
+ if defined?(@expires_in) && defined?(@created_at)
+ @x = (@created_at + @expires_in).to_i
+ remove_instance_variable(:@created_at)
+ remove_instance_variable(:@expires_in)
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index 7fd5e3b53d..4d26fb7e42 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -135,6 +135,7 @@ module ActiveSupport
end
def write_entry(key, entry, options) # :nodoc:
+ entry.dup_value!
synchronize do
old_entry = @data[key]
return false if @data.key?(key) && options[:unless_exist]
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 71cd9d81b3..f8e4dd6349 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -224,25 +224,22 @@ module CacheStoreBehavior
end
def test_read_multi_with_expires
- @cache.write('foo', 'bar', :expires_in => 0.001)
+ time = Time.now
+ @cache.write('foo', 'bar', :expires_in => 10)
@cache.write('fu', 'baz')
@cache.write('fud', 'biz')
- sleep(0.002)
+ Time.stubs(:now).returns(time + 11)
assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
end
def test_read_and_write_compressed_small_data
@cache.write('foo', 'bar', :compress => true)
- raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
assert_equal 'bar', @cache.read('foo')
- assert_equal 'bar', Marshal.load(raw_value)
end
def test_read_and_write_compressed_large_data
@cache.write('foo', 'bar', :compress => true, :compress_threshold => 2)
- raw_value = @cache.send(:read_entry, 'foo', {}).raw_value
assert_equal 'bar', @cache.read('foo')
- assert_equal 'bar', Marshal.load(Zlib::Inflate.inflate(raw_value))
end
def test_read_and_write_compressed_nil
@@ -301,14 +298,6 @@ module CacheStoreBehavior
assert !@cache.exist?('foo')
end
- def test_read_should_return_a_different_object_id_each_time_it_is_called
- @cache.write('foo', 'bar')
- assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id
- value = @cache.read('foo')
- value << 'bingo'
- assert_not_equal value, @cache.read('foo')
- end
-
def test_original_store_objects_should_not_be_immutable
bar = 'bar'
@cache.write('foo', bar)
@@ -363,7 +352,7 @@ module CacheStoreBehavior
rescue ArgumentError
end
assert_equal "bar", @cache.read('foo')
- Time.stubs(:now).returns(time + 71)
+ Time.stubs(:now).returns(time + 91)
assert_nil @cache.read('foo')
end
@@ -646,9 +635,9 @@ class MemoryStoreTest < ActiveSupport::TestCase
@cache.prune(@record_size * 3)
assert @cache.exist?(5)
assert @cache.exist?(4)
- assert !@cache.exist?(3)
+ assert !@cache.exist?(3), "no entry"
assert @cache.exist?(2)
- assert !@cache.exist?(1)
+ assert !@cache.exist?(1), "no entry"
end
def test_prune_size_on_write
@@ -670,12 +659,12 @@ class MemoryStoreTest < ActiveSupport::TestCase
assert @cache.exist?(9)
assert @cache.exist?(8)
assert @cache.exist?(7)
- assert !@cache.exist?(6)
- assert !@cache.exist?(5)
+ assert !@cache.exist?(6), "no entry"
+ assert !@cache.exist?(5), "no entry"
assert @cache.exist?(4)
- assert !@cache.exist?(3)
+ assert !@cache.exist?(3), "no entry"
assert @cache.exist?(2)
- assert !@cache.exist?(1)
+ assert !@cache.exist?(1), "no entry"
end
def test_pruning_is_capped_at_a_max_time
@@ -764,6 +753,14 @@ class MemCacheStoreTest < ActiveSupport::TestCase
assert_equal [], cache.read("foo")
end
end
+
+ def test_read_should_return_a_different_object_id_each_time_it_is_called
+ @cache.write('foo', 'bar')
+ assert_not_equal @cache.read('foo').object_id, @cache.read('foo').object_id
+ value = @cache.read('foo')
+ value << 'bingo'
+ assert_not_equal value, @cache.read('foo')
+ end
end
class NullStoreTest < ActiveSupport::TestCase
@@ -844,15 +841,6 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
end
class CacheEntryTest < ActiveSupport::TestCase
- def test_create_raw_entry
- time = Time.now
- entry = ActiveSupport::Cache::Entry.create("raw", time, :compress => false, :expires_in => 300)
- assert_equal "raw", entry.raw_value
- assert_equal time.to_f, entry.created_at
- assert !entry.compressed?
- assert_equal 300, entry.expires_in
- end
-
def test_expired
entry = ActiveSupport::Cache::Entry.new("value")
assert !entry.expired?, 'entry not expired'
@@ -864,16 +852,43 @@ class CacheEntryTest < ActiveSupport::TestCase
end
def test_compress_values
- entry = ActiveSupport::Cache::Entry.new("value", :compress => true, :compress_threshold => 1)
- assert_equal "value", entry.value
- assert entry.compressed?
- assert_equal "value", Marshal.load(Zlib::Inflate.inflate(entry.raw_value))
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value, :compress => true, :compress_threshold => 1)
+ assert_equal value, entry.value
+ assert (value.bytesize > entry.size), "value is compressed"
end
def test_non_compress_values
- entry = ActiveSupport::Cache::Entry.new("value")
- assert_equal "value", entry.value
- assert_equal "value", Marshal.load(entry.raw_value)
- assert !entry.compressed?
+ value = "value" * 100
+ entry = ActiveSupport::Cache::Entry.new(value)
+ assert_equal value, entry.value
+ assert_equal value.bytesize, entry.size
+ end
+
+ def test_restoring_version_3_entries
+ version_3_entry = ActiveSupport::Cache::Entry.allocate
+ version_3_entry.instance_variable_set(:@value, "hello")
+ version_3_entry.instance_variable_set(:@created_at, Time.now - 60)
+ entry = Marshal.load(Marshal.dump(version_3_entry))
+ assert_equal "hello", entry.value
+ assert_equal false, entry.expired?
+ end
+
+ def test_restoring_compressed_version_3_entries
+ version_3_entry = ActiveSupport::Cache::Entry.allocate
+ version_3_entry.instance_variable_set(:@value, Zlib::Deflate.deflate(Marshal.dump("hello")))
+ version_3_entry.instance_variable_set(:@compressed, true)
+ entry = Marshal.load(Marshal.dump(version_3_entry))
+ assert_equal "hello", entry.value
+ end
+
+ def test_restoring_expired_version_3_entries
+ version_3_entry = ActiveSupport::Cache::Entry.allocate
+ version_3_entry.instance_variable_set(:@value, "hello")
+ version_3_entry.instance_variable_set(:@created_at, Time.now - 60)
+ version_3_entry.instance_variable_set(:@expires_in, 58.9)
+ entry = Marshal.load(Marshal.dump(version_3_entry))
+ assert_equal "hello", entry.value
+ assert_equal true, entry.expired?
end
end
diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb
index d19aab1438..9516556844 100644
--- a/activesupport/test/test_test.rb
+++ b/activesupport/test/test_test.rb
@@ -100,11 +100,11 @@ class AssertPresentTest < ActiveSupport::TestCase
BLANK = [ EmptyTrue.new, nil, false, '', ' ', " \n\t \r ", [], {} ]
NOT_BLANK = [ EmptyFalse.new, Object.new, true, 0, 1, 'x', [nil], { nil => 0 } ]
- def test_assert_blank_true
+ def test_assert_present_true
NOT_BLANK.each { |v| assert_present v }
end
- def test_assert_blank_false
+ def test_assert_present_false
BLANK.each { |v|
begin
assert_present v