diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-05-15 21:54:46 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-05-15 21:54:46 +0100 |
commit | 879493c35fd8d9e12e5cf3e56cd67ff07c3345c5 (patch) | |
tree | 9615859e1d9a52f71da444b0b7359817bb6acc50 | |
parent | d6ecce66f4e125531875006eea8022b73fe135b5 (diff) | |
parent | fc02eabf296d6edb74a95174c7322293a54c9492 (diff) | |
download | rails-879493c35fd8d9e12e5cf3e56cd67ff07c3345c5.tar.gz rails-879493c35fd8d9e12e5cf3e56cd67ff07c3345c5.tar.bz2 rails-879493c35fd8d9e12e5cf3e56cd67ff07c3345c5.zip |
Merge commit 'mainstream/master'
Conflicts:
actionmailer/lib/action_mailer/base.rb
84 files changed, 733 insertions, 483 deletions
diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 15c9964d9b..b2ce462f0c 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,4 +1,4 @@ -*SVN* +*2.1.0 RC1 (May 11th, 2008)* * Fixed that a return-path header would be ignored #7572 [joost] diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE index 007cc942e1..13c90d46e9 100644 --- a/actionmailer/MIT-LICENSE +++ b/actionmailer/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2007 David Heinemeier Hansson +Copyright (c) 2004-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 75c3eb2db9..22ed396417 100755 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -55,7 +55,7 @@ spec = Gem::Specification.new do |s| s.rubyforge_project = "actionmailer" s.homepage = "http://www.rubyonrails.org" - s.add_dependency('actionpack', '= 2.0.2' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.0.991' + PKG_BUILD) s.has_rdoc = true s.requirements << 'none' diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index ec803f5a8e..2e324d4637 100755 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 908bd1c5f2..7ed133d099 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -11,11 +11,11 @@ module ActionMailer #:nodoc: # = Mailer Models # # To use ActionMailer, you need to create a mailer model. - # + # # $ script/generate mailer Notifier # - # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then - # used to set variables to be used in the mail template, to change options on the mail, or + # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then + # used to set variables to be used in the mail template, to change options on the mail, or # to add attachments. # # Examples: @@ -48,7 +48,7 @@ module ActionMailer #:nodoc: # named after each key in the hash containing the value that that key points to. # # So, for example, <tt>body :account => recipient</tt> would result - # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the + # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the # view. # # @@ -57,7 +57,7 @@ module ActionMailer #:nodoc: # Like ActionController, each mailer class has a corresponding view directory # in which each method of the class looks for a template with its name. # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same name as the method - # in your mailer model. For example, in the mailer defined above, the template at + # in your mailer model. For example, in the mailer defined above, the template at # <tt>app/views/notifier/signup_notification.erb</tt> would be used to generate the email. # # Variables defined in the model are accessible as instance variables in the view. @@ -71,48 +71,48 @@ module ActionMailer #:nodoc: # # You got a new note! # <%= truncate(note.body, 25) %> - # + # # # = Generating URLs - # + # # URLs can be generated in mailer views using <tt>url_for</tt> or named routes. - # Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request, - # so you'll need to provide all of the details needed to generate a URL. + # Unlike controllers from Action Pack, the mailer instance doesn't have any context about the incoming request, + # so you'll need to provide all of the details needed to generate a URL. # # When using <tt>url_for</tt> you'll need to provide the <tt>:host</tt>, <tt>:controller</tt>, and <tt>:action</tt>: - # + # # <%= url_for(:host => "example.com", :controller => "welcome", :action => "greeting") %> # # When using named routes you only need to supply the <tt>:host</tt>: - # + # # <%= users_url(:host => "example.com") %> # # You will want to avoid using the <tt>name_of_route_path</tt> form of named routes because it doesn't make sense to # generate relative URLs in email messages. # - # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> option in + # It is also possible to set a default host that will be used in all mailers by setting the <tt>:host</tt> option in # the <tt>ActionMailer::Base.default_url_options</tt> hash as follows: # # ActionMailer::Base.default_url_options[:host] = "example.com" - # + # # This can also be set as a configuration option in <tt>config/environment.rb</tt>: # # config.action_mailer.default_url_options = { :host => "example.com" } # # If you do decide to set a default <tt>:host</tt> for your mailers you will want to use the # <tt>:only_path => false</tt> option when using <tt>url_for</tt>. This will ensure that absolute URLs are generated because - # the <tt>url_for</tt> view helper will, by default, generate relative URLs when a <tt>:host</tt> option isn't + # the <tt>url_for</tt> view helper will, by default, generate relative URLs when a <tt>:host</tt> option isn't # explicitly provided. # # = Sending mail # - # Once a mailer action and template are defined, you can deliver your message or create it and save it + # Once a mailer action and template are defined, you can deliver your message or create it and save it # for delivery later: # # Notifier.deliver_signup_notification(david) # sends the email # mail = Notifier.create_signup_notification(david) # => a tmail object # Notifier.deliver(mail) - # + # # You never instantiate your mailer class. Rather, your delivery instance # methods are automatically wrapped in class methods that start with the word # <tt>deliver_</tt> followed by the name of the mailer method that you would @@ -133,7 +133,7 @@ module ActionMailer #:nodoc: # body :account => recipient # content_type "text/html" # end - # end + # end # # # = Multipart email @@ -156,17 +156,17 @@ module ActionMailer #:nodoc: # end # end # end - # + # # Multipart messages can also be used implicitly because ActionMailer will automatically # detect and use multipart templates, where each template is named after the name of the action, followed # by the content type. Each such detected template will be added as separate part to the message. - # + # # For example, if the following templates existed: # * signup_notification.text.plain.erb # * signup_notification.text.html.erb # * signup_notification.text.xml.builder # * signup_notification.text.x-yaml.erb - # + # # Each would be rendered and added as a separate part to the message, # with the corresponding content type. The content type for the entire # message is automatically set to <tt>multipart/alternative</tt>, which indicates @@ -197,7 +197,7 @@ module ActionMailer #:nodoc: # a.body = generate_your_pdf_here() # end # end - # end + # end # # # = Configuration options @@ -255,16 +255,16 @@ module ActionMailer #:nodoc: cattr_accessor :template_extensions @@template_extensions = ['erb', 'builder', 'rhtml', 'rxml'] - @@smtp_settings = { - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, + @@smtp_settings = { + :address => "localhost", + :port => 25, + :domain => 'localhost.localdomain', + :user_name => nil, + :password => nil, :authentication => nil } cattr_accessor :smtp_settings - + @@sendmail_settings = { :location => '/usr/sbin/sendmail', :arguments => '-i -t' @@ -276,10 +276,10 @@ module ActionMailer #:nodoc: superclass_delegating_accessor :delivery_method self.delivery_method = :smtp - + @@perform_deliveries = true cattr_accessor :perform_deliveries - + @@deliveries = [] cattr_accessor :deliveries @@ -288,7 +288,7 @@ module ActionMailer #:nodoc: @@default_content_type = "text/plain" cattr_accessor :default_content_type - + @@default_mime_version = "1.0" cattr_accessor :default_mime_version @@ -297,47 +297,47 @@ module ActionMailer #:nodoc: # Specify the BCC addresses for the message adv_attr_accessor :bcc - + # Define the body of the message. This is either a Hash (in which case it # specifies the variables to pass to the template when it is rendered), # or a string, in which case it specifies the actual text of the message. adv_attr_accessor :body - + # Specify the CC addresses for the message. adv_attr_accessor :cc - + # Specify the charset to use for the message. This defaults to the # +default_charset+ specified for ActionMailer::Base. adv_attr_accessor :charset - + # Specify the content type for the message. This defaults to <tt>text/plain</tt> # in most cases, but can be automatically set in some situations. adv_attr_accessor :content_type - + # Specify the from address for the message. adv_attr_accessor :from - + # Specify additional headers to be added to the message. adv_attr_accessor :headers - + # Specify the order in which parts should be sorted, based on content-type. # This defaults to the value for the +default_implicit_parts_order+. adv_attr_accessor :implicit_parts_order - + # Defaults to "1.0", but may be explicitly given if needed. adv_attr_accessor :mime_version - + # The recipient addresses for the message, either as a string (for a single # address) or an array (for multiple addresses). adv_attr_accessor :recipients - + # The date on which the message was sent. If not set (the default), the # header will be set by the delivery agent. adv_attr_accessor :sent_on - + # Specify the subject of the message. adv_attr_accessor :subject - + # Specify the template name to use for current message. This is the "base" # template name, without the extension or directory, and may be used to # have multiple mailer methods share the same template. @@ -353,7 +353,7 @@ module ActionMailer #:nodoc: self.class.mailer_name end end - + def mailer_name=(value) self.class.mailer_name = value end @@ -431,7 +431,7 @@ module ActionMailer #:nodoc: # remain uninitialized (useful when you only need to invoke the "receive" # method, for instance). def initialize(method_name=nil, *parameters) #:nodoc: - create!(method_name, *parameters) if method_name + create!(method_name, *parameters) if method_name end # Initialize the mailer via the given +method_name+. The body will be @@ -517,7 +517,7 @@ module ActionMailer #:nodoc: @content_type ||= @@default_content_type.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup @template ||= method_name - @mailer_name ||= Inflector.underscore(self.class.name) + @mailer_name ||= self.class.name.underscore @parts ||= [] @headers ||= {} @body ||= {} @@ -603,7 +603,7 @@ module ActionMailer #:nodoc: part = (TMail::Mail === p ? p : p.to_mail(self)) m.parts << part end - + if real_content_type =~ /multipart/ ctype_attrs.delete "charset" m.set_content_type(real_content_type, nil, ctype_attrs) @@ -618,7 +618,7 @@ module ActionMailer #:nodoc: mail.ready_to_send sender = mail['return-path'] || mail.from - Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain], + Net::SMTP.start(smtp_settings[:address], smtp_settings[:port], smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password], smtp_settings[:authentication]) do |smtp| smtp.sendmail(mail.encoded, sender, destinations) end diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index 954a472757..88b9a3c200 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -2,7 +2,7 @@ module ActionMailer module VERSION #:nodoc: MAJOR = 2 MINOR = 0 - TINY = 2 + TINY = 991 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 2caaa40bf6..4a24d2f8b9 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,9 +1,10 @@ -*SVN* +*2.1.0 RC1 (May 11th, 2008)* * Fixed that forgery protection can be used without session tracking (Peter Jones) [#139] * Added session(:on) to turn session management back on in a controller subclass if the superclass turned it off (Peter Jones) [#136] +* Change the request forgery protection to go by Content-Type instead of request.format so that you can't bypass it by POSTing to "#{request.uri}.xml" [rick] * InstanceTag#default_time_from_options with hash args uses Time.current as default; respects hash settings when time falls in system local spring DST gap [Geoff Buesing] * select_date defaults to Time.zone.today when config.time_zone is set [Geoff Buesing] diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE index 007cc942e1..13c90d46e9 100644 --- a/actionpack/MIT-LICENSE +++ b/actionpack/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2007 David Heinemeier Hansson +Copyright (c) 2004-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionpack/Rakefile b/actionpack/Rakefile index e460d820ba..0147a5c1e8 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 919fbc6c6a..810a5fb9b5 100755 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index e1bf005f39..ea55fe42ce 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -259,12 +259,12 @@ module ActionController #:nodoc: DEFAULT_RENDER_STATUS_CODE = "200 OK" include StatusCodes - + # Controller specific instance variables which will not be accessible inside views. @@protected_view_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params @_flash @_response) - + # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, # and images to a dedicated asset server away from the main web server. Example: # ActionController::Base.asset_host = "http://assets.example.com" @@ -325,7 +325,7 @@ module ActionController #:nodoc: # Controls the default charset for all renders. @@default_charset = "utf-8" cattr_accessor :default_charset - + # The logger is used for generating information on the action run-time (including benchmarking) if available. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. cattr_accessor :logger @@ -333,7 +333,7 @@ module ActionController #:nodoc: # Controls the resource action separator @@resource_action_separator = "/" cattr_accessor :resource_action_separator - + # Allow to override path names for default resources' actions @@resources_path_names = { :new => 'new', :edit => 'edit' } cattr_accessor :resources_path_names @@ -433,7 +433,7 @@ module ActionController #:nodoc: end # Adds a view_path to the front of the view_paths array. - # If the current class has no view paths, copy them from + # If the current class has no view paths, copy them from # the superclass. This change will be visible for all future requests. # # ArticleController.prepend_view_path("views/default") @@ -444,9 +444,9 @@ module ActionController #:nodoc: view_paths.unshift(*path) ActionView::TemplateFinder.process_view_paths(path) end - + # Adds a view_path to the end of the view_paths array. - # If the current class has no view paths, copy them from + # If the current class has no view paths, copy them from # the superclass. This change will be visible for all future requests. # # ArticleController.append_view_path("views/default") @@ -457,7 +457,7 @@ module ActionController #:nodoc: view_paths.push(*path) ActionView::TemplateFinder.process_view_paths(path) end - + # Replace sensitive parameter data from the request log. # Filters parameters that have any of the arguments as a substring. # Looks in all subhashes of the param hash for keys to filter. @@ -504,6 +504,7 @@ module ActionController #:nodoc: filtered_parameters end + protected :filter_parameters end # Don't render layouts for templates with the given extensions. @@ -643,12 +644,12 @@ module ActionController #:nodoc: end self.view_paths = [] - + # View load paths for controller. def view_paths @template.finder.view_paths end - + def view_paths=(value) @template.finder.view_paths = value # Mutex needed end @@ -662,7 +663,7 @@ module ActionController #:nodoc: def prepend_view_path(path) @template.finder.prepend_view_path(path) # Mutex needed end - + # Adds a view_path to the end of the view_paths array. # This change affects the current request only. # @@ -874,10 +875,10 @@ module ActionController #:nodoc: elsif action_name = options[:action] template = default_template_name(action_name.to_s) if options[:layout] && !template_exempt_from_layout?(template) - render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true) + render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true) else render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true) - end + end elsif xml = options[:xml] response.content_type ||= Mime::XML @@ -895,12 +896,12 @@ module ActionController #:nodoc: if collection = options[:collection] render_for_text( - @template.send!(:render_partial_collection, partial, collection, + @template.send!(:render_partial_collection, partial, collection, options[:spacer_template], options[:locals]), options[:status] ) else render_for_text( - @template.send!(:render_partial, partial, + @template.send!(:render_partial, partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]), options[:status] ) end @@ -1024,7 +1025,7 @@ module ActionController #:nodoc: # redirect_to articles_url # redirect_to :back # - # The redirection happens as a "302 Moved" header unless otherwise specified. + # The redirection happens as a "302 Moved" header unless otherwise specified. # # Examples: # redirect_to post_url(@post), :status=>:found @@ -1035,17 +1036,17 @@ module ActionController #:nodoc: # When using <tt>redirect_to :back</tt>, if there is no referrer, # RedirectBackError will be raised. You may specify some fallback # behavior for this case by rescuing RedirectBackError. - def redirect_to(options = {}, response_status = {}) #:doc: + def redirect_to(options = {}, response_status = {}) #:doc: raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? - if options.is_a?(Hash) && options[:status] - status = options.delete(:status) - elsif response_status[:status] - status = response_status[:status] - else - status = 302 + if options.is_a?(Hash) && options[:status] + status = options.delete(:status) + elsif response_status[:status] + status = response_status[:status] + else + status = 302 end - + case options when %r{^\w+://.*} raise DoubleRenderError if performed? @@ -1119,7 +1120,7 @@ module ActionController #:nodoc: response.body = text.is_a?(Proc) ? text : text.to_s end end - + def initialize_template_class(response) response.template = ActionView::Base.new(self.class.view_paths, {}, self) response.template.extend self.class.master_helper_module diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 7b0551c664..1ef9e60a21 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -9,7 +9,7 @@ module ActionController #:nodoc: # class ListsController < ApplicationController # before_filter :authenticate, :except => :public # caches_page :public - # caches_action :show, :feed + # caches_action :index, :show, :feed # end # # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the @@ -27,15 +27,19 @@ module ActionController #:nodoc: # You can set modify the default action cache path by passing a :cache_path option. This will be passed directly to ActionCachePath.path_for. This is handy # for actions with multiple possible routes that should be cached differently. If a block is given, it is called with the current controller instance. # + # And you can also use :if to pass a Proc that specifies when the action should be cached. + # # class ListsController < ApplicationController # before_filter :authenticate, :except => :public # caches_page :public + # caches_action :index, :if => Proc.new { |c| !c.request.format.json? } # cache if is not a JSON request # caches_action :show, :cache_path => { :project => 1 } - # caches_action :show, :cache_path => Proc.new { |controller| - # controller.params[:user_id] ? + # caches_action :feed, :cache_path => Proc.new { |controller| + # controller.params[:user_id] ? # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) : # controller.send(:list_url, c.params[:id]) } # end + # module Actions def self.included(base) #:nodoc: base.extend(ClassMethods) @@ -49,7 +53,8 @@ module ActionController #:nodoc: # See ActionController::Caching::Actions for details. def caches_action(*actions) return unless cache_configured? - around_filter(ActionCacheFilter.new(*actions)) + options = actions.extract_options! + around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options)) end end @@ -67,16 +72,12 @@ module ActionController #:nodoc: end class ActionCacheFilter #:nodoc: - def initialize(*actions, &block) - @options = actions.extract_options! - @actions = Set.new(actions) + def initialize(options, &block) + @options = options end def before(controller) - return unless @actions.include?(controller.action_name.intern) - cache_path = ActionCachePath.new(controller, path_options_for(controller, @options)) - if cache = controller.read_fragment(cache_path.path) controller.rendered_action_cache = true set_content_type!(controller, cache_path.extension) @@ -88,7 +89,7 @@ module ActionController #:nodoc: end def after(controller) - return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache || !caching_allowed(controller) + return if controller.rendered_action_cache || !caching_allowed(controller) controller.write_fragment(controller.action_cache_path.path, controller.response.body) end @@ -105,16 +106,16 @@ module ActionController #:nodoc: controller.request.get? && controller.response.headers['Status'].to_i == 200 end end - + class ActionCachePath attr_reader :path, :extension - + class << self def path_for(controller, options) new(controller, options).path end end - + def initialize(controller, options = {}) @extension = extract_extension(controller.request.path) path = controller.url_for(options).split('://').last @@ -122,16 +123,16 @@ module ActionController #:nodoc: add_extension!(path, @extension) @path = URI.unescape(path) end - + private def normalize!(path) path << 'index' if path[-1] == ?/ end - + def add_extension!(path, extension) path << ".#{extension}" if extension end - + def extract_extension(file_path) # Don't want just what comes after the last '.' to accommodate multi part extensions # such as tar.gz. @@ -140,4 +141,4 @@ module ActionController #:nodoc: end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 3164e14f6f..61559e9ec7 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -28,7 +28,7 @@ module ActionController #:nodoc: # class ListsController < ApplicationController # caches_action :index, :show, :public, :feed # cache_sweeper OpenBar::Sweeper, :only => [ :edit, :destroy, :share ] - # end + # end module Sweeping def self.included(base) #:nodoc: base.extend(ClassMethods) @@ -40,7 +40,7 @@ module ActionController #:nodoc: sweepers.each do |sweeper| ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base) - sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(Inflector.classify(sweeper)) : sweeper).instance + sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance if sweeper_instance.is_a?(Sweeper) around_filter(sweeper_instance, :only => configuration[:only]) @@ -94,4 +94,4 @@ module ActionController #:nodoc: end end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/cgi_ext/cookie.rb b/actionpack/lib/action_controller/cgi_ext/cookie.rb index 3dd374f126..a244e2a39a 100644 --- a/actionpack/lib/action_controller/cgi_ext/cookie.rb +++ b/actionpack/lib/action_controller/cgi_ext/cookie.rb @@ -37,7 +37,7 @@ class CGI #:nodoc: @path = nil else @name = name['name'] - @value = Array(name['value']) + @value = (name['value'].kind_of?(String) ? [name['value']] : Array(name['value'])).delete_if(&:blank?) @domain = name['domain'] @expires = name['expires'] @secure = name['secure'] || false diff --git a/actionpack/lib/action_controller/mime_type.rb b/actionpack/lib/action_controller/mime_type.rb index 8c02f20521..f43e2ba06d 100644 --- a/actionpack/lib/action_controller/mime_type.rb +++ b/actionpack/lib/action_controller/mime_type.rb @@ -17,6 +17,10 @@ module Mime # end # end class Type + @@html_types = Set.new [:html, :all] + @@unverifiable_types = Set.new [:text, :json, :csv, :xml, :rss, :atom, :yaml] + cattr_reader :html_types, :unverifiable_types + # A simple helper class used in parsing the accept header class AcceptItem #:nodoc: attr_accessor :order, :name, :q @@ -153,12 +157,21 @@ module Mime synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym end end - + + # Returns true if ActionPack should check requests using this Mime Type for possible request forgery. See + # ActionController::RequestForgerProtection. + def verify_request? + !@@unverifiable_types.include?(to_sym) + end + + def html? + @@html_types.include?(to_sym) || @string =~ /html/ + end + private def method_missing(method, *args) if method.to_s =~ /(\w+)\?$/ - mime_type = $1.downcase.to_sym - mime_type == @symbol || (mime_type == :html && @symbol == :all) + $1.downcase.to_sym == to_sym else super end diff --git a/actionpack/lib/action_controller/mime_types.rb b/actionpack/lib/action_controller/mime_types.rb index 71706b4c41..01a266d3fe 100644 --- a/actionpack/lib/action_controller/mime_types.rb +++ b/actionpack/lib/action_controller/mime_types.rb @@ -17,4 +17,4 @@ Mime::Type.register "multipart/form-data", :multipart_form Mime::Type.register "application/x-www-form-urlencoded", :url_encoded_form # http://www.ietf.org/rfc/rfc4627.txt -Mime::Type.register "application/json", :json, %w( text/x-json ) +Mime::Type.register "application/json", :json, %w( text/x-json )
\ No newline at end of file diff --git a/actionpack/lib/action_controller/request_forgery_protection.rb b/actionpack/lib/action_controller/request_forgery_protection.rb index 139e91ecf9..02c9d59d07 100644 --- a/actionpack/lib/action_controller/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/request_forgery_protection.rb @@ -99,7 +99,7 @@ module ActionController #:nodoc: end def verifiable_request_format? - request.format.html? || request.format.js? + request.content_type.nil? || request.content_type.verify_request? end # Sets the token value for the current session. Pass a <tt>:secret</tt> option diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index 5022c9a815..40ef4ea044 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -199,10 +199,8 @@ module ActionController #:nodoc: private def perform_action_with_rescue #:nodoc: perform_action_without_rescue - rescue Exception => exception # errors from action performed - return if rescue_action_with_handler(exception) - - rescue_action(exception) + rescue Exception => exception + rescue_action_with_handler(exception) || rescue_action(exception) end def rescues_path(template_name) diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb index 560491f996..ada1862c3e 100644 --- a/actionpack/lib/action_controller/session/cookie_store.rb +++ b/actionpack/lib/action_controller/session/cookie_store.rb @@ -130,17 +130,20 @@ class CGI::Session::CookieStore # Marshal a session hash into safe cookie data. Include an integrity hash. def marshal(session) data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop - CGI.escape "#{data}--#{generate_digest(data)}" + "#{data}--#{generate_digest(data)}" end # Unmarshal cookie data to a hash and verify its integrity. def unmarshal(cookie) if cookie - data, digest = CGI.unescape(cookie).split('--') - unless digest == generate_digest(data) + data, digest = cookie.split('--') + + # Do two checks to transparently support old double-escaped data. + unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data)) delete raise TamperedWithCookie end + Marshal.load(ActiveSupport::Base64.decode64(data)) end end diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 006c83dbc8..c7fd3092e7 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index 7aa6a5db96..70fc1ced8c 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 0 - TINY = 2 + TINY = 991 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 609334d52d..5f4126e4e9 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js index fbc4418b83..5aaf0bb2b7 100644 --- a/actionpack/lib/action_view/helpers/javascripts/controls.js +++ b/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) // Contributors: diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index ccf4a1e45c..bf5cfea66c 100644 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // script.aculo.us is freely distributable under the terms of an MIT-style license. diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js index 65aed23957..f030b5dbe9 100644 --- a/actionpack/lib/action_view/helpers/javascripts/effects.js +++ b/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 1b12aa8058..1a0e660d52 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -458,7 +458,7 @@ module ActionView url_options = options[:url] url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) - function << "'#{url_for(url_options)}'" + function << "'#{escape_javascript(url_for(url_options))}'" function << ", #{javascript_options})" function = "#{options[:before]}; #{function}" if options[:before] diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index f8c3b67474..9d220c546a 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -3,19 +3,19 @@ require 'html/document' module ActionView module Helpers #:nodoc: - # The TextHelper module provides a set of methods for filtering, formatting - # and transforming strings, which can reduce the amount of inline Ruby code in - # your views. These helper methods extend ActionView making them callable + # The TextHelper module provides a set of methods for filtering, formatting + # and transforming strings, which can reduce the amount of inline Ruby code in + # your views. These helper methods extend ActionView making them callable # within your template files. - module TextHelper - # The preferred method of outputting text in your views is to use the - # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods - # do not operate as expected in an eRuby code block. If you absolutely must + module TextHelper + # The preferred method of outputting text in your views is to use the + # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods + # do not operate as expected in an eRuby code block. If you absolutely must # output text within a non-output code block (i.e., <% %>), you can use the concat method. # # ==== Examples - # <% - # concat "hello", binding + # <% + # concat "hello", binding # # is the equivalent of <%= "hello" %> # # if (logged_in == true): @@ -30,15 +30,15 @@ module ActionView end if RUBY_VERSION < '1.9' - # If +text+ is longer than +length+, +text+ will be truncated to the length of + # If +text+ is longer than +length+, +text+ will be truncated to the length of # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+ # (defaults to "..."). # # ==== Examples - # truncate("Once upon a time in a world far far away", 14) + # truncate("Once upon a time in a world far far away", 14) # # => Once upon a... # - # truncate("Once upon a time in a world far far away") + # truncate("Once upon a time in a world far far away") # # => Once upon a time in a world f... # # truncate("And they found that many people were sleeping better.", 25, "(clipped)") @@ -63,20 +63,20 @@ module ActionView end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into - # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+ + # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+ # as a single-quoted string with \1 where the phrase is to be inserted (defaults to # '<strong class="highlight">\1</strong>') # # ==== Examples - # highlight('You searched for: rails', 'rails') + # highlight('You searched for: rails', 'rails') # # => You searched for: <strong class="highlight">rails</strong> # # highlight('You searched for: ruby, rails, dhh', 'actionpack') - # # => You searched for: ruby, rails, dhh + # # => You searched for: ruby, rails, dhh # - # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>') + # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>') # # => You searched <em>for</em>: <em>rails</em> - # + # # highlight('You searched for: rails', 'rails', "<a href='search?q=\1'>\1</a>") # # => You searched for: <a href='search?q=rails>rails</a> def highlight(text, phrases, highlighter = '<strong class="highlight">\1</strong>') @@ -89,23 +89,23 @@ module ActionView end if RUBY_VERSION < '1.9' - # Extracts an excerpt from +text+ that matches the first instance of +phrase+. + # Extracts an excerpt from +text+ that matches the first instance of +phrase+. # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, # then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case. # If the +phrase+ isn't found, nil is returned. # # ==== Examples - # excerpt('This is an example', 'an', 5) + # excerpt('This is an example', 'an', 5) # # => "...s is an exam..." # - # excerpt('This is an example', 'is', 5) + # excerpt('This is an example', 'is', 5) # # => "This is a..." # - # excerpt('This is an example', 'is') + # excerpt('This is an example', 'is') # # => "This is an example" # - # excerpt('This next thing is an example', 'ex', 2) + # excerpt('This next thing is an example', 'ex', 2) # # => "...next..." # # excerpt('This is also an example', 'an', 8, '<chop> ') @@ -147,33 +147,24 @@ module ActionView end end - # Attempts to pluralize the +singular+ word unless +count+ is 1. If +plural+ - # is supplied, it will use that when count is > 1, if the ActiveSupport Inflector - # is loaded, it will use the Inflector to determine the plural form, otherwise - # it will just add an 's' to the +singular+ word. + # Attempts to pluralize the +singular+ word unless +count+ is 1. If + # +plural+ is supplied, it will use that when count is > 1, otherwise + # it will use the Inflector to determine the plural form # # ==== Examples - # pluralize(1, 'person') + # pluralize(1, 'person') # # => 1 person # - # pluralize(2, 'person') + # pluralize(2, 'person') # # => 2 people # - # pluralize(3, 'person', 'users') + # pluralize(3, 'person', 'users') # # => 3 users # # pluralize(0, 'person') # # => 0 people def pluralize(count, singular, plural = nil) - "#{count || 0} " + if count == 1 || count == '1' - singular - elsif plural - plural - elsif Object.const_defined?("Inflector") - Inflector.pluralize(singular) - else - singular + "s" - end + "#{count || 0} " + ((count == 1 || count == '1') ? singular : (plural || singular.pluralize)) end # Wraps the +text+ into lines no longer than +line_width+ width. This method @@ -229,7 +220,7 @@ module ActionView end end - # Returns the text with all the Textile codes turned into HTML tags, + # Returns the text with all the Textile codes turned into HTML tags, # but without the bounding <p> tag that RedCloth adds. # # You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile]. @@ -273,25 +264,25 @@ module ActionView # # => "<p>We like to <em>write</em> <code>code</code>, not just <em>read</em> it!</p>" # # markdown("The [Markdown website](http://daringfireball.net/projects/markdown/) has more information.") - # # => "<p>The <a href="http://daringfireball.net/projects/markdown/">Markdown website</a> + # # => "<p>The <a href="http://daringfireball.net/projects/markdown/">Markdown website</a> # # has more information.</p>" # # markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")') - # # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>' + # # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>' def markdown(text) text.blank? ? "" : BlueCloth.new(text).to_html end rescue LoadError # We can't really help what's not there end - + # Returns +text+ transformed into HTML using simple formatting rules. - # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a + # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is # considered as a linebreak and a <tt><br /></tt> tag is appended. This - # method does not remove the newlines from the +text+. + # method does not remove the newlines from the +text+. # - # You can pass any HTML attributes into <tt>html_options</tt>. These + # You can pass any HTML attributes into <tt>html_options</tt>. These # will be added to all created paragraphs. # ==== Examples # my_text = "Here is some basic text...\n...with a line break." @@ -316,19 +307,19 @@ module ActionView text << "</p>" end - # Turns all URLs and e-mail addresses into clickable links. The +link+ parameter + # Turns all URLs and e-mail addresses into clickable links. The +link+ parameter # will limit what should be linked. You can add HTML attributes to the links using - # +href_options+. Options for +link+ are <tt>:all</tt> (default), - # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and + # +href_options+. Options for +link+ are <tt>:all</tt> (default), + # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and # e-mail address is yielded and the result is used as the link text. # # ==== Examples - # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") + # auto_link("Go to http://www.rubyonrails.org and say hello to david@loudthinking.com") # # => "Go to <a href=\"http://www.rubyonrails.org\">http://www.rubyonrails.org</a> and # # say hello to <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>" # # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :urls) - # # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a> + # # => "Visit <a href=\"http://www.loudthinking.com/\">http://www.loudthinking.com/</a> # # or e-mail david@loudthinking.com" # # auto_link("Visit http://www.loudthinking.com/ or e-mail david@loudthinking.com", :email_addresses) @@ -338,9 +329,9 @@ module ActionView # auto_link(post_body, :all, :target => '_blank') do |text| # truncate(text, 15) # end - # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. + # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." - # + # def auto_link(text, link = :all, href_options = {}, &block) return '' if text.blank? case link @@ -349,15 +340,15 @@ module ActionView when :urls then auto_link_urls(text, href_options, &block) end end - + # Creates a Cycle object whose _to_s_ method cycles through elements of an - # array every time it is called. This can be used for example, to alternate - # classes for table rows. You can use named cycles to allow nesting in loops. - # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a - # named cycle. You can manually reset a cycle by calling reset_cycle and passing the + # array every time it is called. This can be used for example, to alternate + # classes for table rows. You can use named cycles to allow nesting in loops. + # Passing a Hash as the last parameter with a <tt>:name</tt> key will create a + # named cycle. You can manually reset a cycle by calling reset_cycle and passing the # name of the cycle. # - # ==== Examples + # ==== Examples # # Alternate CSS classes for even and odd numbers... # @items = [1,2,3,4] # <table> @@ -370,8 +361,8 @@ module ActionView # # # # Cycle CSS classes for rows, and text colors for values within each row - # @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'}, - # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'}, + # @items = x = [{:first => 'Robert', :middle => 'Daniel', :last => 'James'}, + # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'}, # {:first => 'June', :middle => 'Dae', :last => 'Jones'}] # <% @items.each do |item| %> # <tr class="<%= cycle("even", "odd", :name => "row_class") -%>"> @@ -401,8 +392,8 @@ module ActionView end return cycle.to_s end - - # Resets a cycle so that it starts from the first element the next time + + # Resets a cycle so that it starts from the first element the next time # it is called. Pass in +name+ to reset a named cycle. # # ==== Example @@ -428,12 +419,12 @@ module ActionView class Cycle #:nodoc: attr_reader :values - + def initialize(first_value, *values) @values = values.unshift(first_value) reset end - + def reset @index = 0 end @@ -453,7 +444,7 @@ module ActionView @_cycles = Hash.new unless defined?(@_cycles) return @_cycles[name] end - + def set_cycle(name, cycle_object) @_cycles = Hash.new unless defined?(@_cycles) @_cycles[name] = cycle_object @@ -462,13 +453,13 @@ module ActionView AUTO_LINK_RE = %r{ ( # leading text <\w+.*?>| # leading HTML tag, or - [^=!:'"/]| # leading punctuation, or + [^=!:'"/]| # leading punctuation, or ^ # beginning of line ) ( (?:https?://)| # protocol spec, or (?:www\.) # www.* - ) + ) ( [-\w]+ # subdomain or domain (?:\.[-\w]+)* # remaining subdomains or domain @@ -502,7 +493,7 @@ module ActionView body = text.dup text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do text = $1 - + if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/) text else diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index ddc1c68383..4aacb4a78a 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -6,6 +6,7 @@ CACHE_DIR = 'test_cache' FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR) ActionController::Base.page_cache_directory = FILE_STORE_PATH ActionController::Base.cache_store = :file_store, FILE_STORE_PATH +ActionController::Base.view_paths = [ File.dirname(__FILE__) + '/../fixtures/' ] class PageCachingTestController < ActionController::Base caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? } @@ -128,7 +129,7 @@ class PageCachingTest < Test::Unit::TestCase end end end - + def test_page_caching_conditional_options @request.env['HTTP_ACCEPT'] = 'application/json' get :ok @@ -151,12 +152,15 @@ end class ActionCachingTestController < ActionController::Base - caches_action :index, :redirected, :forbidden + caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? } caches_action :show, :cache_path => 'http://test.host/custom/show' caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" } + caches_action :with_layout + + layout 'talk_from_action.erb' def index - @cache_this = Time.now.to_f.to_s + @cache_this = MockTime.now.to_f.to_s render :text => @cache_this end @@ -169,14 +173,26 @@ class ActionCachingTestController < ActionController::Base headers["Status"] = "403 Forbidden" end + def with_layout + @cache_this = MockTime.now.to_f.to_s + render :text => @cache_this, :layout => true + end + alias_method :show, :index alias_method :edit, :index + alias_method :destroy, :index def expire expire_action :controller => 'action_caching_test', :action => 'index' render :nothing => true end +end +class MockTime < Time + # Let Time spicy to assure that Time.now != Time.now + def to_f + super+rand + end end class ActionCachingMockController @@ -223,6 +239,36 @@ class ActionCacheTest < Test::Unit::TestCase assert_equal cached_time, @response.body end + def test_simple_action_not_cached + get :destroy + cached_time = content_to_cache + assert_equal cached_time, @response.body + assert_cache_does_not_exist 'hostname.com/action_caching_test/destroy' + reset! + + get :destroy + assert_not_equal cached_time, @response.body + end + + def test_action_cache_with_layout + get :with_layout + cached_time = content_to_cache + assert_not_equal cached_time, @response.body + assert_cache_exists 'hostname.com/action_caching_test/with_layout' + reset! + + get :with_layout + assert_not_equal cached_time, @response.body + + assert_equal @response.body, read_fragment('hostname.com/action_caching_test/with_layout') + end + + def test_action_cache_conditional_options + @request.env['HTTP_ACCEPT'] = 'application/json' + get :index + assert_cache_does_not_exist 'hostname.com/action_caching_test' + end + def test_action_cache_with_custom_cache_path get :show cached_time = content_to_cache @@ -350,9 +396,22 @@ class ActionCacheTest < Test::Unit::TestCase end def assert_cache_exists(path) - full_path = File.join(FILE_STORE_PATH, "views", path + '.cache') + full_path = cache_path(path) assert File.exist?(full_path), "#{full_path.inspect} does not exist." end + + def assert_cache_does_not_exist(path) + full_path = cache_path(path) + assert !File.exist?(full_path), "#{full_path.inspect} should not exist." + end + + def cache_path(path) + File.join(FILE_STORE_PATH, 'views', path + '.cache') + end + + def read_fragment(path) + @controller.read_fragment(path) + end end class FragmentCachingTestController < ActionController::Base @@ -516,7 +575,7 @@ class FunctionalFragmentCachingTest < Test::Unit::TestCase def setup ActionController::Base.perform_caching = true @store = ActiveSupport::Cache::MemoryStore.new - ActionController::Base.cache_store = @store + ActionController::Base.cache_store = @store @controller = FunctionalCachingController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @@ -529,17 +588,17 @@ Hello This bit's fragment cached CACHED assert_equal expected_body, @response.body - + assert_equal "This bit's fragment cached", @store.read('views/test.host/functional_caching/fragment_cached') end - + def test_fragment_caching_in_partials get :html_fragment_cached_with_partial assert_response :success assert_match /Fragment caching in a partial/, @response.body assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/html_fragment_cached_with_partial') end - + def test_fragment_caching_in_rjs_partials xhr :get, :js_fragment_cached_with_partial assert_response :success @@ -547,8 +606,3 @@ CACHED assert_match "Fragment caching in a partial", @store.read('views/test.host/functional_caching/js_fragment_cached_with_partial') end end - - - - - diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 42f3bd26a4..b45fbb17d3 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -82,6 +82,7 @@ class CookieTest < Test::Unit::TestCase def test_expiring_cookie get :logout assert_equal [ CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)) ], @response.headers["cookie"] + assert_equal CGI::Cookie::new("name" => "user_name", "value" => "", "expires" => Time.at(0)).value, [] end def test_cookiejar_accessor @@ -137,4 +138,9 @@ class CookieTest < Test::Unit::TestCase cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true') assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies) end + + def test_cookies_should_not_be_split_on_values_with_newlines + cookies = CGI::Cookie.new("name" => "val", "value" => "this\nis\na\ntest") + assert cookies.size == 1 + end end diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb index 11adacb5e3..c4de10181d 100644 --- a/actionpack/test/controller/filter_params_test.rb +++ b/actionpack/test/controller/filter_params_test.rb @@ -7,14 +7,14 @@ class FilterParamTest < Test::Unit::TestCase def setup @controller = FilterParamController.new end - + def test_filter_parameters assert FilterParamController.respond_to?(:filter_parameter_logging) assert !@controller.respond_to?(:filter_parameters) - + FilterParamController.filter_parameter_logging assert @controller.respond_to?(:filter_parameters) - + test_hashes = [[{},{},[]], [{'foo'=>nil},{'foo'=>nil},[]], [{'foo'=>'bar'},{'foo'=>'bar'},[]], @@ -24,11 +24,11 @@ class FilterParamTest < Test::Unit::TestCase [{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'], [{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'], [{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']] - + test_hashes.each do |before_filter, after_filter, filter_words| FilterParamController.filter_parameter_logging(*filter_words) - assert_equal after_filter, @controller.filter_parameters(before_filter) - + assert_equal after_filter, @controller.send!(:filter_parameters, before_filter) + filter_words.push('blah') FilterParamController.filter_parameter_logging(*filter_words) do |key, value| value.reverse! if key =~ /bargain/ @@ -37,7 +37,13 @@ class FilterParamTest < Test::Unit::TestCase before_filter['barg'] = {'bargain'=>'gain', 'blah'=>'bar', 'bar'=>{'bargain'=>{'blah'=>'foo'}}} after_filter['barg'] = {'bargain'=>'niag', 'blah'=>'[FILTERED]', 'bar'=>{'bargain'=>{'blah'=>'[FILTERED]'}}} - assert_equal after_filter, @controller.filter_parameters(before_filter) + assert_equal after_filter, @controller.send!(:filter_parameters, before_filter) end end + + def test_filter_parameters_is_protected + FilterParamController.filter_parameter_logging(:foo) + assert !FilterParamController.action_methods.include?('filter_parameters') + assert_raise(NoMethodError) { @controller.filter_parameters([{'password' => '[FILTERED]'}]) } + end end diff --git a/actionpack/test/controller/mime_type_test.rb b/actionpack/test/controller/mime_type_test.rb index 03b0f8bad2..f16a3c68b4 100644 --- a/actionpack/test/controller/mime_type_test.rb +++ b/actionpack/test/controller/mime_type_test.rb @@ -52,16 +52,33 @@ class MimeTypeTest < Test::Unit::TestCase end def test_type_convenience_methods - types = [:html, :xml, :png, :pdf, :yaml, :url_encoded_form] + # Don't test Mime::ALL, since it Mime::ALL#html? == true + types = Mime::SET.to_a.map(&:to_sym).uniq - [:all] + + # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE + types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } + types.each do |type| mime = Mime.const_get(type.to_s.upcase) - assert mime.send("#{type}?"), "Mime::#{type.to_s.upcase} is not #{type}?" - (types - [type]).each { |t| assert !mime.send("#{t}?"), "Mime::#{t.to_s.upcase} is #{t}?" } + assert mime.send("#{type}?"), "#{mime.inspect} is not #{type}?" + (types - [type]).each { |other_type| assert !mime.send("#{other_type}?"), "#{mime.inspect} is #{other_type}?" } end end - + def test_mime_all_is_html assert Mime::ALL.all?, "Mime::ALL is not all?" assert Mime::ALL.html?, "Mime::ALL is not html?" end + + def test_verifiable_mime_types + unverified_types = Mime::Type.unverifiable_types + all_types = Mime::SET.to_a.map(&:to_sym) + all_types.uniq! + # Remove custom Mime::Type instances set in other tests, like Mime::GIF and Mime::IPHONE + all_types.delete_if { |type| !Mime.const_defined?(type.to_s.upcase) } + + unverified, verified = all_types.partition { |type| Mime::Type.unverifiable_types.include? type } + assert verified.all? { |type| Mime.const_get(type.to_s.upcase).verify_request? }, "Not all Mime Types are verified: #{verified.inspect}" + assert unverified.all? { |type| !Mime.const_get(type.to_s.upcase).verify_request? }, "Some Mime Types are verified: #{unverified.inspect}" + end end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 7022713e30..f7adaa7d4e 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -101,19 +101,79 @@ module RequestForgeryProtectionTests post :unsafe assert_response :success end - + def test_should_not_allow_post_without_token assert_raises(ActionController::InvalidAuthenticityToken) { post :index } end - + def test_should_not_allow_put_without_token assert_raises(ActionController::InvalidAuthenticityToken) { put :index } end - + def test_should_not_allow_delete_without_token assert_raises(ActionController::InvalidAuthenticityToken) { delete :index } end - + + def test_should_not_allow_api_formatted_post_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + post :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_put_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + put :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_delete_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + delete :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + post :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + put :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s + delete :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s + post :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s + put :index, :format => 'xml' + end + end + + def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token + assert_raises(ActionController::InvalidAuthenticityToken) do + @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s + delete :index, :format => 'xml' + end + end + def test_should_not_allow_xhr_post_without_token assert_raises(ActionController::InvalidAuthenticityToken) { xhr :post, :index } end @@ -142,16 +202,19 @@ module RequestForgeryProtectionTests end def test_should_allow_post_with_xml + @request.env['CONTENT_TYPE'] = Mime::XML.to_s post :index, :format => 'xml' assert_response :success end def test_should_allow_put_with_xml + @request.env['CONTENT_TYPE'] = Mime::XML.to_s put :index, :format => 'xml' assert_response :success end def test_should_allow_delete_with_xml + @request.env['CONTENT_TYPE'] = Mime::XML.to_s delete :index, :format => 'xml' assert_response :success end diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index d308f2a31b..5adaeaf5c5 100755 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -43,7 +43,9 @@ class CookieStoreTest < Test::Unit::TestCase { :empty => ['BAgw--0686dcaccc01040f4bd4f35fe160afe9bc04c330', {}], :a_one => ['BAh7BiIGYWkG--5689059497d7f122a7119f171aef81dcfd807fec', { 'a' => 1 }], :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--9d20154623b9eeea05c62ab819be0e2483238759', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}], - :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }] } + :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA==--bf9785a666d3c4ac09f7fe3353496b437546cfbf', { 'user_id' => 123, 'flash' => {} }], + :double_escaped => [CGI.escape('BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--bf9785a666d3c4ac09f7fe3353496b437546cfbf'), { 'user_id' => 123, 'flash' => {} }] } + end def setup @@ -101,6 +103,15 @@ class CookieStoreTest < Test::Unit::TestCase end end + def test_restores_double_encoded_cookies + set_cookie! cookie_value(:double_escaped) + new_session do |session| + session.dbman.restore + assert_equal session["user_id"], 123 + assert_equal session["flash"], {} + end + end + def test_close_doesnt_write_cookie_if_data_is_blank new_session do |session| assert_no_cookies session @@ -241,6 +252,7 @@ class CookieStoreWithMD5DigestTest < CookieStoreTest { :empty => ['BAgw--0415cc0be9579b14afc22ee2d341aa21', {}], :a_one => ['BAh7BiIGYWkG--5a0ed962089cc6600ff44168a5d59bc8', { 'a' => 1 }], :typical => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7BiILbm90aWNlIgxIZXkgbm93--f426763f6ef435b3738b493600db8d64', { 'user_id' => 123, 'flash' => { 'notice' => 'Hey now' }}], - :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--0af9156650dab044a53a91a4ddec2c51', { 'user_id' => 123, 'flash' => {} }] } + :flashed => ['BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA==--0af9156650dab044a53a91a4ddec2c51', { 'user_id' => 123, 'flash' => {} }], + :double_escaped => [CGI.escape('BAh7ByIMdXNlcl9pZGkBeyIKZmxhc2h7AA%3D%3D--0af9156650dab044a53a91a4ddec2c51'), { 'user_id' => 123, 'flash' => {} }] } end end diff --git a/actionpack/test/template/prototype_helper_test.rb b/actionpack/test/template/prototype_helper_test.rb index a84d4e72af..9a1079b297 100644 --- a/actionpack/test/template/prototype_helper_test.rb +++ b/actionpack/test/template/prototype_helper_test.rb @@ -86,6 +86,11 @@ class PrototypeHelperTest < PrototypeHelperBaseTest link_to_remote("Remote outauthor", { :url => { :action => "whatnot" }, :html => { :class => "fine" } }) end + def test_link_to_remote_url_quote_escaping + assert_dom_equal %(<a href="#" onclick="new Ajax.Request('http://www.example.com/whatnot\\\'s', {asynchronous:true, evalScripts:true}); return false;">Remote</a>), + link_to_remote("Remote", { :url => { :action => "whatnot's" } }) + end + def test_periodically_call_remote assert_dom_equal %(<script type="text/javascript">\n//<![CDATA[\nnew PeriodicalExecuter(function() {new Ajax.Updater('schremser_bier', 'http://www.example.com/mehr_bier', {asynchronous:true, evalScripts:true})}, 10)\n//]]>\n</script>), periodically_call_remote(:update => "schremser_bier", :url => { :action => "mehr_bier" }) diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 25ecda687f..06e1fd1929 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -23,9 +23,9 @@ class TextHelperTest < ActionView::TestCase text = "A\r\n \nB\n\n\r\n\t\nC\nD".freeze assert_equal "<p>A\n<br /> \n<br />B</p>\n\n<p>\t\n<br />C\n<br />D</p>", simple_format(text) - + assert_equal %q(<p class="test">This is a classy test</p>), simple_format("This is a classy test", :class => 'test') - assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') + assert_equal %Q(<p class="test">para 1</p>\n\n<p class="test">para 2</p>), simple_format("para 1\n\npara 2", :class => 'test') end def test_truncate @@ -41,7 +41,7 @@ class TextHelperTest < ActionView::TestCase if RUBY_VERSION < '1.9.0' def test_truncate_multibyte with_kcode 'none' do - assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10) + assert_equal "\354\225\210\353\205\225\355...", truncate("\354\225\210\353\205\225\355\225\230\354\204\270\354\232\224", 10) end with_kcode 'u' do assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...", @@ -73,7 +73,7 @@ class TextHelperTest < ActionView::TestCase "This is a <b>beautiful</b> morning, but also a <b>beautiful</b> day", highlight("This is a beautiful morning, but also a beautiful day", "beautiful", '<b>\1</b>') ) - + assert_equal( "This text is not changed because we supplied an empty phrase", highlight("This text is not changed because we supplied an empty phrase", nil) @@ -166,18 +166,9 @@ class TextHelperTest < ActionView::TestCase assert_equal("2 counters", pluralize(2, "count", "counters")) assert_equal("0 counters", pluralize(nil, "count", "counters")) assert_equal("2 people", pluralize(2, "person")) - assert_equal("10 buffaloes", pluralize(10, "buffalo")) - end - - uses_mocha("should_just_add_s_for_pluralize_without_inflector_loaded") do - def test_should_just_add_s_for_pluralize_without_inflector_loaded - Object.expects(:const_defined?).with("Inflector").times(4).returns(false) - assert_equal("1 count", pluralize(1, "count")) - assert_equal("2 persons", pluralize(2, "person")) - assert_equal("2 personss", pluralize("2", "persons")) - assert_equal("2 counts", pluralize(2, "count")) - assert_equal("10 buffalos", pluralize(10, "buffalo")) - end + assert_equal("10 buffaloes", pluralize(10, "buffalo")) + assert_equal("1 berry", pluralize(1, "berry")) + assert_equal("12 berries", pluralize(12, "berry")) end def test_auto_link_parsing @@ -298,7 +289,7 @@ class TextHelperTest < ActionView::TestCase assert_equal("2", value.to_s) assert_equal("3", value.to_s) end - + def test_cycle_class_with_no_arguments assert_raise(ArgumentError) { value = Cycle.new() } end @@ -311,11 +302,11 @@ class TextHelperTest < ActionView::TestCase assert_equal("2", cycle("one", 2, "3")) assert_equal("3", cycle("one", 2, "3")) end - + def test_cycle_with_no_arguments assert_raise(ArgumentError) { value = cycle() } end - + def test_cycle_resets_with_new_values assert_equal("even", cycle("even", "odd")) assert_equal("odd", cycle("even", "odd")) @@ -325,7 +316,7 @@ class TextHelperTest < ActionView::TestCase assert_equal("3", cycle(1, 2, 3)) assert_equal("1", cycle(1, 2, 3)) end - + def test_named_cycles assert_equal("1", cycle(1, 2, 3, :name => "numbers")) assert_equal("red", cycle("red", "blue", :name => "colors")) @@ -334,24 +325,24 @@ class TextHelperTest < ActionView::TestCase assert_equal("3", cycle(1, 2, 3, :name => "numbers")) assert_equal("red", cycle("red", "blue", :name => "colors")) end - + def test_default_named_cycle assert_equal("1", cycle(1, 2, 3)) assert_equal("2", cycle(1, 2, 3, :name => "default")) assert_equal("3", cycle(1, 2, 3)) end - + def test_reset_cycle assert_equal("1", cycle(1, 2, 3)) assert_equal("2", cycle(1, 2, 3)) reset_cycle assert_equal("1", cycle(1, 2, 3)) end - + def test_reset_unknown_cycle reset_cycle("colors") end - + def test_recet_named_cycle assert_equal("1", cycle(1, 2, 3, :name => "numbers")) assert_equal("red", cycle("red", "blue", :name => "colors")) @@ -361,7 +352,7 @@ class TextHelperTest < ActionView::TestCase assert_equal("2", cycle(1, 2, 3, :name => "numbers")) assert_equal("red", cycle("red", "blue", :name => "colors")) end - + def test_cycle_no_instance_variable_clashes @cycles = %w{Specialized Fuji Giant} assert_equal("red", cycle("red", "blue")) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 4a130bf5c0..cd526a52a7 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,4 +1,4 @@ -*SVN* +*2.1.0 RC1 (May 11th, 2008)* * Ensure hm:t preloading honours reflection options. Resolves #137. [Frederick Cheung] diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE index 5fee6e106d..93be57f683 100644 --- a/activerecord/MIT-LICENSE +++ b/activerecord/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2007 David Heinemeier Hansson +Copyright (c) 2004-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activerecord/Rakefile b/activerecord/Rakefile index d6033a9b85..043ab6d551 100755 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -171,7 +171,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD) s.files.delete FIXTURES_ROOT + "/fixture_database.sqlite" s.files.delete FIXTURES_ROOT + "/fixture_database_2.sqlite" diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 8b274120df..d4f7170305 100755 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index fb5f1f8a8c..c17e35f5e0 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1451,9 +1451,6 @@ module ActiveRecord join_dependency.joins_for_table_name(table) }.flatten.compact.uniq - - - is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order) sql = "SELECT " if is_distinct @@ -1500,26 +1497,28 @@ module ActiveRecord order.scan(/([\.\w]+).?\./).flatten end + def selects_tables(options) + select = options[:select] + return [] unless select && select.is_a?(String) + select.scan(/"?([\.\w]+)"?.?\./).flatten + end + # Checks if the conditions reference a table other than the current model table - def include_eager_conditions?(options,tables = nil) - tables = conditions_tables(options) - return false unless tables.any? - tables.any? do |condition_table_name| - condition_table_name != table_name - end + def include_eager_conditions?(options, tables = nil) + ((tables || conditions_tables(options)) - [table_name]).any? end # Checks if the query order references a table other than the current model's table. - def include_eager_order?(options,tables = nil) - tables = order_tables(options) - return false unless tables.any? - tables.any? do |order_table_name| - order_table_name != table_name - end + def include_eager_order?(options, tables = nil) + ((tables || order_tables(options)) - [table_name]).any? + end + + def include_eager_select?(options) + (selects_tables(options) - [table_name]).any? end def references_eager_loaded_tables?(options) - include_eager_order?(options) || include_eager_conditions?(options) + include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options) end def using_limitable_reflections?(reflections) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7999eec55d..5351f55200 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -438,7 +438,11 @@ module ActiveRecord #:nodoc: # adapters for, e.g., your development and test environments. cattr_accessor :schema_format , :instance_writer => false @@schema_format = :ruby - + + # Determine whether to store the full constant name including namespace when using STI + superclass_delegating_accessor :store_full_sti_class + self.store_full_sti_class = false + class << self # Class methods # Find operates with four different retrieval approaches: # @@ -522,7 +526,7 @@ module ActiveRecord #:nodoc: else find_from_ids(args, options) end end - + # This is an alias for find(:first). You can pass in all the same arguments to this method as you can # to find(:first) def first(*args) @@ -534,13 +538,13 @@ module ActiveRecord #:nodoc: def last(*args) find(:last, *args) end - + # This is an alias for find(:all). You can pass in all the same arguments to this method as you can # to find(:all) def all(*args) find(:all, *args) end - + # # Executes a custom sql query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call @@ -587,10 +591,10 @@ module ActiveRecord #:nodoc: def exists?(id_or_conditions) connection.select_all( construct_finder_sql( - :select => "#{quoted_table_name}.#{primary_key}", - :conditions => expand_id_conditions(id_or_conditions), + :select => "#{quoted_table_name}.#{primary_key}", + :conditions => expand_id_conditions(id_or_conditions), :limit => 1 - ), + ), "#{name} Exists" ).size > 0 end @@ -616,7 +620,7 @@ module ActiveRecord #:nodoc: # # Creating an Array of new objects using a block, where the block is executed for each object: # User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u| # u.is_admin = false - # end + # end def create(attributes = nil, &block) if attributes.is_a?(Array) attributes.collect { |attr| create(attr, &block) } @@ -1023,9 +1027,9 @@ module ActiveRecord #:nodoc: key = 'id' case primary_key_prefix_type when :table_name - key = Inflector.foreign_key(base_name, false) + key = base_name.to_s.foreign_key(false) when :table_name_with_underscore - key = Inflector.foreign_key(base_name) + key = base_name.to_s.foreign_key end key end @@ -1298,7 +1302,7 @@ module ActiveRecord #:nodoc: scoped_order = reverse_sql_order(scope(:find, :order)) scoped_methods.select { |s| s[:find].update(:order => scoped_order) } end - + find_initial(options.merge({ :order => order })) end @@ -1308,12 +1312,12 @@ module ActiveRecord #:nodoc: s.gsub!(/\s(asc|ASC)$/, ' DESC') elsif s.match(/\s(desc|DESC)$/) s.gsub!(/\s(desc|DESC)$/, ' ASC') - elsif !s.match(/\s(asc|ASC|desc|DESC)$/) + elsif !s.match(/\s(asc|ASC|desc|DESC)$/) s.concat(' DESC') end }.join(',') end - + def find_every(options) include_associations = merge_includes(scope(:find, :include), options[:include]) @@ -1557,8 +1561,8 @@ module ActiveRecord #:nodoc: def type_condition quoted_inheritance_column = connection.quote_column_name(inheritance_column) - type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{name.demodulize}' ") do |condition, subclass| - condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' " + type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{store_full_sti_class ? name : name.demodulize}' ") do |condition, subclass| + condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{store_full_sti_class ? subclass.name : subclass.name.demodulize}' " end " (#{type_condition}) " @@ -1566,8 +1570,8 @@ module ActiveRecord #:nodoc: # Guesses the table name, but does not decorate it with prefix and suffix information. def undecorated_table_name(class_name = base_class.name) - table_name = Inflector.underscore(Inflector.demodulize(class_name)) - table_name = Inflector.pluralize(table_name) if pluralize_table_names + table_name = class_name.to_s.demodulize.underscore + table_name = table_name.pluralize if pluralize_table_names table_name end @@ -1616,7 +1620,7 @@ module ActiveRecord #:nodoc: self.class_eval %{ def self.#{method_id}(*args) guard_protected_attributes = false - + if args[0].is_a?(Hash) guard_protected_attributes = true attributes = args[0].with_indifferent_access @@ -1629,7 +1633,7 @@ module ActiveRecord #:nodoc: set_readonly_option!(options) record = find_initial(options) - + if record.nil? record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) } #{'yield(record) if block_given?'} @@ -2129,14 +2133,14 @@ module ActiveRecord #:nodoc: # We can't use alias_method here, because method 'id' optimizes itself on the fly. (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes end - + # Returns a cache key that can be used to identify this record. Examples: # # Product.new.cache_key # => "products/new" # Product.find(5).cache_key # => "products/5" (updated_at not available) # Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available) def cache_key - case + case when new_record? "#{self.class.name.tableize}/new" when self[:updated_at] @@ -2170,7 +2174,7 @@ module ActiveRecord #:nodoc: # Note: If your model specifies any validations then the method declaration dynamically # changes to: # save(perform_validation=true) - # Calling save(false) saves the model without running validations. + # Calling save(false) saves the model without running validations. # See ActiveRecord::Validations for more information. def save create_or_update @@ -2342,7 +2346,7 @@ module ActiveRecord #:nodoc: # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. - def attributes(options = nil) + def attributes self.attribute_names.inject({}) do |attrs, name| attrs[name] = read_attribute(name) attrs @@ -2492,7 +2496,7 @@ module ActiveRecord #:nodoc: # Message class in that example. def ensure_proper_type unless self.class.descends_from_active_record? - write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name)) + write_attribute(self.class.inheritance_column, store_full_sti_class ? self.class.name : self.class.name.demodulize) end end diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index c6d89e3a05..6034963811 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -69,19 +69,19 @@ module ActiveRecord changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } end - - # Clear changed attributes after they are saved. + # Attempts to +save+ the record and clears changed attributes if successful. def save_with_dirty(*args) #:nodoc: - save_without_dirty(*args) - ensure - changed_attributes.clear + if status = save_without_dirty(*args) + changed_attributes.clear + end + status end - # Clear changed attributes after they are saved. + # Attempts to <tt>save!</tt> the record and clears changed attributes if successful. def save_with_dirty!(*args) #:nodoc: - save_without_dirty!(*args) - ensure + status = save_without_dirty!(*args) changed_attributes.clear + status end private diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 9367ea523d..ac06cdbe43 100755 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -197,20 +197,20 @@ end # class FooTest < ActiveSupport::TestCase # self.use_transactional_fixtures = true # self.use_instantiated_fixtures = false -# +# # fixtures :foos -# +# # def test_godzilla # assert !Foo.find(:all).empty? # Foo.destroy_all # assert Foo.find(:all).empty? # end -# +# # def test_godzilla_aftermath # assert !Foo.find(:all).empty? # end # end -# +# # If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, # then you may omit all fixtures declarations in your test cases since all the data's already there and every case rolls back its changes. # @@ -730,7 +730,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash) reader.each do |row| data = {} row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip } - self["#{Inflector::underscore(@class_name)}_#{i+=1}"] = Fixture.new(data, model_class) + self["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class) end end @@ -854,14 +854,14 @@ module Test #:nodoc: require_dependency file_name rescue LoadError => e # Let's hope the developer has included it himself - + # Let's warn in case this is a subdependency, otherwise # subdependency error messages are totally cryptic if ActiveRecord::Base.logger ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}") end end - + def require_fixture_classes(table_names = nil) (table_names || fixture_table_names).each do |table_name| file_name = table_name.to_s diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index a8ee7dbeb9..1463e84764 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 MINOR = 0 - TINY = 2 + TINY = 991 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 7412e63872..1266eb5036 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -78,7 +78,7 @@ class DirtyTest < ActiveRecord::TestCase end def test_association_assignment_changes_foreign_key - pirate = Pirate.create! + pirate = Pirate.create!(:catchphrase => 'jarl') pirate.parrot = Parrot.create! assert pirate.changed? assert_equal %w(parrot_id), pirate.changed @@ -115,6 +115,18 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_changed_attributes_should_be_preserved_if_save_failure + pirate = Pirate.new + pirate.parrot_id = 1 + assert !pirate.save + check_pirate_after_save_failure(pirate) + + pirate = Pirate.new + pirate.parrot_id = 1 + assert_raises(ActiveRecord::RecordInvalid) { pirate.save! } + check_pirate_after_save_failure(pirate) + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? @@ -123,4 +135,11 @@ class DirtyTest < ActiveRecord::TestCase ensure klass.partial_updates = old end + + def check_pirate_after_save_failure(pirate) + assert pirate.changed? + assert pirate.parrot_id_changed? + assert_equal %w(parrot_id), pirate.changed + assert_nil pirate.parrot_id_was + end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 2acfe9b387..5c0f0e2ef1 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -9,6 +9,7 @@ require 'models/developer' require 'models/post' require 'models/customer' require 'models/job' +require 'models/categorization' class FinderTest < ActiveRecord::TestCase fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers @@ -859,12 +860,17 @@ class FinderTest < ActiveRecord::TestCase end def test_find_with_order_on_included_associations_with_construct_finder_sql_for_association_limiting_and_is_distinct - assert_equal 2, Post.find(:all,:include=>{:authors=>:author_address},:order=>' author_addresses.id DESC ', :limit=>2).size + assert_equal 2, Post.find(:all, :include => { :authors => :author_address }, :order => ' author_addresses.id DESC ', :limit => 2).size - assert_equal 3, Post.find(:all,:include=>{:author=>:author_address,:authors=>:author_address}, - :order=>' author_addresses_authors.id DESC ', :limit=>3).size + assert_equal 3, Post.find(:all, :include => { :author => :author_address, :authors => :author_address}, + :order => ' author_addresses_authors.id DESC ', :limit => 3).size end + def test_with_limiting_with_custom_select + posts = Post.find(:all, :include => :author, :select => ' posts.*, authors.id as "author_id"', :limit => 3) + assert_equal 3, posts.size + assert_equal [0, 1, 1], posts.map(&:author_id).sort + end protected def bind(statement, *vars) diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 2787b9a39d..aca7cfb367 100755 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -610,15 +610,17 @@ class ActiveSupportSubclassWithFixturesTest < ActiveRecord::TestCase end class FixtureLoadingTest < ActiveRecord::TestCase - def test_logs_message_for_failed_dependency_load - Test::Unit::TestCase.expects(:require_dependency).with(:does_not_exist).raises(LoadError) - ActiveRecord::Base.logger.expects(:warn) - Test::Unit::TestCase.try_to_load_dependency(:does_not_exist) - end + uses_mocha 'reloading_fixtures_through_accessor_methods' do + def test_logs_message_for_failed_dependency_load + Test::Unit::TestCase.expects(:require_dependency).with(:does_not_exist).raises(LoadError) + ActiveRecord::Base.logger.expects(:warn) + Test::Unit::TestCase.try_to_load_dependency(:does_not_exist) + end - def test_does_not_logs_message_for_successful_dependency_load - Test::Unit::TestCase.expects(:require_dependency).with(:works_out_fine) - ActiveRecord::Base.logger.expects(:warn).never - Test::Unit::TestCase.try_to_load_dependency(:works_out_fine) + def test_does_not_logs_message_for_successful_dependency_load + Test::Unit::TestCase.expects(:require_dependency).with(:works_out_fine) + ActiveRecord::Base.logger.expects(:warn).never + Test::Unit::TestCase.try_to_load_dependency(:works_out_fine) + end end end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index c9eb83e371..27394924a1 100755 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -5,6 +5,34 @@ require 'models/subscriber' class InheritanceTest < ActiveRecord::TestCase fixtures :companies, :projects, :subscribers, :accounts + + def test_should_store_demodulized_class_name_with_store_full_sti_class_option_disabled + old = ActiveRecord::Base.store_full_sti_class + ActiveRecord::Base.store_full_sti_class = false + item = Namespaced::Company.new + assert_equal 'Company', item[:type] + ensure + ActiveRecord::Base.store_full_sti_class = old + end + + def test_should_store_full_class_name_with_store_full_sti_class_option_enabled + old = ActiveRecord::Base.store_full_sti_class + ActiveRecord::Base.store_full_sti_class = true + item = Namespaced::Company.new + assert_equal 'Namespaced::Company', item[:type] + ensure + ActiveRecord::Base.store_full_sti_class = old + end + + def test_different_namespace_subclass_should_load_correctly_with_store_full_sti_class_option + old = ActiveRecord::Base.store_full_sti_class + ActiveRecord::Base.store_full_sti_class = true + item = Namespaced::Company.create :name => "Wolverine 2" + assert_not_nil Company.find(item.id) + assert_not_nil Namespaced::Company.find(item.id) + ensure + ActiveRecord::Base.store_full_sti_class = old + end def test_company_descends_from_active_record assert_raise(NoMethodError) { ActiveRecord::Base.descends_from_active_record? } diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 3d76dfd398..f637490c59 100755 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -15,6 +15,10 @@ class Company < AbstractCompany end end +module Namespaced + class Company < ::Company + end +end class Firm < Company has_many :clients, :order => "id", :dependent => :destroy, :counter_sql => diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index bb4d02c10f..51c8183dee 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -4,4 +4,6 @@ class Pirate < ActiveRecord::Base has_many :treasures, :as => :looter has_many :treasure_estimates, :through => :treasures, :source => :price_estimates + + validates_presence_of :catchphrase end diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG index 7becf4fc0c..9aa7d455a2 100644 --- a/activeresource/CHANGELOG +++ b/activeresource/CHANGELOG @@ -1,4 +1,4 @@ -*SVN* +*2.1.0 RC1 (May 11th, 2008)* * Fixed response logging to use length instead of the entire thing (seangeo) [#27] diff --git a/activeresource/Rakefile b/activeresource/Rakefile index 8af78ceb55..75fe52aea8 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -64,7 +64,7 @@ spec = Gem::Specification.new do |s| s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) } end - s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD) + s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'active_resource' diff --git a/activeresource/lib/active_resource/version.rb b/activeresource/lib/active_resource/version.rb index 4f7de5e1a0..34fb05b703 100644 --- a/activeresource/lib/active_resource/version.rb +++ b/activeresource/lib/active_resource/version.rb @@ -2,7 +2,7 @@ module ActiveResource module VERSION #:nodoc: MAJOR = 2 MINOR = 0 - TINY = 2 + TINY = 991 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 185aff9088..e8821060f9 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,4 +1,4 @@ -*SVN* +*2.1.0 RC1 (May 11th, 2008)* * Remove unused JSON::RESERVED_WORDS, JSON.valid_identifier? and JSON.reserved_word? methods. Resolves #164. [Cheah Chu Yeow] diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index dbe78035ba..2ba4e17035 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2007 David Heinemeier Hansson +Copyright (c) 2005-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 4d2f873a8d..e4cb145c98 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -39,6 +39,7 @@ require 'active_support/cache' require 'active_support/dependencies' require 'active_support/deprecation' +require 'active_support/ordered_hash' require 'active_support/ordered_options' require 'active_support/option_merger' diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index 371d074d34..8724a492bf 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -128,7 +128,7 @@ class Class # :nodoc: new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES else new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)| - memo.update(key => (value.dup rescue value)) + memo.update(key => value.duplicable? ? value.dup : value) end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 7613652c71..6aa379b550 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -176,19 +176,6 @@ module ActiveSupport end end - class DeprecatedInstanceVariable < Delegator #:nodoc: - def initialize(value, method) - super(value) - @method = method - @value = value - end - - def __getobj__ - ActiveSupport::Deprecation.warn("Instance variable @#{@method} is deprecated! Call instance method #{@method} instead.") - @value - end - end - end end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb new file mode 100644 index 0000000000..6993621ef9 --- /dev/null +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -0,0 +1,43 @@ +# OrderedHash is namespaced to prevent conflicts with other implementations +module ActiveSupport + # Hash is ordered in Ruby 1.9! + if RUBY_VERSION >= '1.9' + OrderedHash = ::Hash + else + class OrderedHash < Array #:nodoc: + def []=(key, value) + if pair = assoc(key) + pair.pop + pair << value + else + self << [key, value] + end + end + + def [](key) + pair = assoc(key) + pair ? pair.last : nil + end + + def delete(key) + pair = assoc(key) + pair ? array_index = index(pair) : nil + array_index ? delete_at(array_index).last : nil + end + + def keys + collect { |key, value| key } + end + + def values + collect { |key, value| value } + end + + def to_hash + returning({}) do |hash| + each { |array| hash[array[0]] = array[1] } + end + end + end + end +end diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 3172f62f0d..306376e9ae 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,47 +1,3 @@ -# OrderedHash is namespaced to prevent conflicts with other implementations -module ActiveSupport - # Hash is ordered in Ruby 1.9! - if RUBY_VERSION >= '1.9' - OrderedHash = ::Hash - else - class OrderedHash < Array #:nodoc: - def []=(key, value) - if pair = assoc(key) - pair.pop - pair << value - else - self << [key, value] - end - end - - def [](key) - pair = assoc(key) - pair ? pair.last : nil - end - - def delete(key) - pair = assoc(key) - pair ? array_index = index(pair) : nil - array_index ? delete_at(array_index).last : nil - end - - def keys - collect { |key, value| key } - end - - def values - collect { |key, value| value } - end - - def to_hash - returning({}) do |hash| - each { |array| hash[array[0]] = array[1] } - end - end - end - end -end - class OrderedOptions < ActiveSupport::OrderedHash #:nodoc: def []=(key, value) super(key.to_sym, value) diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 21ddcaad48..48606dbcff 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -78,7 +78,7 @@ module ActiveSupport def to_json(options = nil) if ActiveSupport.use_standard_json_time_format - utc.xmlschema.inspect + xmlschema.inspect else %("#{time.strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}") end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 0fa99135e2..f522b64108 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -325,6 +325,9 @@ class TimeZone ZONES.sort! ZONES.freeze ZONES_MAP.freeze + + US_ZONES = ZONES.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } + US_ZONES.freeze end class << self @@ -361,14 +364,10 @@ class TimeZone end end - # A regular expression that matches the names of all time zones in - # the USA. - US_ZONES = /US|Arizona|Indiana|Hawaii|Alaska/.freeze - # A convenience method for returning a collection of TimeZone objects # for time zones in the USA. def us_zones - all.find_all { |z| z.name =~ US_ZONES } + US_ZONES end end end diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 83fbaec62c..f3d141cf72 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -2,7 +2,7 @@ module ActiveSupport module VERSION #:nodoc: MAJOR = 2 MINOR = 0 - TINY = 2 + TINY = 991 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 64fcb4af09..70c393dd46 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -70,7 +70,7 @@ class TimeWithZoneTest < Test::Unit::TestCase def test_to_json_with_use_standard_json_time_format_config_set_to_true old, ActiveSupport.use_standard_json_time_format = ActiveSupport.use_standard_json_time_format, true - assert_equal "\"2000-01-01T00:00:00Z\"", @twz.to_json + assert_equal "\"1999-12-31T19:00:00-05:00\"", @twz.to_json ensure ActiveSupport.use_standard_json_time_format = old end diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 11357e250f..ebfa405947 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -149,13 +149,3 @@ class DeprecationTest < Test::Unit::TestCase assert_nil @last_message end end - -class DeprecatedIvarTest < Test::Unit::TestCase - - def test_deprecated_ivar - @action = ActiveSupport::Deprecation::DeprecatedInstanceVariable.new("fubar", :foobar) - - assert_deprecated(/Instance variable @foobar is deprecated! Call instance method foobar instead/) { assert_equal "fubar", @action } - end - -end diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb new file mode 100644 index 0000000000..14be48724e --- /dev/null +++ b/activesupport/test/ordered_hash_test.rb @@ -0,0 +1,45 @@ +require 'abstract_unit' + +class OrderedHashTest < Test::Unit::TestCase + def setup + @keys = %w( blue green red pink orange ) + @values = %w( 000099 009900 aa0000 cc0066 cc6633 ) + @ordered_hash = ActiveSupport::OrderedHash.new + + @keys.each_with_index do |key, index| + @ordered_hash[key] = @values[index] + end + end + + def test_order + assert_equal @keys, @ordered_hash.keys + assert_equal @values, @ordered_hash.values + end + + def test_access + assert @keys.zip(@values).all? { |k, v| @ordered_hash[k] == v } + end + + def test_assignment + key, value = 'purple', '5422a8' + + @ordered_hash[key] = value + assert_equal @keys.length + 1, @ordered_hash.length + assert_equal key, @ordered_hash.keys.last + assert_equal value, @ordered_hash.values.last + assert_equal value, @ordered_hash[key] + end + + def test_delete + key, value = 'white', 'ffffff' + bad_key = 'black' + + @ordered_hash[key] = value + assert_equal @keys.length + 1, @ordered_hash.length + + assert_equal value, @ordered_hash.delete(key) + assert_equal @keys.length, @ordered_hash.length + + assert_nil @ordered_hash.delete(bad_key) + end +end diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb index 3d537a0ae4..fb7a58d0b0 100644 --- a/activesupport/test/ordered_options_test.rb +++ b/activesupport/test/ordered_options_test.rb @@ -1,49 +1,5 @@ require 'abstract_unit' -class OrderedHashTest < Test::Unit::TestCase - def setup - @keys = %w( blue green red pink orange ) - @values = %w( 000099 009900 aa0000 cc0066 cc6633 ) - @ordered_hash = ActiveSupport::OrderedHash.new - - @keys.each_with_index do |key, index| - @ordered_hash[key] = @values[index] - end - end - - def test_order - assert_equal @keys, @ordered_hash.keys - assert_equal @values, @ordered_hash.values - end - - def test_access - assert @keys.zip(@values).all? { |k, v| @ordered_hash[k] == v } - end - - def test_assignment - key, value = 'purple', '5422a8' - - @ordered_hash[key] = value - assert_equal @keys.length + 1, @ordered_hash.length - assert_equal key, @ordered_hash.keys.last - assert_equal value, @ordered_hash.values.last - assert_equal value, @ordered_hash[key] - end - - def test_delete - key, value = 'white', 'ffffff' - bad_key = 'black' - - @ordered_hash[key] = value - assert_equal @keys.length + 1, @ordered_hash.length - - assert_equal value, @ordered_hash.delete(key) - assert_equal @keys.length, @ordered_hash.length - - assert_nil @ordered_hash.delete(bad_key) - end -end - class OrderedOptionsTest < Test::Unit::TestCase def test_usage a = OrderedOptions.new diff --git a/pushgems.rb b/pushgems.rb index 6a1f1ffe8f..784aa5de68 100755 --- a/pushgems.rb +++ b/pushgems.rb @@ -1,7 +1,7 @@ #!/usr/bin/env ruby unless ARGV.first == "no_build" - build_number = build_number = `svn log -q -rhead http://dev.rubyonrails.org/svn/rails`.scan(/r([0-9]*)/).first.first.to_i + build_number = Time.now.strftime("%Y%m%d%H%M%S").to_i end %w( activeresource actionmailer actionpack activerecord railties activesupport ).each do |pkg| diff --git a/railties/CHANGELOG b/railties/CHANGELOG index 2ca1965d97..11d2926f31 100644 --- a/railties/CHANGELOG +++ b/railties/CHANGELOG @@ -1,4 +1,4 @@ -*SVN* +*2.1.0 RC1 (May 11th, 2008)* * script/dbconsole fires up the command-line database client. #102 [Steve Purcell] diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index 5fee6e106d..93be57f683 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2007 David Heinemeier Hansson +Copyright (c) 2004-2008 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/railties/Rakefile b/railties/Rakefile index 4eaa1bef63..45ba394299 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -303,12 +303,12 @@ spec = Gem::Specification.new do |s| on top of either MySQL, PostgreSQL, SQLite, DB2, SQL Server, or Oracle with eRuby- or Builder-based templates. EOF - s.add_dependency('rake', '>= 0.7.2') - s.add_dependency('activesupport', '= 2.0.2' + PKG_BUILD) - s.add_dependency('activerecord', '= 2.0.2' + PKG_BUILD) - s.add_dependency('actionpack', '= 2.0.2' + PKG_BUILD) - s.add_dependency('actionmailer', '= 2.0.2' + PKG_BUILD) - s.add_dependency('activeresource', '= 2.0.2' + PKG_BUILD) + s.add_dependency('rake', '>= 0.8.1') + s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD) + s.add_dependency('activerecord', '= 2.0.991' + PKG_BUILD) + s.add_dependency('actionpack', '= 2.0.991' + PKG_BUILD) + s.add_dependency('actionmailer', '= 2.0.991' + PKG_BUILD) + s.add_dependency('activeresource', '= 2.0.991' + PKG_BUILD) s.rdoc_options << '--exclude' << '.' s.has_rdoc = false diff --git a/railties/configs/initializers/new_rails_defaults.rb b/railties/configs/initializers/new_rails_defaults.rb index 3a617e3ae3..b8f0e2cac2 100644 --- a/railties/configs/initializers/new_rails_defaults.rb +++ b/railties/configs/initializers/new_rails_defaults.rb @@ -7,6 +7,9 @@ ActiveRecord::Base.partial_updates = true # Include ActiveRecord class name as root for JSON serialized output. ActiveRecord::Base.include_root_in_json = true +# Store the full class name (including module namespace) in STI type column +ActiveRecord::Base.store_full_sti_class = true + # Use ISO 8601 format for JSON serialized times and dates ActiveSupport.use_standard_json_time_format = true diff --git a/railties/html/javascripts/controls.js b/railties/html/javascripts/controls.js index fbc4418b83..5aaf0bb2b7 100644 --- a/railties/html/javascripts/controls.js +++ b/railties/html/javascripts/controls.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) // Contributors: diff --git a/railties/html/javascripts/dragdrop.js b/railties/html/javascripts/dragdrop.js index ccf4a1e45c..bf5cfea66c 100644 --- a/railties/html/javascripts/dragdrop.js +++ b/railties/html/javascripts/dragdrop.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // script.aculo.us is freely distributable under the terms of an MIT-style license. diff --git a/railties/html/javascripts/effects.js b/railties/html/javascripts/effects.js index 65aed23957..f030b5dbe9 100644 --- a/railties/html/javascripts/effects.js +++ b/railties/html/javascripts/effects.js @@ -1,4 +1,4 @@ -// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) diff --git a/railties/lib/commands/dbconsole.rb b/railties/lib/commands/dbconsole.rb index 28c3a3e41f..3e5010c688 100644 --- a/railties/lib/commands/dbconsole.rb +++ b/railties/lib/commands/dbconsole.rb @@ -1,3 +1,4 @@ +require 'erb' require 'yaml' require 'optparse' @@ -8,7 +9,7 @@ OptionParser.new do |opt| end env = ARGV.first || ENV['RAILS_ENV'] || 'development' -unless config = YAML.load_file(RAILS_ROOT + "/config/database.yml")[env] +unless config = YAML::load(ERB.new(IO.read(RAILS_ROOT + "/config/database.yml")).result)[env] abort "No database is configured for the environment '#{env}'" end diff --git a/railties/lib/dispatcher.rb b/railties/lib/dispatcher.rb index 9db424f14b..0bad45d9d8 100644 --- a/railties/lib/dispatcher.rb +++ b/railties/lib/dispatcher.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2007 David Heinemeier Hansson +# Copyright (c) 2004-2008 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb index fd7de4ee28..f06a51a572 100644 --- a/railties/lib/rails/plugin/locator.rb +++ b/railties/lib/rails/plugin/locator.rb @@ -78,11 +78,12 @@ module Rails # a <tt>rails/init.rb</tt> file. class GemLocator < Locator def plugins - specs = initializer.configuration.gems.map(&:specification) - specs + Gem.loaded_specs.values.select do |spec| + specs = initializer.configuration.gems.map(&:specification) + specs += Gem.loaded_specs.values.select do |spec| spec.loaded_from && # prune stubs File.exist?(File.join(spec.full_gem_path, "rails", "init.rb")) end + specs.compact! require "rubygems/dependency_list" diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index da90645738..fea63beea9 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -2,7 +2,7 @@ module Rails module VERSION #:nodoc: MAJOR = 2 MINOR = 0 - TINY = 2 + TINY = 991 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb index 440bab276c..03b7d354a6 100644 --- a/railties/lib/rails_generator/commands.rb +++ b/railties/lib/rails_generator/commands.rb @@ -154,28 +154,35 @@ HELP # Ruby or Rails. In the future, expand to check other namespaces # such as the rest of the user's app. def class_collisions(*class_names) + + # Initialize some check varibles + last_class = Object + current_class = nil + name = nil + class_names.flatten.each do |class_name| # Convert to string to allow symbol arguments. class_name = class_name.to_s # Skip empty strings. - next if class_name.strip.empty? + class_name.strip.empty? ? next : current_class = class_name # Split the class from its module nesting. nesting = class_name.split('::') name = nesting.pop # Extract the last Module in the nesting. - last = nesting.inject(Object) { |last, nest| - break unless last.const_defined?(nest) - last.const_get(nest) + last = nesting.inject(last_class) { |last, nest| + break unless last_class.const_defined?(nest) + last_class = last_class.const_get(nest) } - # If the last Module exists, check whether the given - # class exists and raise a collision if so. - if last and last.const_defined?(name.camelize) - raise_class_collision(class_name) - end + end + # If the last Module exists, check whether the given + # class exists and raise a collision if so. + + if last_class and last_class.const_defined?(name.camelize) + raise_class_collision(current_class) end end diff --git a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb index 864a0bc528..e2902bf4b9 100644 --- a/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb +++ b/railties/lib/rails_generator/generators/components/scaffold/scaffold_generator.rb @@ -34,7 +34,7 @@ class ScaffoldGenerator < Rails::Generator::NamedBase m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper") m.class_collisions(class_path, "#{class_name}") - # Controller, helper, views, and test directories. + # Controller, helper, views, test and stylesheets directories. m.directory(File.join('app/models', class_path)) m.directory(File.join('app/controllers', controller_class_path)) m.directory(File.join('app/helpers', controller_class_path)) @@ -42,6 +42,7 @@ class ScaffoldGenerator < Rails::Generator::NamedBase m.directory(File.join('app/views/layouts', controller_class_path)) m.directory(File.join('test/functional', controller_class_path)) m.directory(File.join('test/unit', class_path)) + m.directory(File.join('public/stylesheets', class_path)) for action in scaffold_views m.template( diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake index 63f71448f8..f40f8463f7 100644 --- a/railties/lib/tasks/databases.rake +++ b/railties/lib/tasks/databases.rake @@ -45,7 +45,7 @@ namespace :db do when 'postgresql' @encoding = config[:encoding] || ENV['CHARSET'] || 'utf8' begin - ActiveRecord::Base.establish_connection(config.merge('database' => 'template1')) + ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding)) ActiveRecord::Base.establish_connection(config) rescue @@ -368,7 +368,7 @@ def drop_database(config) when /^sqlite/ FileUtils.rm(File.join(RAILS_ROOT, config['database'])) when 'postgresql' - ActiveRecord::Base.establish_connection(config.merge('database' => 'template1')) + ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public')) ActiveRecord::Base.connection.drop_database config['database'] end end diff --git a/railties/test/generators/generator_test_helper.rb b/railties/test/generators/generator_test_helper.rb index 190bc91d52..c99df6e2d3 100644 --- a/railties/test/generators/generator_test_helper.rb +++ b/railties/test/generators/generator_test_helper.rb @@ -13,7 +13,7 @@ module ActiveRecord module ConnectionAdapters class Column attr_reader :name, :default, :type, :limit, :null, :sql_type, :precision, :scale - + def initialize(name, default, sql_type = nil) @name = name @default = default @@ -59,16 +59,16 @@ require 'rails_generator' class GeneratorTestCase < Test::Unit::TestCase include FileUtils - + def setup ActiveRecord::Base.pluralize_table_names = true - + mkdir_p "#{RAILS_ROOT}/app/views/layouts" mkdir_p "#{RAILS_ROOT}/config" mkdir_p "#{RAILS_ROOT}/db" mkdir_p "#{RAILS_ROOT}/test/fixtures" mkdir_p "#{RAILS_ROOT}/public/stylesheets" - + File.open("#{RAILS_ROOT}/config/routes.rb", 'w') do |f| f << "ActionController::Routing::Routes.draw do |map|\n\nend" end @@ -85,7 +85,7 @@ class GeneratorTestCase < Test::Unit::TestCase def test_truth # don't complain, test/unit end - + # Instantiates the Generator def build_generator(name, params) Rails::Generator::Base.instance(name, params) @@ -171,9 +171,16 @@ class GeneratorTestCase < Test::Unit::TestCase # asserts that the given class source file was generated. # It takes a path without the <tt>.rb</tt> part and an optional super class. # the contents of the class source file is passed to a block. - def assert_generated_class(path, parent=nil) - path =~ /\/?(\d+_)?(\w+)$/ - class_name = $2.camelize + def assert_generated_class(path, parent = nil) + # FIXME: Sucky way to detect namespaced classes + if path.split('/').size > 3 + path =~ /\/?(\d+_)?(\w+)\/(\w+)$/ + class_name = "#{$2.camelize}::#{$3.camelize}" + else + path =~ /\/?(\d+_)?(\w+)$/ + class_name = $2.camelize + end + assert_generated_file("#{path}.rb") do |body| assert_match /class #{class_name}#{parent.nil? ? '':" < #{parent}"}/, body, "the file '#{path}.rb' should be a class" yield body if block_given? @@ -184,8 +191,15 @@ class GeneratorTestCase < Test::Unit::TestCase # It takes a path without the <tt>.rb</tt> part. # the contents of the class source file is passed to a block. def assert_generated_module(path) - path =~ /\/?(\w+)$/ - module_name = $1.camelize + # FIXME: Sucky way to detect namespaced modules + if path.split('/').size > 3 + path =~ /\/?(\w+)\/(\w+)$/ + module_name = "#{$1.camelize}::#{$2.camelize}" + else + path =~ /\/?(\w+)$/ + module_name = $1.camelize + end + assert_generated_file("#{path}.rb") do |body| assert_match /module #{module_name}/, body, "the file '#{path}.rb' should be a module" yield body if block_given? diff --git a/railties/test/generators/rails_controller_generator_test.rb b/railties/test/generators/rails_controller_generator_test.rb new file mode 100644 index 0000000000..0090d21b85 --- /dev/null +++ b/railties/test/generators/rails_controller_generator_test.rb @@ -0,0 +1,20 @@ +require 'generators/generator_test_helper' + +class RailsControllerGeneratorTest < GeneratorTestCase + + def test_controller_generates_controller + run_generator('controller', %w(products)) + + assert_generated_controller_for :products + assert_generated_functional_test_for :products + assert_generated_helper_for :products + end + + def test_controller_generates_namespaced_controller + run_generator('controller', %w(admin::products)) + + assert_generated_controller_for "admin::products" + assert_generated_functional_test_for "admin::products" + assert_generated_helper_for "admin::products" + end +end |