From 5f7cfffc5377824e03432d7773ed8864a872b18c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 10 Aug 2009 18:29:20 +0200 Subject: Use less strict rules in generators lookup, so people can lay their generators wherever they want. --- railties/lib/generators.rb | 90 +++++++++------------- railties/lib/generators/active_record.rb | 1 - .../xspec/lib/generators/xspec_generator.rb | 2 + .../gems/mspec/lib/generators/mspec_generator.rb | 2 - .../mspec/lib/generators/mspec_generator.rb | 2 + railties/test/generators/generators_test_helper.rb | 7 +- .../generators/session_migration_generator_test.rb | 13 +--- railties/test/generators_test.rb | 19 +++++ 8 files changed, 66 insertions(+), 70 deletions(-) create mode 100644 railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb delete mode 100644 railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb create mode 100644 railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb diff --git a/railties/lib/generators.rb b/railties/lib/generators.rb index c97c61507a..a2f462ae81 100644 --- a/railties/lib/generators.rb +++ b/railties/lib/generators.rb @@ -76,39 +76,37 @@ module Rails } def self.aliases #:nodoc: - @@aliases ||= DEFAULT_ALIASES.dup + @aliases ||= DEFAULT_ALIASES.dup end def self.options #:nodoc: - @@options ||= DEFAULT_OPTIONS.dup + @options ||= DEFAULT_OPTIONS.dup end - # Get paths only from loaded rubygems. In other words, to use rspec - # generators, you first have to ensure that rspec gem was already loaded. + # We have two scenarios here: when rubygems is loaded and when bundler is + # being used. If rubygems is loaded, we get all generators paths from loaded + # specs. Otherwise we just have to look into vendor/gems/gems. # - def self.rubygems_generators_paths + def self.gems_generators_paths paths = [] - return paths unless defined?(Gem) - Gem.loaded_specs.each do |name, spec| - generator_path = File.join(spec.full_gem_path, "lib/generators") - paths << generator_path if File.exist?(generator_path) + if defined?(Gem) && Gem.respond_to?(:loaded_specs) + Gem.loaded_specs.each do |name, spec| + generator_path = File.join(spec.full_gem_path, "lib/generators") + paths << generator_path if File.exist?(generator_path) + end + elsif defined?(RAILS_ROOT) + paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")] end paths end - # If RAILS_ROOT is defined, add vendor/gems, vendor/plugins and lib/generators - # paths. + # Load paths from plugin. # - def self.rails_root_generators_paths - paths = [] - if defined?(RAILS_ROOT) - paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")] - paths += Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] - paths << File.join(RAILS_ROOT, "lib", "generators") - end - paths + def self.plugins_generators_paths + return [] unless defined?(RAILS_ROOT) + Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] end # Hold configured generators fallbacks. If a plugin developer wants a @@ -125,7 +123,7 @@ module Rails # Rails::Generators.fallbacks[:shoulda] = :test_unit # def self.fallbacks - @@fallbacks ||= {} + @fallbacks ||= {} end # Remove the color from output. @@ -143,14 +141,17 @@ module Rails # 5) rubygems generators # 6) builtin generators # - # TODO Remove hardcoded paths for all, except (1). + # TODO Remove hardcoded paths for all, except (6). # def self.load_paths - @@load_paths ||= begin - paths = self.rails_root_generators_paths + @load_paths ||= begin + paths = [] + paths << File.join(RAILS_ROOT, "lib", "generators") if defined?(RAILS_ROOT) paths << File.join(Thor::Util.user_home, ".rails", "generators") - paths += self.rubygems_generators_paths + paths += self.plugins_generators_paths + paths += self.gems_generators_paths paths << File.expand_path(File.join(File.dirname(__FILE__), "generators")) + paths.uniq! paths end end @@ -279,39 +280,18 @@ module Rails end # Receives namespaces in an array and tries to find matching generators - # in the load path. Each path is traversed into directory lookups. For - # example: - # - # rails:generators:model - # - # Becomes: - # - # generators/rails/model/model_generator.rb - # generators/rails/model_generator.rb - # generators/model_generator.rb + # in the load path. # def self.lookup(attempts) #:nodoc: - attempts.each do |attempt| - generators_path = ['.'] - - paths = attempt.gsub(':generators:', ':').split(':') - name = "#{paths.last}_generator.rb" - - until paths.empty? - generators_path.unshift File.join(*paths) - paths.pop - end - - generators_path.uniq! - generators_path = "{#{generators_path.join(',')}}" - - self.load_paths.each do |path| - Dir[File.join(path, generators_path, name)].each do |file| - begin - require file - rescue Exception => e - warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}" - end + attempts = attempts.map { |a| "#{a.split(":").last}_generator" }.uniq + attempts = "{#{attempts.join(',')}}.rb" + + self.load_paths.each do |path| + Dir[File.join(path, '**', attempts)].each do |file| + begin + require file + rescue Exception => e + warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}" end end end diff --git a/railties/lib/generators/active_record.rb b/railties/lib/generators/active_record.rb index 924b70881a..ff3093f356 100644 --- a/railties/lib/generators/active_record.rb +++ b/railties/lib/generators/active_record.rb @@ -1,7 +1,6 @@ require 'generators/named_base' require 'generators/migration' require 'generators/active_model' -require 'active_record' module ActiveRecord module Generators diff --git a/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb b/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb new file mode 100644 index 0000000000..cd477eb4c9 --- /dev/null +++ b/railties/test/fixtures/vendor/another_gem_path/xspec/lib/generators/xspec_generator.rb @@ -0,0 +1,2 @@ +class XspecGenerator < Rails::Generators::NamedBase +end diff --git a/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb deleted file mode 100644 index 191bdbf2fc..0000000000 --- a/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb +++ /dev/null @@ -1,2 +0,0 @@ -class MspecGenerator < Rails::Generators::NamedBase -end diff --git a/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb b/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb new file mode 100644 index 0000000000..191bdbf2fc --- /dev/null +++ b/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb @@ -0,0 +1,2 @@ +class MspecGenerator < Rails::Generators::NamedBase +end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index 9444a9ed4b..a258574dce 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -8,9 +8,12 @@ else RAILS_ROOT = fixtures end -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib" $LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib" +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib" +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib" require 'generators' +require 'activerecord' +require 'action_dispatch' CURRENT_PATH = File.expand_path(Dir.pwd) Rails::Generators.no_color! @@ -19,7 +22,7 @@ class GeneratorsTestCase < Test::Unit::TestCase include FileUtils def destination_root - @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__), + @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures', 'tmp')) end diff --git a/railties/test/generators/session_migration_generator_test.rb b/railties/test/generators/session_migration_generator_test.rb index 57bd755a9a..293b903b87 100644 --- a/railties/test/generators/session_migration_generator_test.rb +++ b/railties/test/generators/session_migration_generator_test.rb @@ -2,16 +2,6 @@ require 'abstract_unit' require 'generators/generators_test_helper' require 'generators/rails/session_migration/session_migration_generator' -module ActiveRecord - module SessionStore - class Session - class << self - attr_accessor :table_name - end - end - end -end - class SessionMigrationGeneratorTest < GeneratorsTestCase def test_session_migration_with_default_name @@ -31,7 +21,10 @@ class SessionMigrationGeneratorTest < GeneratorsTestCase assert_match /class AddSessionsTable < ActiveRecord::Migration/, migration assert_match /create_table :custom_table_name/, migration end + ensure + ActiveRecord::SessionStore::Session.table_name = "sessions" end + protected def run_generator(args=[]) diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 89d52dd170..4cc0b33521 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -4,6 +4,11 @@ require 'generators/test_unit/model/model_generator' require 'mocha' class GeneratorsTest < GeneratorsTestCase + def setup + Rails::Generators.instance_variable_set(:@load_paths, nil) + Gem.stubs(:respond_to?).with(:loaded_specs).returns(false) + end + def test_invoke_when_generator_is_not_found output = capture(:stdout){ Rails::Generators.invoke :unknown } assert_equal "Could not find generator unknown.\n", output @@ -70,6 +75,20 @@ class GeneratorsTest < GeneratorsTestCase assert_equal "mspec", klass.namespace end + def test_find_by_namespace_lookup_with_gem_specification + assert_nil Rails::Generators.find_by_namespace(:xspec) + Rails::Generators.instance_variable_set(:@load_paths, nil) + + spec = Gem::Specification.new + spec.expects(:full_gem_path).returns(File.join(RAILS_ROOT, 'vendor', 'another_gem_path', 'xspec')) + Gem.expects(:respond_to?).with(:loaded_specs).returns(true) + Gem.expects(:loaded_specs).returns(:spec => spec) + + klass = Rails::Generators.find_by_namespace(:xspec) + assert klass + assert_equal "xspec", klass.namespace + end + def test_builtin_generators assert Rails::Generators.builtin.include? %w(rails model) end -- cgit v1.2.3 From 04d4537cd40d0415d15af4395213632735c8683f Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Sun, 9 Aug 2009 07:14:24 -0300 Subject: This change causes some failing tests, but it should be possible to make them pass with minimal performance impact. --- actionpack/examples/minimal.rb | 6 ++++-- actionpack/lib/action_dispatch/http/request.rb | 23 ++++++++++++----------- actionpack/lib/action_view/template/resolver.rb | 6 ++++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index a9015da053..b3733b487b 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -91,6 +91,8 @@ class HttpPostController < ActionController::Metal end end +ActionController::Base.use_accept_header = false + unless ENV["PROFILE"] Runner.run(BasePostController.action(:overhead), N, 'overhead', false) Runner.run(BasePostController.action(:index), N, 'index', false) @@ -108,10 +110,10 @@ unless ENV["PROFILE"] Runner.run(BasePostController.action(:show_template), N, 'template') end else - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') + Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), N, ENV["PROFILE"]) require "ruby-prof" RubyProf.start - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') + Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), N, ENV["PROFILE"]) result = RubyProf.stop printer = RubyProf::CallStackPrinter.new(result) printer.print(File.open("output.html", "w")) diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 4190fa21cd..958a541436 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -176,17 +176,18 @@ module ActionDispatch # Expand raw_formats by converting Mime::ALL to the Mime::SET. # def formats - if ActionController::Base.use_accept_header - raw_formats.tap do |ret| - if ret == ONLY_ALL - ret.replace Mime::SET - elsif all = ret.index(Mime::ALL) - ret.delete_at(all) && ret.insert(all, *Mime::SET) - end - end - else - raw_formats + Mime::SET - end + return raw_formats + # if ActionController::Base.use_accept_header + # raw_formats.tap do |ret| + # if ret == ONLY_ALL + # ret.replace Mime::SET + # elsif all = ret.index(Mime::ALL) + # ret.delete_at(all) && ret.insert(all, *Mime::SET) + # end + # end + # else + # raw_formats + Mime::SET + # end end # Sets the \format by string extension, which can be used to force custom formats diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index ebfc6cc8ce..3bd2acae7a 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -41,8 +41,10 @@ module ActionView end def handler_glob - e = TemplateHandlers.extensions.map{|h| ".#{h},"}.join - "{#{e}}" + @handler_glob ||= begin + e = TemplateHandlers.extensions.map{|h| ".#{h},"}.join + "{#{e}}" + end end def formats_glob -- cgit v1.2.3 From 02d9dd900048407ef555cf09b0038a57ae924b0a Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Sun, 9 Aug 2009 09:46:50 -0300 Subject: Add some more caching to the lookup --- actionpack/lib/abstract_controller/layouts.rb | 22 ++++++++++++++---- actionpack/lib/action_view/template/resolver.rb | 30 +++++++++++++++---------- actionpack/test/dispatch/request_test.rb | 4 ++-- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 0063d54149..d7317b415c 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -15,6 +15,18 @@ module AbstractController klass._write_layout_method end + def cache_layout(details) + layout = @found_layouts ||= {} + values = details.values_at(:formats, :locale) + + # Cache nil + if layout.key?(values) + return layout[values] + else + layout[values] = yield + end + end + # Specify the layout to use for this class. # # If the specified layout is a: @@ -76,10 +88,12 @@ module AbstractController when nil self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 def _layout(details) - if view_paths.exists?("#{_implied_layout_name}", details, "layouts") - "#{_implied_layout_name}" - else - super + self.class.cache_layout(details) do + if view_paths.exists?("#{_implied_layout_name}", details, "layouts") + "#{_implied_layout_name}" + else + super + end end end ruby_eval diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 3bd2acae7a..10f664736f 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -62,6 +62,10 @@ module ActionView class FileSystemResolver < Resolver + def self.cached_glob + @@cached_glob ||= {} + end + def initialize(path, options = {}) raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver) super(options) @@ -107,20 +111,22 @@ module ActionView # :api: plugin def details_to_glob(name, details, prefix, partial, root) - path = "" - path << "#{prefix}/" unless prefix.empty? - path << (partial ? "_#{name}" : name) - - extensions = "" - [:locales, :formats].each do |k| - extensions << if exts = details[k] - '{' + exts.map {|e| ".#{e},"}.join + '}' - else - k == :formats ? formats_glob : '' + self.class.cached_glob[[name, prefix, partial, details, root]] ||= begin + path = "" + path << "#{prefix}/" unless prefix.empty? + path << (partial ? "_#{name}" : name) + + extensions = "" + [:locales, :formats].each do |k| + extensions << if exts = details[k] + '{' + exts.map {|e| ".#{e},"}.join + '}' + else + k == :formats ? formats_glob : '' + end end - end - "#{root}#{path}#{extensions}#{handler_glob}" + "#{root}#{path}#{extensions}#{handler_glob}" + end end # TODO: fix me diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index f3500fca34..b626063df4 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -427,7 +427,7 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8' request.expects(:parameters).at_least_once.returns({}) - assert_equal with_set(Mime::XML, Mime::HTML), request.formats + assert_equal with_set(Mime::XML, Mime::HTML, Mime::ALL), request.formats end with_accept_header false do @@ -460,7 +460,7 @@ protected end def with_set(*args) - args + Mime::SET + args end def with_accept_header(value) -- cgit v1.2.3 From bef7576c09bbd51aeeb8e83b4cf24a994a0256b0 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Sun, 9 Aug 2009 09:59:54 -0300 Subject: Add a few more benches --- actionpack/examples/minimal.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index b3733b487b..c4e5ffd36e 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -73,10 +73,18 @@ class BasePostController < ActionController::Base render :partial => "/many_partials" end + def hundred_partials + render :partial => "/hundred_partials" + end + def partial_collection render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10] end + def large_collection + render :partial => "/collection", :collection => (1...100).to_a + end + def show_template render :template => "template" end @@ -99,6 +107,8 @@ unless ENV["PROFILE"] Runner.run(BasePostController.action(:partial), N, 'partial', false) Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false) Runner.run(BasePostController.action(:partial_collection), N, 'collection', false) + Runner.run(BasePostController.action(:hundred_partials), N, 'hundred_partials', false) + Runner.run(BasePostController.action(:large_collection), N, 'large_collection', false) Runner.run(BasePostController.action(:show_template), N, 'template', false) (ENV["M"] || 1).to_i.times do @@ -107,6 +117,8 @@ unless ENV["PROFILE"] Runner.run(BasePostController.action(:partial), N, 'partial') Runner.run(BasePostController.action(:many_partials), N, 'many_partials') Runner.run(BasePostController.action(:partial_collection), N, 'collection') + Runner.run(BasePostController.action(:hundred_partials), N, 'hundred_partials') + Runner.run(BasePostController.action(:large_collection), N, 'large_collection') Runner.run(BasePostController.action(:show_template), N, 'template') end else -- cgit v1.2.3 From 4945d8223964d4ccb3c0a0f4107f15ae1c6c4a09 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 10 Aug 2009 02:54:17 -0400 Subject: Further experimentation. Was able to cut the cost of rendering 100 partials in a collection in half. To discuss: What are the desired semantics (if any) for layouts in a collection. There are no tests for it at present, and I'm not sure if it's needed at all. Deprecated on this branch: `object` pointing at the current object in partials. You can still use the partial name, or use :as to achieve the same thing. This is obviously up for discussion. --- actionpack/examples/minimal.rb | 4 +- actionpack/examples/views/_hundred_partials.erb | 12 ++++++ actionpack/lib/action_view/render/partials.rb | 49 ++++++++++++++++++++----- actionpack/lib/action_view/test_case.rb | 20 +++++----- 4 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 actionpack/examples/views/_hundred_partials.erb diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index c4e5ffd36e..90435d0b89 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -104,22 +104,22 @@ ActionController::Base.use_accept_header = false unless ENV["PROFILE"] Runner.run(BasePostController.action(:overhead), N, 'overhead', false) Runner.run(BasePostController.action(:index), N, 'index', false) + Runner.run(BasePostController.action(:show_template), N, 'template', false) Runner.run(BasePostController.action(:partial), N, 'partial', false) Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false) Runner.run(BasePostController.action(:partial_collection), N, 'collection', false) Runner.run(BasePostController.action(:hundred_partials), N, 'hundred_partials', false) Runner.run(BasePostController.action(:large_collection), N, 'large_collection', false) - Runner.run(BasePostController.action(:show_template), N, 'template', false) (ENV["M"] || 1).to_i.times do Runner.run(BasePostController.action(:overhead), N, 'overhead') Runner.run(BasePostController.action(:index), N, 'index') + Runner.run(BasePostController.action(:show_template), N, 'template') Runner.run(BasePostController.action(:partial), N, 'partial') Runner.run(BasePostController.action(:many_partials), N, 'many_partials') Runner.run(BasePostController.action(:partial_collection), N, 'collection') Runner.run(BasePostController.action(:hundred_partials), N, 'hundred_partials') Runner.run(BasePostController.action(:large_collection), N, 'large_collection') - Runner.run(BasePostController.action(:show_template), N, 'template') end else Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), N, ENV["PROFILE"]) diff --git a/actionpack/examples/views/_hundred_partials.erb b/actionpack/examples/views/_hundred_partials.erb new file mode 100644 index 0000000000..15e99c1b68 --- /dev/null +++ b/actionpack/examples/views/_hundred_partials.erb @@ -0,0 +1,12 @@ +<% 10.times do %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> + <%= render :partial => '/hello' %> +<% end %> \ No newline at end of file diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 64f08c447d..2aa3bd7db8 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -207,9 +207,7 @@ module ActionView end def render_collection - # Even if no template is rendered, this will ensure that the MIME type - # for the empty response is the same as the provided template - @options[:_template] = default_template = find_template + @options[:_template] = template = find_template return nil if collection.blank? @@ -217,15 +215,48 @@ module ActionView spacer = find_template(@options[:spacer_template]).render(@view, @locals) end - segments = [] + result = template ? collection_with_template(template) : collection_without_template + result.join(spacer) + end + + def collection_with_template(template) + options = @options + + segments, locals, as = [], @locals, options[:as] + + [].tap do |segments| + variable_name = template.variable_name + counter_name = template.counter_name + locals[counter_name] = -1 - collection.each_with_index do |object, index| - template = default_template || find_template(partial_path(object)) - @locals[template.counter_name] = index - segments << render_template(template, object) + collection.each do |object| + locals[counter_name] += 1 + locals[variable_name] = object + locals[as] = object if as + + segments << template.render(@view, locals) + end end + end - segments.join(spacer) + def collection_without_template + options = @options + + segments, locals, as = [], @locals, options[:as] + index, template = -1, nil + + [].tap do |segments| + collection.each do |object| + template = find_template(partial_path(object)) + locals[template.counter_name] = (index += 1) + locals[template.variable_name] = object + locals[as] = object if as + + segments << template.render(@view, locals) + end + + @options[:_template] = template + end end def render_template(template, object = @object) diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index e51744d095..b317b6dc1a 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -9,14 +9,16 @@ module ActionView end attr_internal :rendered - alias_method :_render_template_without_template_tracking, :_render_single_template - def _render_single_template(template, local_assigns, &block) - if template.respond_to?(:identifier) && template.present? - @_rendered[:partials][template] += 1 if template.partial? - @_rendered[:template] ||= [] - @_rendered[:template] << template - end - _render_template_without_template_tracking(template, local_assigns, &block) + end + + class Template + alias_method :render_without_tracking, :render + def render(view, locals, &blk) + rendered = view.rendered + rendered[:partials][self] += 1 if partial? + rendered[:template] ||= [] + rendered[:template] << self + render_without_tracking(view, locals, &blk) end end @@ -68,7 +70,7 @@ module ActionView def initialize @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - + @params = {} send(:initialize_current_url) end -- cgit v1.2.3 From 945a7df9f8ad2986ba2351b331a9f7225dfaf61c Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 10 Aug 2009 03:28:21 -0400 Subject: Make large_collection 1,000 partials --- actionpack/examples/minimal.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb index 90435d0b89..62b71de2cb 100644 --- a/actionpack/examples/minimal.rb +++ b/actionpack/examples/minimal.rb @@ -82,7 +82,7 @@ class BasePostController < ActionController::Base end def large_collection - render :partial => "/collection", :collection => (1...100).to_a + render :partial => "/collection", :collection => (1...1000).to_a end def show_template @@ -102,14 +102,14 @@ end ActionController::Base.use_accept_header = false unless ENV["PROFILE"] - Runner.run(BasePostController.action(:overhead), N, 'overhead', false) - Runner.run(BasePostController.action(:index), N, 'index', false) - Runner.run(BasePostController.action(:show_template), N, 'template', false) - Runner.run(BasePostController.action(:partial), N, 'partial', false) - Runner.run(BasePostController.action(:many_partials), N, 'many_partials', false) - Runner.run(BasePostController.action(:partial_collection), N, 'collection', false) - Runner.run(BasePostController.action(:hundred_partials), N, 'hundred_partials', false) - Runner.run(BasePostController.action(:large_collection), N, 'large_collection', false) + Runner.run(BasePostController.action(:overhead), 1, 'overhead', false) + Runner.run(BasePostController.action(:index), 1, 'index', false) + Runner.run(BasePostController.action(:show_template), 1, 'template', false) + Runner.run(BasePostController.action(:partial), 1, 'partial', false) + Runner.run(BasePostController.action(:many_partials), 1, 'many_partials', false) + Runner.run(BasePostController.action(:partial_collection), 1, 'collection', false) + Runner.run(BasePostController.action(:hundred_partials), 1, 'hundred_partials', false) + Runner.run(BasePostController.action(:large_collection), 1, 'large_collection', false) (ENV["M"] || 1).to_i.times do Runner.run(BasePostController.action(:overhead), N, 'overhead') @@ -122,7 +122,7 @@ unless ENV["PROFILE"] Runner.run(BasePostController.action(:large_collection), N, 'large_collection') end else - Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), N, ENV["PROFILE"]) + Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), 1, ENV["PROFILE"]) require "ruby-prof" RubyProf.start Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), N, ENV["PROFILE"]) -- cgit v1.2.3 From 9e62d6d1c0c53e8b03c9659500e2b5549a1fd2ec Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 10 Aug 2009 08:57:44 -0400 Subject: Tentatively accept the ":as or :object, but not both" solution --- actionpack/lib/action_view/render/partials.rb | 43 +++++++++++----------- .../test/fixtures/test/_customer_with_var.erb | 2 +- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 2aa3bd7db8..83175ab4cf 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -184,6 +184,7 @@ module ActionView def initialize(view_context, options, block) partial = options[:partial] + @memo = {} @view = view_context @options = options @locals = options[:locals] || {} @@ -222,41 +223,39 @@ module ActionView def collection_with_template(template) options = @options - segments, locals, as = [], @locals, options[:as] + segments, locals, as = [], @locals, options[:as] || :object - [].tap do |segments| - variable_name = template.variable_name - counter_name = template.counter_name - locals[counter_name] = -1 + variable_name = template.variable_name + counter_name = template.counter_name + locals[counter_name] = -1 - collection.each do |object| - locals[counter_name] += 1 - locals[variable_name] = object - locals[as] = object if as + collection.each do |object| + locals[counter_name] += 1 + locals[variable_name] = object + locals[as] = object if as - segments << template.render(@view, locals) - end + segments << template.render(@view, locals) end + segments end def collection_without_template options = @options - segments, locals, as = [], @locals, options[:as] + segments, locals, as = [], @locals, options[:as] || :object index, template = -1, nil - [].tap do |segments| - collection.each do |object| - template = find_template(partial_path(object)) - locals[template.counter_name] = (index += 1) - locals[template.variable_name] = object - locals[as] = object if as - - segments << template.render(@view, locals) - end + collection.each do |object| + template = find_template(partial_path(object)) + locals[template.counter_name] = (index += 1) + locals[template.variable_name] = object + locals[as] = object if as - @options[:_template] = template + segments << template.render(@view, locals) end + + @options[:_template] = template + segments end def render_template(template, object = @object) diff --git a/actionpack/test/fixtures/test/_customer_with_var.erb b/actionpack/test/fixtures/test/_customer_with_var.erb index 3379246b7e..c28824936b 100644 --- a/actionpack/test/fixtures/test/_customer_with_var.erb +++ b/actionpack/test/fixtures/test/_customer_with_var.erb @@ -1 +1 @@ -<%= customer.name %> <%= object.name %> <%= customer_with_var.name %> \ No newline at end of file +<%= customer.name %> <%= customer.name %> <%= customer_with_var.name %> \ No newline at end of file -- cgit v1.2.3 From 0adbeeb0c92c6de2e4a148e4b54d56cd4a325800 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 10 Aug 2009 11:40:41 -0400 Subject: Got overhead down from 127 to 85. All tests pass: * Tentatively replaced HeaderHash with SimpleHeaderHash, which does not preserve case but does handle converting Arrays to Strings in to_hash. This requires further discussion. * Moved default_charset to ActionDispatch::Response to avoid having to hop over to ActionController. Ideally, this would be a constant on AD::Response, but some tests expect to be able to change it dynamically and I didn't want to change them yet. * Completely override #initialize from Rack::Response. Previously, it was creating a HeaderHash, and then we were creating an entirely new one. There is no way to call super without incurring the overhead of creating a HeaderHash. * Override #write from Rack::Response. Its implementation tracks Content-Length, and doing so adds additional overhead that could be mooted if other middleware changes the body. It is more efficiently done at the top-level server. * Change sending_file to an instance_variable instead of header inspection. In general, if a state is important, it should be set as a property of the response not reconstructed later. * Set the Etag to @body instead of .body. AS::Cache.expand_cache_key handles Arrays fine, and it's more efficient to let it handle the body parts, since it is not forced to create a joined String. * If we detect the default cache control case, just set it, rather than setting the constituent parts and then running the normal (expensive) code to generate the string. --- .../lib/action_controller/metal/compatibility.rb | 5 +- .../lib/action_controller/metal/streaming.rb | 3 +- .../lib/action_controller/testing/process.rb | 2 +- actionpack/lib/action_dispatch/http/response.rb | 92 +++++++++++++--------- .../core_ext/class/attribute_accessors.rb | 5 +- 5 files changed, 65 insertions(+), 42 deletions(-) diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index f94d1c669c..a008b53d45 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -25,8 +25,9 @@ module ActionController cattr_accessor :relative_url_root self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] - cattr_accessor :default_charset - self.default_charset = "utf-8" + class << self + delegate :default_charset=, :to => "ActionDispatch::Response" + end # cattr_reader :protected_instance_variables cattr_accessor :protected_instance_variables diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 57318e8747..4761763a26 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -145,7 +145,6 @@ module ActionController #:nodoc: def send_data(data, options = {}) #:doc: logger.info "Sending data #{options[:filename]}" if logger send_file_headers! options.merge(:length => data.bytesize) - @performed_render = false render :status => options[:status], :text => data end @@ -175,6 +174,8 @@ module ActionController #:nodoc: 'Content-Transfer-Encoding' => 'binary' ) + response.sending_file = true + # Fix a problem with IE 6.0 on opening downloaded files: # If Cache-Control: no-cache is set (which Rails does by default), # IE removes the file it just downloaded from its cache immediately diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index d32d5562e8..147a7e7631 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -52,7 +52,7 @@ module ActionController #:nodoc: class TestResponse < ActionDispatch::TestResponse def recycle! @status = 200 - @header = Rack::Utils::HeaderHash.new + @header = ActionDispatch::Response::SimpleHeaderHash.new @writer = lambda { |x| @body << x } @block = nil @length = 0 diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 03d1780b77..1d3cf63984 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,18 +32,42 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response + class SimpleHeaderHash < Hash + def to_hash + result = {} + each do |k,v| + v = v.join("\n") if v.is_a?(Array) + result[k] = v + end + result + end + end + attr_accessor :request attr_reader :cache_control - attr_writer :header + attr_writer :header, :sending_file alias_method :headers=, :header= - delegate :default_charset, :to => 'ActionController::Base' - def initialize - super + @status = 200 + @header = SimpleHeaderHash.new @cache_control = {} - @header = Rack::Utils::HeaderHash.new + + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 + + @body = [] + @sending_file = false + + yield self if block_given? + end + + def write(str) + s = str.to_s + @writer.call s + str end def status=(status) @@ -128,20 +152,20 @@ module ActionDispatch # :nodoc: end end - def sending_file? - headers["Content-Transfer-Encoding"] == "binary" - end + CONTENT_TYPE = "Content-Type" + + cattr_accessor(:default_charset) { "utf-8" } def assign_default_content_type_and_charset! - return if !headers["Content-Type"].blank? + return if !headers[CONTENT_TYPE].blank? @content_type ||= Mime::HTML - @charset ||= default_charset + @charset ||= self.class.default_charset type = @content_type.to_s.dup - type << "; charset=#{@charset}" unless sending_file? + type << "; charset=#{@charset}" unless @sending_file - headers["Content-Type"] = type + headers[CONTENT_TYPE] = type end def prepare! @@ -168,17 +192,6 @@ module ActionDispatch # :nodoc: str end - def set_cookie(key, value) - if value.has_key?(:http_only) - ActiveSupport::Deprecation.warn( - "The :http_only option in ActionController::Response#set_cookie " + - "has been renamed. Please use :httponly instead.", caller) - value[:httponly] ||= value.delete(:http_only) - end - - super(key, value) - end - # Returns the response cookies, converted to a Hash of (name => value) pairs # # assert_equal 'AuthorOfNewPage', r.cookies['author'] @@ -201,7 +214,7 @@ module ActionDispatch # :nodoc: if etag? || last_modified? || !cache_control.empty? set_conditional_cache_control! elsif nonempty_ok_response? - self.etag = body + self.etag = @body if request && request.etag_matches?(etag) self.status = 304 @@ -214,30 +227,37 @@ module ActionDispatch # :nodoc: end end + EMPTY_RESPONSE = [" "] + def nonempty_ok_response? ok = !@status || @status == 200 - ok && string_body? + ok && string_body? && @body != EMPTY_RESPONSE end def string_body? !body_parts.respond_to?(:call) && body_parts.any? && body_parts.all? { |part| part.is_a?(String) } end + DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" + def set_conditional_cache_control! - if cache_control.empty? - cache_control.merge!(:public => false, :max_age => 0, :must_revalidate => true) - end + control = cache_control - public_cache, max_age, must_revalidate, extras = - cache_control.values_at(:public, :max_age, :must_revalidate, :extras) + if control.empty? + headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + else + extras = control[:extras] + max_age = control[:max_age] + + options = [] + options << "max-age=#{max_age}" if max_age + options << (control[:public] ? "public" : "private") + options << "must-revalidate" if control[:must_revalidate] + options.concat(extras) if extras - options = [] - options << "max-age=#{max_age}" if max_age - options << (public_cache ? "public" : "private") - options << "must-revalidate" if must_revalidate - options.concat(extras) if extras + headers["Cache-Control"] = options.join(", ") + end - headers["Cache-Control"] = options.join(", ") end end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index 74ce85a1c2..1602a609eb 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -46,11 +46,12 @@ class Class end # end " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false EOS + self.send("#{sym}=", yield) if block_given? end end - def cattr_accessor(*syms) + def cattr_accessor(*syms, &blk) cattr_reader(*syms) - cattr_writer(*syms) + cattr_writer(*syms, &blk) end end -- cgit v1.2.3 From 4bf516e072f5279bdb462c6592e17b195fd9cf05 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 10 Aug 2009 15:49:33 -0700 Subject: More perf work: * Move #set_cookie and #delete_cookie inline to optimize. These optimizations should almost certainly be sent back upstream to Rack. The optimization involves using an ivar for cookies instead of indexing into the headers each time. * Was able to use a bare Hash for headers now that cookies have their own joining semantics (some code assumed that the raw cookies were an Array). * Cache blankness of body on body= * Improve expand_cache_key for Arrays of a single element (common in our case) * Use a simple layout condition check unless conditions are used * Cache visible actions * Lazily load the UrlRewriter * Make etag an ivar that is set on prepare! --- actionpack/lib/abstract_controller/layouts.rb | 49 ++++++++----- .../lib/action_controller/metal/compatibility.rb | 7 +- .../lib/action_controller/metal/hide_actions.rb | 14 +++- actionpack/lib/action_controller/metal/url_for.rb | 10 +-- .../lib/action_controller/testing/process.rb | 2 +- .../lib/action_controller/testing/test_case.rb | 1 - actionpack/lib/action_dispatch/http/request.rb | 37 +++------- actionpack/lib/action_dispatch/http/response.rb | 84 ++++++++++++++-------- actionpack/lib/action_view/test_case.rb | 1 - actionpack/test/controller/caching_test.rb | 1 - activesupport/lib/active_support/cache.rb | 22 ++++-- 11 files changed, 128 insertions(+), 100 deletions(-) diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index d7317b415c..ac2154dffc 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -6,17 +6,21 @@ module AbstractController included do extlib_inheritable_accessor(:_layout_conditions) { Hash.new } + extlib_inheritable_accessor(:_action_has_layout) { Hash.new } _write_layout_method end module ClassMethods def inherited(klass) super - klass._write_layout_method + klass.class_eval do + _write_layout_method + @found_layouts = {} + end end def cache_layout(details) - layout = @found_layouts ||= {} + layout = @found_layouts values = details.values_at(:formats, :locale) # Cache nil @@ -27,6 +31,28 @@ module AbstractController end end + # This module is mixed in if layout conditions are provided. This means + # that if no layout conditions are used, this method is not used + module LayoutConditions + # Determines whether the current action has a layout by checking the + # action name against the :only and :except conditions set on the + # layout. + # + # ==== Returns + # Boolean:: True if the action has a layout, false otherwise. + def _action_has_layout? + conditions = _layout_conditions + + if only = conditions[:only] + only.include?(action_name) + elsif except = conditions[:except] + !except.include?(action_name) + else + true + end + end + end + # Specify the layout to use for this class. # # If the specified layout is a: @@ -43,6 +69,8 @@ module AbstractController # :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to. # :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one def layout(layout, conditions = {}) + include LayoutConditions unless conditions.empty? + conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } self._layout_conditions = conditions @@ -150,7 +178,7 @@ module AbstractController view_paths.find(name, details, prefix) end - # Returns the default layout for this controller and a given set of details. + # Returns the default layout for this controller and a given set of details. # Optionally raises an exception if the layout could not be found. # # ==== Parameters @@ -176,21 +204,8 @@ module AbstractController end end - # Determines whether the current action has a layout by checking the - # action name against the :only and :except conditions set on the - # layout. - # - # ==== Returns - # Boolean:: True if the action has a layout, false otherwise. def _action_has_layout? - conditions = _layout_conditions - if only = conditions[:only] - only.include?(action_name) - elsif except = conditions[:except] - !except.include?(action_name) - else - true - end + true end end end diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index a008b53d45..5b0165f0e7 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -102,11 +102,10 @@ module ActionController options[:template].sub!(/^\//, '') end - options[:text] = nil if options[:nothing] == true + options[:text] = nil if options.delete(:nothing) == true + options[:text] = " " if options.key?(:text) && options[:text].nil? - body = super - body = [' '] if body.blank? - body + super || " " end def _handle_method_missing diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb index af68c772b1..cdacdc40a6 100644 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ b/actionpack/lib/action_controller/metal/hide_actions.rb @@ -13,7 +13,9 @@ module ActionController # Overrides AbstractController::Base#action_method? to return false if the # action name is in the list of hidden actions. def action_method?(action_name) - !hidden_actions.include?(action_name) && super + self.class.visible_action?(action_name) do + !hidden_actions.include?(action_name) && super + end end module ClassMethods @@ -25,6 +27,16 @@ module ActionController hidden_actions.merge(args.map! {|a| a.to_s }) end + def inherited(klass) + klass.instance_variable_set("@visible_actions", {}) + super + end + + def visible_action?(action_name) + return @visible_actions[action_name] if @visible_actions.key?(action_name) + @visible_actions[action_name] = yield + end + # Overrides AbstractController::Base#action_methods to remove any methods # that are listed as hidden methods. def action_methods diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 7119c14cd3..14c6523045 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -4,15 +4,6 @@ module ActionController include RackConvenience - def process_action(*) - initialize_current_url - super - end - - def initialize_current_url - @url = UrlRewriter.new(request, params.clone) - end - # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in # the form of a hash, just like the one you would use for url_for directly. Example: # @@ -40,6 +31,7 @@ module ActionController when String options when Hash + @url ||= UrlRewriter.new(request, params) @url.rewrite(rewrite_options(options)) else polymorphic_url(options) diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index 147a7e7631..09b1a59254 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -52,7 +52,7 @@ module ActionController #:nodoc: class TestResponse < ActionDispatch::TestResponse def recycle! @status = 200 - @header = ActionDispatch::Response::SimpleHeaderHash.new + @header = {} @writer = lambda { |x| @body << x } @block = nil @length = 0 diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb index a11755b517..b66a4c15ff 100644 --- a/actionpack/lib/action_controller/testing/test_case.rb +++ b/actionpack/lib/action_controller/testing/test_case.rb @@ -179,7 +179,6 @@ module ActionController if @controller @controller.request = @request @controller.params = {} - @controller.send(:initialize_current_url) end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 958a541436..b23306af62 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -173,21 +173,16 @@ module ActionDispatch end end - # Expand raw_formats by converting Mime::ALL to the Mime::SET. - # def formats - return raw_formats - # if ActionController::Base.use_accept_header - # raw_formats.tap do |ret| - # if ret == ONLY_ALL - # ret.replace Mime::SET - # elsif all = ret.index(Mime::ALL) - # ret.delete_at(all) && ret.insert(all, *Mime::SET) - # end - # end - # else - # raw_formats + Mime::SET - # end + if ActionController::Base.use_accept_header + if param = parameters[:format] + Array.wrap(Mime[param]) + else + accepts.dup + end + else + [format] + end end # Sets the \format by string extension, which can be used to force custom formats @@ -488,7 +483,7 @@ EOM # matches the order array. # def negotiate_mime(order) - raw_formats.each do |priority| + formats.each do |priority| if priority == Mime::ALL return order.first elsif order.include?(priority) @@ -501,18 +496,6 @@ EOM private - def raw_formats - if ActionController::Base.use_accept_header - if param = parameters[:format] - Array.wrap(Mime[param]) - else - accepts.dup - end - else - [format] - end - end - def named_host?(host) !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 1d3cf63984..055f29a972 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,18 +32,7 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response - class SimpleHeaderHash < Hash - def to_hash - result = {} - each do |k,v| - v = v.join("\n") if v.is_a?(Array) - result[k] = v - end - result - end - end - - attr_accessor :request + attr_accessor :request, :blank attr_reader :cache_control attr_writer :header, :sending_file @@ -51,19 +40,23 @@ module ActionDispatch # :nodoc: def initialize @status = 200 - @header = SimpleHeaderHash.new + @header = {} @cache_control = {} @writer = lambda { |x| @body << x } @block = nil @length = 0 - @body = [] + @body, @cookie = [], [] @sending_file = false yield self if block_given? end + def cache_control + @cache_control ||= {} + end + def write(str) s = str.to_s @writer.call s @@ -95,7 +88,10 @@ module ActionDispatch # :nodoc: str end + EMPTY = " " + def body=(body) + @blank = true if body == EMPTY @body = body.respond_to?(:to_str) ? [body] : body end @@ -137,19 +133,16 @@ module ActionDispatch # :nodoc: end def etag - headers['ETag'] + @etag end def etag? - headers.include?('ETag') + @etag end def etag=(etag) - if etag.blank? - headers.delete('ETag') - else - headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}") - end + key = ActiveSupport::Cache.expand_cache_key(etag) + @etag = %("#{Digest::MD5.hexdigest(key)}") end CONTENT_TYPE = "Content-Type" @@ -157,7 +150,7 @@ module ActionDispatch # :nodoc: cattr_accessor(:default_charset) { "utf-8" } def assign_default_content_type_and_charset! - return if !headers[CONTENT_TYPE].blank? + return if headers[CONTENT_TYPE].present? @content_type ||= Mime::HTML @charset ||= self.class.default_charset @@ -171,7 +164,8 @@ module ActionDispatch # :nodoc: def prepare! assign_default_content_type_and_charset! handle_conditional_get! - self["Set-Cookie"] ||= "" + self["Set-Cookie"] = @cookie.join("\n") + self["ETag"] = @etag if @etag end def each(&callback) @@ -197,7 +191,7 @@ module ActionDispatch # :nodoc: # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} - if header = headers['Set-Cookie'] + if header = @cookie header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| if pair = cookie.split(';').first @@ -209,9 +203,40 @@ module ActionDispatch # :nodoc: cookies end + def set_cookie(key, value) + case value + when Hash + domain = "; domain=" + value[:domain] if value[:domain] + path = "; path=" + value[:path] if value[:path] + # According to RFC 2109, we need dashes here. + # N.B.: cgi.rb uses spaces... + expires = "; expires=" + value[:expires].clone.gmtime. + strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if value[:httponly] + value = value[:value] + end + value = [value] unless Array === value + cookie = Rack::Utils.escape(key) + "=" + + value.map { |v| Rack::Utils.escape v }.join("&") + + "#{domain}#{path}#{expires}#{secure}#{httponly}" + + @cookie << cookie + end + + def delete_cookie(key, value={}) + @cookie.reject! { |cookie| + cookie =~ /\A#{Rack::Utils.escape(key)}=/ + } + + set_cookie(key, + {:value => '', :path => nil, :domain => nil, + :expires => Time.at(0) }.merge(value)) + end + private def handle_conditional_get! - if etag? || last_modified? || !cache_control.empty? + if etag? || last_modified? || !@cache_control.empty? set_conditional_cache_control! elsif nonempty_ok_response? self.etag = @body @@ -227,21 +252,18 @@ module ActionDispatch # :nodoc: end end - EMPTY_RESPONSE = [" "] - def nonempty_ok_response? - ok = !@status || @status == 200 - ok && string_body? && @body != EMPTY_RESPONSE + @status == 200 && string_body? end def string_body? - !body_parts.respond_to?(:call) && body_parts.any? && body_parts.all? { |part| part.is_a?(String) } + !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) } end DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" def set_conditional_cache_control! - control = cache_control + control = @cache_control if control.empty? headers["Cache-Control"] = DEFAULT_CACHE_CONTROL diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index b317b6dc1a..c2ccd1d3a5 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -72,7 +72,6 @@ module ActionView @response = ActionController::TestResponse.new @params = {} - send(:initialize_current_url) end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 346fa09414..f1c93e6b1e 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -536,7 +536,6 @@ class FragmentCachingTest < ActionController::TestCase @controller.params = @params @controller.request = @request @controller.response = @response - @controller.send(:initialize_current_url) @controller.send(:initialize_template_class, @response) @controller.send(:assign_shortcuts, @request, @response) end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 448db538ab..e28df8efa5 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -62,19 +62,27 @@ module ActiveSupport end end + RAILS_CACHE_ID = ENV["RAILS_CACHE_ID"] + RAILS_APP_VERION = ENV["RAILS_APP_VERION"] + EXPANDED_CACHE = RAILS_CACHE_ID || RAILS_APP_VERION + def self.expand_cache_key(key, namespace = nil) expanded_cache_key = namespace ? "#{namespace}/" : "" - if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] - expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/" + if EXPANDED_CACHE + expanded_cache_key << "#{RAILS_CACHE_ID || RAILS_APP_VERION}/" end - expanded_cache_key << case - when key.respond_to?(:cache_key) + expanded_cache_key << + if key.respond_to?(:cache_key) key.cache_key - when key.is_a?(Array) - key.collect { |element| expand_cache_key(element) }.to_param - when key + elsif key.is_a?(Array) + if key.size > 1 + key.collect { |element| expand_cache_key(element) }.to_param + else + key.first.to_param + end + elsif key key.to_param end.to_s -- cgit v1.2.3 From ccd1c5e521c020118b5bcfeb4cd5a651997b6806 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Mon, 10 Aug 2009 15:49:53 -0700 Subject: Allow superclass_delegating_accessor to take a block for initial set. --- .../lib/active_support/core_ext/class/delegating_attributes.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index fd029544c3..6c67df7f50 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -26,13 +26,14 @@ class Class end end - def superclass_delegating_writer(*names) + def superclass_delegating_writer(*names, &block) names.each do |name| class_eval(<<-EOS, __FILE__, __LINE__ + 1) def self.#{name}=(value) # def self.property=(value) @#{name} = value # @property = value end # end EOS + self.send("#{name}=", yield) if block_given? end end @@ -42,8 +43,8 @@ class Class # delegate to their superclass unless they have been given a # specific value. This stops the strange situation where values # set after class definition don't get applied to subclasses. - def superclass_delegating_accessor(*names) + def superclass_delegating_accessor(*names, &block) superclass_delegating_reader(*names) - superclass_delegating_writer(*names) + superclass_delegating_writer(*names, &block) end end -- cgit v1.2.3 From 27e880729eeb058583b79ccf84a7577c434addfd Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Tue, 11 Aug 2009 16:49:27 -0700 Subject: Made benchmarks submodule so it's easier to keep in sync --- .gitmodules | 3 + actionpack/examples | 1 + actionpack/examples/minimal.rb | 132 ----------------------- actionpack/examples/very_simple.rb | 50 --------- actionpack/examples/views/_collection.erb | 1 - actionpack/examples/views/_hello.erb | 1 - actionpack/examples/views/_hundred_partials.erb | 12 --- actionpack/examples/views/_many_partials.erb | 10 -- actionpack/examples/views/_partial.erb | 10 -- actionpack/examples/views/layouts/alt.html.erb | 1 - actionpack/examples/views/layouts/kaigi.html.erb | 1 - actionpack/examples/views/template.html.erb | 1 - 12 files changed, 4 insertions(+), 219 deletions(-) create mode 100644 .gitmodules create mode 160000 actionpack/examples delete mode 100644 actionpack/examples/minimal.rb delete mode 100644 actionpack/examples/very_simple.rb delete mode 100644 actionpack/examples/views/_collection.erb delete mode 100644 actionpack/examples/views/_hello.erb delete mode 100644 actionpack/examples/views/_hundred_partials.erb delete mode 100644 actionpack/examples/views/_many_partials.erb delete mode 100644 actionpack/examples/views/_partial.erb delete mode 100644 actionpack/examples/views/layouts/alt.html.erb delete mode 100644 actionpack/examples/views/layouts/kaigi.html.erb delete mode 100644 actionpack/examples/views/template.html.erb diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..d6730e194a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "actionpack/examples"] + path = actionpack/examples + url = git://github.com/wycats/rails-simple-benches.git diff --git a/actionpack/examples b/actionpack/examples new file mode 160000 index 0000000000..4e1327f06d --- /dev/null +++ b/actionpack/examples @@ -0,0 +1 @@ +Subproject commit 4e1327f06da6df1a1981d69c04e8d6463b38a4c1 diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb deleted file mode 100644 index 62b71de2cb..0000000000 --- a/actionpack/examples/minimal.rb +++ /dev/null @@ -1,132 +0,0 @@ -# Pass NEW=1 to run with the new Base -ENV['RAILS_ENV'] ||= 'production' -ENV['NO_RELOAD'] ||= '1' - -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../lib" -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" -require 'action_controller' -require 'action_controller/new_base' if ENV['NEW'] -require 'action_view' -require 'benchmark' - -class Runner - def initialize(app, output) - @app, @output = app, output - end - - def puts(*) - super if @output - end - - def call(env) - env['n'].to_i.times { @app.call(env) } - @app.call(env).tap { |response| report(env, response) } - end - - def report(env, response) - return unless ENV["DEBUG"] - out = env['rack.errors'] - out.puts response[0], response[1].to_yaml, '---' - response[2].each { |part| out.puts part } - out.puts '---' - end - - def self.puts(*) - super if @output - end - - def self.run(app, n, label, output = true) - @output = output - puts label, '=' * label.size if label - env = Rack::MockRequest.env_for("/").merge('n' => n, 'rack.input' => StringIO.new(''), 'rack.errors' => $stdout) - t = Benchmark.realtime { new(app, output).call(env) } - puts "%d ms / %d req = %.1f usec/req" % [10**3 * t, n, 10**6 * t / n] - puts - end -end - - -N = (ENV['N'] || 1000).to_i - -module ActionController::Rails2Compatibility - instance_methods.each do |name| - remove_method name - end -end - -class BasePostController < ActionController::Base - append_view_path "#{File.dirname(__FILE__)}/views" - - def overhead - self.response_body = '' - end - - def index - render :text => '' - end - - def partial - render :partial => "/partial" - end - - def many_partials - render :partial => "/many_partials" - end - - def hundred_partials - render :partial => "/hundred_partials" - end - - def partial_collection - render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10] - end - - def large_collection - render :partial => "/collection", :collection => (1...1000).to_a - end - - def show_template - render :template => "template" - end -end - -OK = [200, {}, []] -MetalPostController = lambda { OK } - -class HttpPostController < ActionController::Metal - def index - self.response_body = '' - end -end - -ActionController::Base.use_accept_header = false - -unless ENV["PROFILE"] - Runner.run(BasePostController.action(:overhead), 1, 'overhead', false) - Runner.run(BasePostController.action(:index), 1, 'index', false) - Runner.run(BasePostController.action(:show_template), 1, 'template', false) - Runner.run(BasePostController.action(:partial), 1, 'partial', false) - Runner.run(BasePostController.action(:many_partials), 1, 'many_partials', false) - Runner.run(BasePostController.action(:partial_collection), 1, 'collection', false) - Runner.run(BasePostController.action(:hundred_partials), 1, 'hundred_partials', false) - Runner.run(BasePostController.action(:large_collection), 1, 'large_collection', false) - - (ENV["M"] || 1).to_i.times do - Runner.run(BasePostController.action(:overhead), N, 'overhead') - Runner.run(BasePostController.action(:index), N, 'index') - Runner.run(BasePostController.action(:show_template), N, 'template') - Runner.run(BasePostController.action(:partial), N, 'partial') - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') - Runner.run(BasePostController.action(:partial_collection), N, 'collection') - Runner.run(BasePostController.action(:hundred_partials), N, 'hundred_partials') - Runner.run(BasePostController.action(:large_collection), N, 'large_collection') - end -else - Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), 1, ENV["PROFILE"]) - require "ruby-prof" - RubyProf.start - Runner.run(BasePostController.action(ENV["PROFILE"].to_sym), N, ENV["PROFILE"]) - result = RubyProf.stop - printer = RubyProf::CallStackPrinter.new(result) - printer.print(File.open("output.html", "w")) -end \ No newline at end of file diff --git a/actionpack/examples/very_simple.rb b/actionpack/examples/very_simple.rb deleted file mode 100644 index 6714185172..0000000000 --- a/actionpack/examples/very_simple.rb +++ /dev/null @@ -1,50 +0,0 @@ -$:.push "rails/activesupport/lib" -$:.push "rails/actionpack/lib" - -require "action_controller" - -class Kaigi < ActionController::Metal - include AbstractController::Callbacks - include ActionController::RackConvenience - include ActionController::RenderingController - include ActionController::Layouts - include ActionView::Context - - before_filter :set_name - append_view_path "views" - - def view_context - self - end - - def controller - self - end - - DEFAULT_LAYOUT = Object.new.tap {|l| def l.render(*) yield end } - - def render_template(template, layout = DEFAULT_LAYOUT, options = {}, partial = false) - ret = template.render(self, {}) - layout.render(self, {}) { ret } - end - - def index - render :template => "template" - end - - def alt - render :template => "template", :layout => "alt" - end - - private - def set_name - @name = params[:name] - end -end - -app = Rack::Builder.new do - map("/kaigi") { run Kaigi.action(:index) } - map("/kaigi/alt") { run Kaigi.action(:alt) } -end.to_app - -Rack::Handler::Mongrel.run app, :Port => 3000 diff --git a/actionpack/examples/views/_collection.erb b/actionpack/examples/views/_collection.erb deleted file mode 100644 index bcfe958e2c..0000000000 --- a/actionpack/examples/views/_collection.erb +++ /dev/null @@ -1 +0,0 @@ -<%= collection %> \ No newline at end of file diff --git a/actionpack/examples/views/_hello.erb b/actionpack/examples/views/_hello.erb deleted file mode 100644 index 5ab2f8a432..0000000000 --- a/actionpack/examples/views/_hello.erb +++ /dev/null @@ -1 +0,0 @@ -Hello \ No newline at end of file diff --git a/actionpack/examples/views/_hundred_partials.erb b/actionpack/examples/views/_hundred_partials.erb deleted file mode 100644 index 15e99c1b68..0000000000 --- a/actionpack/examples/views/_hundred_partials.erb +++ /dev/null @@ -1,12 +0,0 @@ -<% 10.times do %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> - <%= render :partial => '/hello' %> -<% end %> \ No newline at end of file diff --git a/actionpack/examples/views/_many_partials.erb b/actionpack/examples/views/_many_partials.erb deleted file mode 100644 index 7e379d46f5..0000000000 --- a/actionpack/examples/views/_many_partials.erb +++ /dev/null @@ -1,10 +0,0 @@ -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> -<%= render :partial => '/hello' %> \ No newline at end of file diff --git a/actionpack/examples/views/_partial.erb b/actionpack/examples/views/_partial.erb deleted file mode 100644 index 3ca8e80b52..0000000000 --- a/actionpack/examples/views/_partial.erb +++ /dev/null @@ -1,10 +0,0 @@ -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> -<%= "Hello" %> diff --git a/actionpack/examples/views/layouts/alt.html.erb b/actionpack/examples/views/layouts/alt.html.erb deleted file mode 100644 index c4816337a6..0000000000 --- a/actionpack/examples/views/layouts/alt.html.erb +++ /dev/null @@ -1 +0,0 @@ -+ <%= yield %> + \ No newline at end of file diff --git a/actionpack/examples/views/layouts/kaigi.html.erb b/actionpack/examples/views/layouts/kaigi.html.erb deleted file mode 100644 index 274607a96a..0000000000 --- a/actionpack/examples/views/layouts/kaigi.html.erb +++ /dev/null @@ -1 +0,0 @@ -Hello <%= yield %> Goodbye \ No newline at end of file diff --git a/actionpack/examples/views/template.html.erb b/actionpack/examples/views/template.html.erb deleted file mode 100644 index 5ab2f8a432..0000000000 --- a/actionpack/examples/views/template.html.erb +++ /dev/null @@ -1 +0,0 @@ -Hello \ No newline at end of file -- cgit v1.2.3 From ba67e256b8bb3749090fcf7ccaff497ea006b1d1 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Tue, 11 Aug 2009 23:44:44 -0700 Subject: Remove submodule --- .gitmodules | 3 --- actionpack/examples | 1 - 2 files changed, 4 deletions(-) delete mode 160000 actionpack/examples diff --git a/.gitmodules b/.gitmodules index d6730e194a..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "actionpack/examples"] - path = actionpack/examples - url = git://github.com/wycats/rails-simple-benches.git diff --git a/actionpack/examples b/actionpack/examples deleted file mode 160000 index 4e1327f06d..0000000000 --- a/actionpack/examples +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4e1327f06da6df1a1981d69c04e8d6463b38a4c1 -- cgit v1.2.3 From f413a703ba5bcf4b369932ae805615c4a34d34cb Mon Sep 17 00:00:00 2001 From: Chad Woolley Date: Tue, 11 Aug 2009 22:53:55 -0700 Subject: make mysql and postgresql rebuild databases on every CI build, to prevent breakages such as collation and character set changing Signed-off-by: Yehuda Katz --- ci/ci_build.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/ci_build.rb b/ci/ci_build.rb index 0b9bd3d278..7d81fa843a 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -28,14 +28,14 @@ cd "#{root_dir}/activerecord" do puts puts "[CruiseControl] Building ActiveRecord with MySQL" puts - build_results[:activerecord_mysql] = system 'rake test_mysql' + build_results[:activerecord_mysql] = system 'rake mysql:rebuild_databases && rake test_mysql' end cd "#{root_dir}/activerecord" do puts puts "[CruiseControl] Building ActiveRecord with PostgreSQL" puts - build_results[:activerecord_postgresql8] = system 'rake test_postgresql' + build_results[:activerecord_postgresql8] = system 'rake postgresql:rebuild_databases && rake test_postgresql' end cd "#{root_dir}/activerecord" do -- cgit v1.2.3 From 4f9047ecc8f0e2c3d4491bae0051679569da71dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 13 Aug 2009 10:27:41 +0200 Subject: Ensure collections are not treated as nested resources. --- .../lib/action_controller/metal/mime_responds.rb | 5 ++-- .../lib/action_controller/metal/responder.rb | 10 ++++---- actionpack/test/controller/mime_responds_test.rb | 27 ++++++++++++++++------ actionpack/test/lib/controller/fake_models.rb | 12 ++++++---- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index c8d042acb5..950105e63f 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -226,10 +226,11 @@ module ActionController #:nodoc: # is quite simple (it just needs to respond to call), you can even give # a proc to it. # - def respond_with(resource, options={}, &block) + def respond_with(*resources, &block) respond_to(&block) rescue ActionView::MissingTemplate - (options.delete(:responder) || responder).call(self, resource, options) + options = resources.extract_options! + (options.delete(:responder) || responder).call(self, resources, options) end def responder diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 9ed99ca623..fc01a0924a 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -64,7 +64,7 @@ module ActionController #:nodoc: # @project = Project.find(params[:project_id]) # @task = @project.comments.build(params[:task]) # flash[:notice] = 'Task was successfully created.' if @task.save - # respond_with([@project, @task]) + # respond_with(@project, @task) # end # # Giving an array of resources, you ensure that the responder will redirect to @@ -74,19 +74,19 @@ module ActionController #:nodoc: # polymorphic urls. If a project has one manager which has many tasks, it # should be invoked as: # - # respond_with([@project, :manager, @task]) + # respond_with(@project, :manager, @task) # # Check polymorphic_url documentation for more examples. # class Responder attr_reader :controller, :request, :format, :resource, :resource_location, :options - def initialize(controller, resource, options={}) + def initialize(controller, resources, options={}) @controller = controller @request = controller.request @format = controller.formats.first - @resource = resource.is_a?(Array) ? resource.last : resource - @resource_location = options[:location] || resource + @resource = resources.is_a?(Array) ? resources.last : resources + @resource_location = options[:location] || resources @options = options end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 8319b5c573..2e2dba5aae 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -497,8 +497,12 @@ class RespondWithController < ActionController::Base respond_with(Customer.new("david", 13)) end + def using_resource_with_collection + respond_with([Customer.new("david", 13), Customer.new("jamis", 9)]) + end + def using_resource_with_parent - respond_with([Quiz::Store.new("developer?", 11), Customer.new("david", 13)]) + respond_with(Quiz::Store.new("developer?", 11), Customer.new("david", 13)) end def using_resource_with_status_and_location @@ -506,7 +510,7 @@ class RespondWithController < ActionController::Base end def using_resource_with_responder - responder = proc { |c, r, o| c.render :text => "Resource name is #{r.name}" } + responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } respond_with(Customer.new("david", 13), :responder => responder) end @@ -592,7 +596,7 @@ class RespondWithControllerTest < ActionController::TestCase @request.accept = "application/xml" get :using_resource assert_equal "application/xml", @response.content_type - assert_equal "XML", @response.body + assert_equal "david", @response.body @request.accept = "application/json" assert_raise ActionView::MissingTemplate do @@ -622,7 +626,7 @@ class RespondWithControllerTest < ActionController::TestCase post :using_resource assert_equal "application/xml", @response.content_type assert_equal 201, @response.status - assert_equal "XML", @response.body + assert_equal "david", @response.body assert_equal "http://www.example.com/customers/13", @response.location errors = { :name => :invalid } @@ -689,7 +693,7 @@ class RespondWithControllerTest < ActionController::TestCase get :using_resource_with_parent assert_equal "application/xml", @response.content_type assert_equal 200, @response.status - assert_equal "XML", @response.body + assert_equal "david", @response.body end def test_using_resource_with_parent_for_post @@ -698,7 +702,7 @@ class RespondWithControllerTest < ActionController::TestCase post :using_resource_with_parent assert_equal "application/xml", @response.content_type assert_equal 201, @response.status - assert_equal "XML", @response.body + assert_equal "david", @response.body assert_equal "http://www.example.com/quiz_stores/11/customers/13", @response.location errors = { :name => :invalid } @@ -710,6 +714,15 @@ class RespondWithControllerTest < ActionController::TestCase assert_nil @response.location end + def test_using_resource_with_collection + @request.accept = "application/xml" + get :using_resource_with_collection + assert_equal "application/xml", @response.content_type + assert_equal 200, @response.status + assert_match /david<\/name>/, @response.body + assert_match /jamis<\/name>/, @response.body + end + def test_clear_respond_to @controller = InheritedRespondWithController.new @request.accept = "text/html" @@ -722,7 +735,7 @@ class RespondWithControllerTest < ActionController::TestCase @request.accept = "*/*" get :index assert_equal "application/xml", @response.content_type - assert_equal "XML", @response.body + assert_equal "david", @response.body end def test_no_double_render_is_raised diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 0faf8f3f9a..18eff7516b 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -10,12 +10,16 @@ class Customer < Struct.new(:name, :id) id.to_s end - def to_xml - "XML" + def to_xml(options={}) + if options[:builder] + options[:builder].name name + else + "#{name}" + end end - def to_js - "JS" + def to_js(options={}) + "name: #{name.inspect}" end def errors -- cgit v1.2.3 From 7a26c21d8e853ed648e4668843a3958de4ac5791 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 13 Aug 2009 21:02:49 -0500 Subject: Use safe tmp dir --- actionpack/test/abstract_unit.rb | 2 ++ actionpack/test/tmp/.gitignore | 0 2 files changed, 2 insertions(+) create mode 100644 actionpack/test/tmp/.gitignore diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index b062a71442..eb4e2fb585 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -6,6 +6,8 @@ $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') +ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') + ENV['new_base'] = "true" $stderr.puts "Running old tests on new_base" diff --git a/actionpack/test/tmp/.gitignore b/actionpack/test/tmp/.gitignore new file mode 100644 index 0000000000..e69de29bb2 -- cgit v1.2.3 From c6bc8e662614be711f45a8d4b231d5f993b024a7 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 13 Aug 2009 22:27:09 -0500 Subject: Break up concerns for choosing what attributes should be serialized and the actual serializer --- activemodel/lib/active_model.rb | 2 +- activemodel/lib/active_model/serialization.rb | 30 ++++++++ activemodel/lib/active_model/serializer.rb | 60 ---------------- activemodel/lib/active_model/serializers/json.rb | 18 ++--- activemodel/lib/active_model/serializers/xml.rb | 76 +++++++++++++------- activerecord/lib/active_record/serialization.rb | 80 +++++++++++----------- .../active_record/serializers/json_serializer.rb | 14 ---- .../active_record/serializers/xml_serializer.rb | 19 ++--- 8 files changed, 136 insertions(+), 163 deletions(-) create mode 100644 activemodel/lib/active_model/serialization.rb delete mode 100644 activemodel/lib/active_model/serializer.rb delete mode 100644 activerecord/lib/active_record/serializers/json_serializer.rb diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index b24a929ff5..244f3a546e 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -35,7 +35,7 @@ module ActiveModel autoload :Naming, 'active_model/naming' autoload :Observer, 'active_model/observing' autoload :Observing, 'active_model/observing' - autoload :Serializer, 'active_model/serializer' + autoload :Serialization, 'active_model/serialization' autoload :StateMachine, 'active_model/state_machine' autoload :TestCase, 'active_model/test_case' autoload :Validations, 'active_model/validations' diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb new file mode 100644 index 0000000000..4c0073f687 --- /dev/null +++ b/activemodel/lib/active_model/serialization.rb @@ -0,0 +1,30 @@ +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' + +module ActiveModel + module Serialization + def serializable_hash(options = nil) + options ||= {} + + options[:only] = Array.wrap(options[:only]).map { |n| n.to_s } + options[:except] = Array.wrap(options[:except]).map { |n| n.to_s } + + attribute_names = attributes.keys.sort + if options[:only].any? + attribute_names &= options[:only] + elsif options[:except].any? + attribute_names -= options[:except] + end + + method_names = Array.wrap(options[:methods]).inject([]) do |methods, name| + methods << name if respond_to?(name.to_s) + methods + end + + (attribute_names + method_names).inject({}) { |hash, name| + hash[name] = send(name) + hash + } + end + end +end diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb deleted file mode 100644 index 5b603bdbd7..0000000000 --- a/activemodel/lib/active_model/serializer.rb +++ /dev/null @@ -1,60 +0,0 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' - -module ActiveModel - class Serializer - attr_reader :options - - def initialize(serializable, options = nil) - @serializable = serializable - @options = options ? options.dup : {} - - @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s } - @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s } - end - - def serialize - raise NotImplemented - end - - def to_s(&block) - serialize(&block) - end - - # To replicate the behavior in ActiveRecord#attributes, - # :except takes precedence over :only. If :only is not set - # for a N level model but is set for the N+1 level models, - # then because :except is set to a default value, the second - # level model can have both :except and :only set. So if - # :only is set, always delete :except. - def serializable_attribute_names - attribute_names = @serializable.attributes.keys.sort - - if options[:only].any? - attribute_names &= options[:only] - elsif options[:except].any? - attribute_names -= options[:except] - end - - attribute_names - end - - def serializable_method_names - Array.wrap(options[:methods]).inject([]) do |methods, name| - methods << name if @serializable.respond_to?(name.to_s) - methods - end - end - - def serializable_names - serializable_attribute_names + serializable_method_names - end - - def serializable_hash - serializable_names.inject({}) { |hash, name| - hash[name] = @serializable.send(name) - hash - } - end - end -end diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index e94512fd64..ee6d48bfc6 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -5,6 +5,7 @@ module ActiveModel module Serializers module JSON extend ActiveSupport::Concern + include ActiveModel::Serialization included do extend ActiveModel::Naming @@ -12,19 +13,6 @@ module ActiveModel cattr_accessor :include_root_in_json, :instance_writer => false end - class Serializer < ActiveModel::Serializer - def serializable_hash - model = super - @serializable.include_root_in_json ? - { @serializable.class.model_name.element => model } : - model - end - - def serialize - ActiveSupport::JSON.encode(serializable_hash) - end - end - # Returns a JSON string representing the model. Some configuration is # available through +options+. # @@ -92,7 +80,9 @@ module ActiveModel # {"comments": [{"body": "Don't think too hard"}], # "title": "So I was thinking"}]} def encode_json(encoder) - Serializer.new(self, encoder.options).to_s + hash = serializable_hash(encoder.options) + hash = { self.class.model_name.element => hash } if include_root_in_json + ActiveSupport::JSON.encode(hash) end def as_json(options = nil) diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 4508a39347..86149f1e5f 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -5,8 +5,9 @@ module ActiveModel module Serializers module Xml extend ActiveSupport::Concern + include ActiveModel::Serialization - class Serializer < ActiveModel::Serializer #:nodoc: + class Serializer #:nodoc: class Attribute #:nodoc: attr_reader :name, :value, :type @@ -74,32 +75,32 @@ module ActiveModel end end - def builder - @builder ||= begin - require 'builder' unless defined? ::Builder - options[:indent] ||= 2 - builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) + attr_reader :options - unless options[:skip_instruct] - builder.instruct! - options[:skip_instruct] = true - end + def initialize(serializable, options = nil) + @serializable = serializable + @options = options ? options.dup : {} - builder - end + @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s } + @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s } end - def root - root = (options[:root] || @serializable.class.model_name.singular).to_s - reformat_name(root) - end - - def dasherize? - !options.has_key?(:dasherize) || options[:dasherize] - end + # To replicate the behavior in ActiveRecord#attributes, + # :except takes precedence over :only. If :only is not set + # for a N level model but is set for the N+1 level models, + # then because :except is set to a default value, the second + # level model can have both :except and :only set. So if + # :only is set, always delete :except. + def serializable_attribute_names + attribute_names = @serializable.attributes.keys.sort + + if options[:only].any? + attribute_names &= options[:only] + elsif options[:except].any? + attribute_names -= options[:except] + end - def camelize? - options.has_key?(:camelize) && options[:camelize] + attribute_names end def serializable_attributes @@ -134,6 +135,34 @@ module ActiveModel end private + def builder + @builder ||= begin + require 'builder' unless defined? ::Builder + options[:indent] ||= 2 + builder = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) + + unless options[:skip_instruct] + builder.instruct! + options[:skip_instruct] = true + end + + builder + end + end + + def root + root = (options[:root] || @serializable.class.model_name.singular).to_s + reformat_name(root) + end + + def dasherize? + !options.has_key?(:dasherize) || options[:dasherize] + end + + def camelize? + options.has_key?(:camelize) && options[:camelize] + end + def reformat_name(name) name = name.camelize if camelize? dasherize? ? name.dasherize : name @@ -163,8 +192,7 @@ module ActiveModel end def to_xml(options = {}, &block) - serializer = Serializer.new(self, options) - block_given? ? serializer.to_s(&block) : serializer.to_s + Serializer.new(self, options).serialize(&block) end def from_xml(xml) diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 94f1e8f1fd..b49471f7ab 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -1,60 +1,58 @@ module ActiveRecord #:nodoc: module Serialization - module RecordSerializer #:nodoc: - def initialize(*args) - super - options[:except] |= Array.wrap(@serializable.class.inheritance_column) + extend ActiveSupport::Concern + include ActiveModel::Serializers::JSON + + def serializable_hash(options = nil) + options ||= {} + + options[:except] = Array.wrap(options[:except]).map { |n| n.to_s } + options[:except] |= Array.wrap(self.class.inheritance_column) + + hash = super(options) + + serializable_add_includes(options) do |association, records, opts| + hash[association] = records.is_a?(Enumerable) ? + records.map { |r| r.serializable_hash(opts) } : + records.serializable_hash(opts) end + hash + end + + private # Add associations specified via the :includes option. # Expects a block that takes as arguments: # +association+ - name of the association # +records+ - the association record(s) to be serialized # +opts+ - options for the association records - def add_includes(&block) - if include_associations = options.delete(:include) - base_only_or_except = { :except => options[:except], - :only => options[:only] } - - include_has_options = include_associations.is_a?(Hash) - associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - - for association in associations - records = case @serializable.class.reflect_on_association(association).macro - when :has_many, :has_and_belongs_to_many - @serializable.send(association).to_a - when :has_one, :belongs_to - @serializable.send(association) - end - - unless records.nil? - association_options = include_has_options ? include_associations[association] : base_only_or_except - opts = options.merge(association_options) - yield(association, records, opts) - end - end + def serializable_add_includes(options = {}) + return unless include_associations = options.delete(:include) - options[:include] = include_associations - end - end + base_only_or_except = { :except => options[:except], + :only => options[:only] } + + include_has_options = include_associations.is_a?(Hash) + associations = include_has_options ? include_associations.keys : Array.wrap(include_associations) - def serializable_hash - hash = super + for association in associations + records = case self.class.reflect_on_association(association).macro + when :has_many, :has_and_belongs_to_many + send(association).to_a + when :has_one, :belongs_to + send(association) + end - add_includes do |association, records, opts| - hash[association] = - if records.is_a?(Enumerable) - records.collect { |r| self.class.new(r, opts).serializable_hash } - else - self.class.new(records, opts).serializable_hash - end + unless records.nil? + association_options = include_has_options ? include_associations[association] : base_only_or_except + opts = options.merge(association_options) + yield(association, records, opts) + end end - hash + options[:include] = include_associations end - end end end require 'active_record/serializers/xml_serializer' -require 'active_record/serializers/json_serializer' diff --git a/activerecord/lib/active_record/serializers/json_serializer.rb b/activerecord/lib/active_record/serializers/json_serializer.rb deleted file mode 100644 index 63bf42c09d..0000000000 --- a/activerecord/lib/active_record/serializers/json_serializer.rb +++ /dev/null @@ -1,14 +0,0 @@ -module ActiveRecord #:nodoc: - module Serialization - extend ActiveSupport::Concern - include ActiveModel::Serializers::JSON - - class JSONSerializer < ActiveModel::Serializers::JSON::Serializer - include Serialization::RecordSerializer - end - - def encode_json(encoder) - JSONSerializer.new(self, encoder.options).to_s - end - end -end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 4e172bd2b6..b19920741e 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -2,6 +2,8 @@ require 'active_support/core_ext/hash/conversions' module ActiveRecord #:nodoc: module Serialization + include ActiveModel::Serializers::Xml + # Builds an XML document to represent the model. Some configuration is # available through +options+. However more complicated cases should # override ActiveRecord::Base#to_xml. @@ -169,18 +171,15 @@ module ActiveRecord #:nodoc: # end # end def to_xml(options = {}, &block) - serializer = XmlSerializer.new(self, options) - block_given? ? serializer.to_s(&block) : serializer.to_s - end - - def from_xml(xml) - self.attributes = Hash.from_xml(xml).values.first - self + XmlSerializer.new(self, options).serialize(&block) end end class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: - include Serialization::RecordSerializer + def initialize(*args) + super + options[:except] |= Array.wrap(@serializable.class.inheritance_column) + end def serializable_attributes serializable_attribute_names.collect { |name| Attribute.new(name, @serializable) } @@ -235,7 +234,9 @@ module ActiveRecord #:nodoc: builder.tag!(*args) do add_attributes procs = options.delete(:procs) - add_includes { |association, records, opts| add_associations(association, records, opts) } + @serializable.send(:serializable_add_includes, options) { |association, records, opts| + add_associations(association, records, opts) + } options[:procs] = procs add_procs yield builder if block_given? -- cgit v1.2.3