diff options
-rw-r--r-- | actionpack/CHANGELOG.md | 49 | ||||
-rw-r--r-- | actionpack/lib/action_controller/test_case.rb | 55 | ||||
-rw-r--r-- | actionpack/lib/action_dispatch/routing/mapper.rb | 4 | ||||
-rw-r--r-- | actionpack/lib/action_view/test_case.rb | 6 | ||||
-rw-r--r-- | actionpack/test/controller/action_pack_assertions_test.rb | 22 | ||||
-rw-r--r-- | actionpack/test/dispatch/routing_test.rb | 20 | ||||
-rw-r--r-- | actionpack/test/dispatch/session/cache_store_test.rb | 1 | ||||
-rw-r--r-- | actionpack/test/fixtures/test/hello/hello.erb | 1 | ||||
-rw-r--r-- | activerecord/lib/active_record/validations/uniqueness.rb | 8 | ||||
-rw-r--r-- | activesupport/CHANGELOG.md | 2 | ||||
-rw-r--r-- | activesupport/lib/active_support/cache.rb | 148 | ||||
-rw-r--r-- | activesupport/lib/active_support/cache/memory_store.rb | 1 | ||||
-rw-r--r-- | activesupport/test/caching_test.rb | 93 | ||||
-rw-r--r-- | railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb | 45 | ||||
-rw-r--r-- | railties/test/generators/namespaced_generators_test.rb | 76 | ||||
-rw-r--r-- | railties/test/generators/scaffold_generator_test.rb | 2 |
16 files changed, 387 insertions, 146 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 248677688f..c8f1102776 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,49 @@ ## Rails 4.0.0 (unreleased) ## +* Rename internal variables on ActionController::TemplateAssertions to prevent + naming collisions. @partials, @templates and @layouts are now prefixed with an underscore. + Fix #7459 + + *Yves Senn* + +* `resource` and `resources` don't modify the passed options hash + Fix #7777 + + *Yves Senn* + +* 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 +88,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..3af378173a 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -12,16 +12,16 @@ module ActionController end def setup_subscriptions - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload| path = payload[:layout] if path - @layouts[path] += 1 + @_layouts[path] += 1 if path =~ /^layouts\/(.*)/ - @layouts[$1] += 1 + @_layouts[$1] += 1 end end end @@ -32,11 +32,11 @@ module ActionController partial = path =~ /^.*\/_[^\/]*$/ if partial - @partials[path] += 1 - @partials[path.split("/").last] += 1 + @_partials[path] += 1 + @_partials[path.split("/").last] += 1 end - @templates[path] += 1 + @_templates[path] += 1 end end @@ -46,9 +46,9 @@ module ActionController end def process(*args) - @partials = Hash.new(0) - @templates = Hash.new(0) - @layouts = Hash.new(0) + @_partials = Hash.new(0) + @_templates = Hash.new(0) + @_layouts = Hash.new(0) super end @@ -86,31 +86,38 @@ 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 + 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 if options.key?(:layout) expected_layout = options[:layout] msg = message || sprintf("expecting layout <%s> but action rendered <%s>", - expected_layout, @layouts.keys) + expected_layout, @_layouts.keys) case expected_layout when String, Symbol - assert_includes @layouts.keys, expected_layout.to_s, msg + assert_includes @_layouts.keys, expected_layout.to_s, msg when Regexp - assert(@layouts.keys.any? {|l| l =~ expected_layout }, msg) + assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg) when nil, false - assert(@layouts.empty?, msg) + assert(@_layouts.empty?, msg) end end @@ -121,17 +128,17 @@ module ActionController assert_equal(v, actual_locals[k]) end elsif expected_count = options[:count] - actual_count = @partials[expected_partial] + actual_count = @_partials[expected_partial] msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)", expected_partial, expected_count, actual_count) assert(actual_count == expected_count.to_i, msg) else msg = message || sprintf("expecting partial <%s> but action rendered <%s>", - options[:partial], @partials.keys) - assert_includes @partials, expected_partial, msg + options[:partial], @_partials.keys) + assert_includes @_partials, expected_partial, msg end elsif options.key?(:partial) - assert @partials.empty?, + assert @_partials.empty?, "Expected no partials to be rendered" end else diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 49afa01d25..c5cf413c8f 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1038,7 +1038,7 @@ module ActionDispatch # === Options # Takes same options as +resources+. def resource(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resource, resources, options, &block) return self @@ -1204,7 +1204,7 @@ module ActionDispatch # # resource actions are at /admin/posts. # resources :posts, :path => "admin/posts" def resources(*resources, &block) - options = resources.extract_options! + options = resources.extract_options!.dup if apply_common_behavior_for(:resources, resources, options, &block) return self diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index b7fd6ec7e1..5434b3421e 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -196,17 +196,17 @@ module ActionView :@_result, :@_routes, :@controller, - :@layouts, + :@_layouts, :@locals, :@method_name, :@output_buffer, - :@partials, + :@_partials, :@passed, :@rendered, :@request, :@routes, :@tagged_logger, - :@templates, + :@_templates, :@options, :@test_passed, :@view, 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/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 4e83ad16d7..93d89f7568 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1124,6 +1124,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/sheep/1/_it', _it_sheep_path(1) end + def test_resource_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resource :user, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + + def test_resources_does_not_modify_passed_options + options = {:id => /.+?/, :format => /json|xml/} + self.class.stub_controllers do |routes| + routes.draw do + resources :users, options + end + end + assert_equal({:id => /.+?/, :format => /json|xml/}, options) + end + def test_path_names get '/pt/projetos' assert_equal 'projects#index', @response.body diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index a74e165826..b8479e8836 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'fixtures/session_autoload_test/session_autoload_test/foo' class CacheStoreTest < ActionDispatch::IntegrationTest class TestController < ActionController::Base 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..ce1053393e 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* Optimize ActiveSupport::Cache::Entry to reduce memory and processing overhead. *Brian Durand* + * Tests tag the Rails log with the current test class and test case: [SessionsControllerTest] [test_0002_sign in] Processing by SessionsController#create as HTML diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index f98ba16cdd..690e5ce194 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 + @v.bytesize + else + @s = Marshal.dump(@v).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..febf0eeeff 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 @@ -627,7 +616,7 @@ end class MemoryStoreTest < ActiveSupport::TestCase def setup - @record_size = Marshal.dump("aaaaaaaaaa").bytesize + @record_size = ActiveSupport::Cache::Entry.new("aaaaaaaaaa").size @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10) 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/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index 6a5d62803c..f33d56b564 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,13 +1,50 @@ module Rails module Generators class ResourceRouteGenerator < NamedBase + + # Properly nests namespaces passed into a generator + # + # $ rails generate resource admin/users/products + # + # should give you + # + # namespace :admin do + # namespace :users + # resources :products + # end + # end def add_resource_route return if options[:actions].present? - route_config = regular_class_path.collect{ |namespace| "namespace :#{namespace} do " }.join(" ") - route_config << "resources :#{file_name.pluralize}" - route_config << " end" * regular_class_path.size - route route_config + + # iterates over all namespaces and opens up blocks + regular_class_path.each_with_index do |namespace, index| + write("namespace :#{namespace} do", index + 1) + end + + # inserts the primary resource + write("resources :#{file_name.pluralize}", route_length + 1) + + # ends blocks + regular_class_path.each_index do |index| + write("end", route_length - index) + end + + # route prepends two spaces onto the front of the string that is passed, this corrects that + route route_string[2..-1] end + + private + def route_string + @route_string ||= "" + end + + def write(str, indent) + route_string << "#{" " * indent}#{str}\n" + end + + def route_length + regular_class_path.length + end end end end diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index db2b8af217..ede779ea59 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -304,7 +304,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Route assert_file "config/routes.rb" do |route| - assert_match(/namespace :admin do resources :roles end$/, route) + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) end # Controller @@ -346,7 +346,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Route assert_file "config/routes.rb" do |route| - assert_no_match(/namespace :admin do resources :roles end$/, route) + assert_no_match(/^ namespace :admin do\n resources :roles\n end$$/, route) end # Controller @@ -364,4 +364,76 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Stylesheets (should not be removed) assert_file "app/assets/stylesheets/scaffold.css" end + + def test_scaffold_with_nested_namespace_on_invoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + + # Model + assert_file "app/models/test_app/admin/user/special.rb", /module TestApp\n module Admin/ + assert_file "app/models/test_app/admin/user/special/role.rb", /module TestApp\n class Admin::User::Special::Role < ActiveRecord::Base/ + assert_file "test/unit/test_app/admin/user/special/role_test.rb", /module TestApp\n class Admin::User::Special::RoleTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_file "app/controllers/test_app/admin/user/special/roles_controller.rb" do |content| + assert_match(/module TestApp\n class Admin::User::Special::RolesController < ApplicationController/, content) + end + + assert_file "test/functional/test_app/admin/user/special/roles_controller_test.rb", + /module TestApp\n class Admin::User::Special::RolesControllerTest < ActionController::TestCase/ + + # Views + %w( + index + edit + new + show + _form + ).each { |view| assert_file "app/views/test_app/admin/user/special/roles/#{view}.html.erb" } + assert_no_file "app/views/layouts/admin/user/special/roles.html.erb" + + # Helpers + assert_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + assert_file "test/unit/helpers/test_app/admin/user/special/roles_helper_test.rb" + + # Stylesheets + assert_file "app/assets/stylesheets/scaffold.css" + end + + def test_scaffold_with_nested_namespace_on_revoke + run_generator [ "admin/user/special/role", "name:string", "description:string" ] + run_generator [ "admin/user/special/role" ], :behavior => :revoke + + # Model + assert_file "app/models/test_app/admin/user/special.rb" # ( should not be remove ) + assert_no_file "app/models/test_app/admin/user/special/role.rb" + assert_no_file "test/unit/test_app/admin/user/special/role_test.rb" + assert_no_file "test/fixtures/test_app/admin/user/special/roles.yml" + assert_no_migration "db/migrate/create_test_app_admin_user_special_roles.rb" + + # Route + assert_file "config/routes.rb" do |route| + assert_no_match(/^ namespace :admin do\n namespace :user do\n namespace :special do\n resources :roles\n end\n end\n end$/, route) + end + + # Controller + assert_no_file "app/controllers/test_app/admin/user/special/roles_controller.rb" + assert_no_file "test/functional/test_app/admin/user/special/roles_controller_test.rb" + + # Views + assert_no_file "app/views/test_app/admin/user/special/roles" + + # Helpers + assert_no_file "app/helpers/test_app/admin/user/special/roles_helper.rb" + assert_no_file "test/unit/helpers/test_app/admin/user/special/roles_helper_test.rb" + + # Stylesheets (should not be removed) + assert_file "app/assets/stylesheets/scaffold.css" + end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 40c5188042..f802f3c4ad 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -142,7 +142,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase # Route assert_file "config/routes.rb" do |route| - assert_match(/namespace :admin do resources :roles end$/, route) + assert_match(/^ namespace :admin do\n resources :roles\n end$/, route) end # Controller |