diff options
author | Emilio Tagua <miloops@gmail.com> | 2009-08-14 12:34:38 -0300 |
---|---|---|
committer | Emilio Tagua <miloops@gmail.com> | 2009-08-14 12:34:38 -0300 |
commit | 48c1e6d584409d774b184e0f4d2ef28e5b0e9c90 (patch) | |
tree | a2becf0cce37a47579e848780b02f420d335ccd9 | |
parent | 9ac01fad193b27e517ea772e0d1e13e06f4ddf34 (diff) | |
parent | c6bc8e662614be711f45a8d4b231d5f993b024a7 (diff) | |
download | rails-48c1e6d584409d774b184e0f4d2ef28e5b0e9c90.tar.gz rails-48c1e6d584409d774b184e0f4d2ef28e5b0e9c90.tar.bz2 rails-48c1e6d584409d774b184e0f4d2ef28e5b0e9c90.zip |
Merge commit 'rails/master'
49 files changed, 506 insertions, 603 deletions
diff --git a/actionpack/examples/minimal.rb b/actionpack/examples/minimal.rb deleted file mode 100644 index a9015da053..0000000000 --- a/actionpack/examples/minimal.rb +++ /dev/null @@ -1,118 +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 partial_collection - render :partial => "/collection", :collection => [1,2,3,4,5,6,7,8,9,10] - 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 - -unless ENV["PROFILE"] - Runner.run(BasePostController.action(:overhead), N, 'overhead', false) - Runner.run(BasePostController.action(:index), N, 'index', 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(: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(:partial), N, 'partial') - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') - Runner.run(BasePostController.action(:partial_collection), N, 'collection') - Runner.run(BasePostController.action(:show_template), N, 'template') - end -else - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') - require "ruby-prof" - RubyProf.start - Runner.run(BasePostController.action(:many_partials), N, 'many_partials') - 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/_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 diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 0063d54149..ac2154dffc 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -6,13 +6,51 @@ 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 + values = details.values_at(:formats, :locale) + + # Cache nil + if layout.key?(values) + return layout[values] + else + layout[values] = yield + 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. @@ -31,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 @@ -76,10 +116,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 @@ -136,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 @@ -162,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 f94d1c669c..5b0165f0e7 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 @@ -101,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/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/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/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 d32d5562e8..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 = Rack::Utils::HeaderHash.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 4190fa21cd..b23306af62 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -173,19 +173,15 @@ module ActionDispatch end end - # 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 + if param = parameters[:format] + Array.wrap(Mime[param]) + else + accepts.dup end else - raw_formats + Mime::SET + [format] end end @@ -487,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) @@ -500,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 03d1780b77..055f29a972 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,18 +32,35 @@ module ActionDispatch # :nodoc: # end # end class Response < Rack::Response - attr_accessor :request + attr_accessor :request, :blank 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 = {} @cache_control = {} - @header = Rack::Utils::HeaderHash.new + + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 + + @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 + str end def status=(status) @@ -71,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 @@ -113,41 +133,39 @@ 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 - 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].present? @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! 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) @@ -168,23 +186,12 @@ 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'] 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 @@ -196,12 +203,43 @@ 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 + self.etag = @body if request && request.etag_matches?(etag) self.status = 304 @@ -215,29 +253,33 @@ module ActionDispatch # :nodoc: end def nonempty_ok_response? - ok = !@status || @status == 200 - ok && string_body? + @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! - if cache_control.empty? - cache_control.merge!(:public => false, :max_age => 0, :must_revalidate => true) - end + control = @cache_control + + if control.empty? + headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + else + extras = control[:extras] + max_age = control[:max_age] - public_cache, max_age, must_revalidate, extras = - cache_control.values_at(:public, :max_age, :must_revalidate, :extras) + 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/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 64f08c447d..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] || {} @@ -207,9 +208,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 +216,46 @@ 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] || :object + + 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 + + segments << template.render(@view, locals) + end + segments + end + + def collection_without_template + options = @options + + segments, locals, as = [], @locals, options[:as] || :object + index, template = -1, nil + + 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 - 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) + segments << template.render(@view, locals) end - segments.join(spacer) + @options[:_template] = template + segments end def render_template(template, object = @object) diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index ebfc6cc8ce..10f664736f 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 @@ -60,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) @@ -105,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/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index e51744d095..c2ccd1d3a5 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,9 +70,8 @@ module ActionView def initialize @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - + @params = {} - send(:initialize_current_url) end end 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/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/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 "<name>david</name>", @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 "<name>david</name>", @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 "<name>david</name>", @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 "<name>david</name>", @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 /<name>david<\/name>/, @response.body + assert_match /<name>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 "<name>david</name>", @response.body end def test_no_double_render_is_raised 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) 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 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>#{name}</name>" + end end - def to_js - "JS" + def to_js(options={}) + "name: #{name.inspect}" end def errors diff --git a/actionpack/test/tmp/.gitignore b/actionpack/test/tmp/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/actionpack/test/tmp/.gitignore 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, - # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set - # for a N level model but is set for the N+1 level models, - # then because <tt>:except</tt> is set to a default value, the second - # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if - # <tt>:only</tt> is set, always delete <tt>:except</tt>. - 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, + # <tt>:except</tt> takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set + # for a N level model but is set for the N+1 level models, + # then because <tt>:except</tt> is set to a default value, the second + # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if + # <tt>:only</tt> is set, always delete <tt>:except</tt>. + 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 <tt>:includes</tt> 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? 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 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 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 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 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/plugins/mspec/lib/generators/mspec_generator.rb index 191bdbf2fc..191bdbf2fc 100644 --- a/railties/test/fixtures/vendor/gems/gems/mspec/lib/generators/mspec_generator.rb +++ b/railties/test/fixtures/vendor/plugins/mspec/lib/generators/mspec_generator.rb 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 |