diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-11-17 22:47:23 +0000 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-11-17 22:47:23 +0000 |
commit | 5446d5cb05b50a9a3f317ded774be438e0eff909 (patch) | |
tree | 6b0b87efe3e95783763208215a3159fb63217a6d | |
parent | 9754debb9a72f9385950e5282f3642b995ab76d8 (diff) | |
parent | f8877d4b2a2a6f68770b376f0b1391a6295f62f2 (diff) | |
download | rails-5446d5cb05b50a9a3f317ded774be438e0eff909.tar.gz rails-5446d5cb05b50a9a3f317ded774be438e0eff909.tar.bz2 rails-5446d5cb05b50a9a3f317ded774be438e0eff909.zip |
Merge remote branch 'mainstream/master'
Conflicts:
activesupport/lib/active_support/core_ext/hash/conversions.rb
659 files changed, 11342 insertions, 12426 deletions
diff --git a/.gitignore b/.gitignore index da296e7e11..70b7c0057a 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ railties/guides/output *.rbc *.swp *.swo -actionpack/bin +*.tmproj +bin vendor/gems/ -*/vendor/gems/ railties/tmp diff --git a/.gitmodules b/.gitmodules index e69de29bb2..20ed3ed9c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "arel"] + path = arel + url = git://github.com/rails/arel.git +[submodule "rack-mount"] + path = rack-mount + url = git://github.com/rails/rack-mount.git diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..8f4fed2cdd --- /dev/null +++ b/Gemfile @@ -0,0 +1,36 @@ +clear_sources +source 'http://gemcutter.org' + +gem "rake", ">= 0.8.7" +gem "mocha", ">= 0.9.8" + +gem "rails", "3.0.pre", :vendored_at => "railties" +%w(activesupport activemodel actionpack actionmailer activerecord activeresource).each do |lib| + gem lib, '3.0.pre', :vendored_at => lib +end + +# AR +gem "arel", "0.2.pre", :git => "git://github.com/rails/arel.git" +gem "sqlite3-ruby", ">= 1.2.5" +gem "pg", ">= 0.8.0" +gem "mysql", ">= 2.8.1" + +# AP +gem "rack", "1.0.1", :git => "git://github.com/rails/rack.git" +gem "rack-mount", :git => "git://github.com/rails/rack-mount.git" +gem "RedCloth", ">= 4.2.2" + +if ENV['CI'] + disable_system_gems + + gem "nokogiri", ">= 1.4.0" + gem "memcache-client", ">= 1.7.6" + + # fcgi gem doesn't compile on 1.9 + # avoid minitest strangeness on 1.9 + if RUBY_VERSION < '1.9.0' + gem "fcgi", ">= 0.8.7" + else + gem "test-unit", ">= 2.0.5" + end +end @@ -10,9 +10,9 @@ Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path| end desc 'Run all tests by default' -task :default => :test +task :default => %w(test test:isolated) -%w(test isolated_test rdoc pgem package release gem gemspec).each do |task_name| +%w(test test:isolated rdoc pgem package release gem gemspec).each do |task_name| desc "Run #{task_name} task for all projects" task task_name do errors = [] diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index a756f35ee8..1a7ece5068 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -24,14 +24,16 @@ Rake::TestTask.new { |t| t.libs << "test" t.pattern = 'test/*_test.rb' t.verbose = true - t.warning = false + t.warning = true } -task :isolated_test do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - Dir.glob("test/*_test.rb").all? do |file| - system(ruby, '-Ilib:test', file) - end or raise "Failures" +namespace :test do + task :isolated do + ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) + Dir.glob("test/*_test.rb").all? do |file| + system(ruby, '-Ilib:test', file) + end or raise "Failures" + end end # Generate the RDoc documentation @@ -44,25 +46,24 @@ Rake::RDocTask.new { |rdoc| rdoc.rdoc_files.include('README', 'CHANGELOG') rdoc.rdoc_files.include('lib/action_mailer.rb') rdoc.rdoc_files.include('lib/action_mailer/*.rb') + rdoc.rdoc_files.include('lib/action_mailer/delivery_method/*.rb') } spec = eval(File.read('actionmailer.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end desc "Publish the API documentation" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload end diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index a427376579..23f04a11ba 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -32,8 +32,9 @@ module ActionMailer end autoload :AdvAttrAccessor, 'action_mailer/adv_attr_accessor' + autoload :DeprecatedBody, 'action_mailer/deprecated_body' autoload :Base, 'action_mailer/base' - autoload :Helpers, 'action_mailer/helpers' + autoload :DeliveryMethod, 'action_mailer/delivery_method' autoload :Part, 'action_mailer/part' autoload :PartContainer, 'action_mailer/part_container' autoload :Quoting, 'action_mailer/quoting' diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index eb648d249a..dfa1b6444b 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -1,8 +1,4 @@ -require 'tmpdir' - -require "active_support/core_ext/class" -# Use the old layouts until actionmailer gets refactored -require "action_controller/legacy/layout" +require 'active_support/core_ext/class' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -232,7 +228,7 @@ module ActionMailer #:nodoc: # * <tt>raise_delivery_errors</tt> - Whether or not errors should be raised if the email fails to be delivered. # # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:test</tt>, - # and <tt>:file</tt>. + # and <tt>:file</tt>. Or you may provide a custom delivery method object eg. MyOwnDeliveryMethodClass.new # # * <tt>perform_deliveries</tt> - Determines whether <tt>deliver_*</tt> methods are actually carried out. By default they are, # but this can be turned off to help functional testing. @@ -256,13 +252,20 @@ module ActionMailer #:nodoc: # +implicit_parts_order+. class Base include AdvAttrAccessor, PartContainer, Quoting, Utils - extend AbstractController::RenderingController + + include AbstractController::RenderingController + include AbstractController::LocalizedCache + include AbstractController::Layouts + + include AbstractController::Helpers + helper MailHelper if Object.const_defined?(:ActionController) include ActionController::UrlWriter - include ActionController::Layout end + include ActionMailer::DeprecatedBody + private_class_method :new #:nodoc: class_inheritable_accessor :view_paths @@ -270,35 +273,9 @@ module ActionMailer #:nodoc: cattr_accessor :logger - @@smtp_settings = { - :address => "localhost", - :port => 25, - :domain => 'localhost.localdomain', - :user_name => nil, - :password => nil, - :authentication => nil, - :enable_starttls_auto => true, - } - cattr_accessor :smtp_settings - - @@sendmail_settings = { - :location => '/usr/sbin/sendmail', - :arguments => '-i -t' - } - cattr_accessor :sendmail_settings - - @@file_settings = { - :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" - } - - cattr_accessor :file_settings - @@raise_delivery_errors = true cattr_accessor :raise_delivery_errors - superclass_delegating_accessor :delivery_method - self.delivery_method = :smtp - @@perform_deliveries = true cattr_accessor :perform_deliveries @@ -317,17 +294,12 @@ module ActionMailer #:nodoc: @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ] cattr_accessor :default_implicit_parts_order + @@protected_instance_variables = [] cattr_reader :protected_instance_variables - @@protected_instance_variables = %w(@body) # 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 @@ -372,43 +344,42 @@ module ActionMailer #:nodoc: # have multiple mailer methods share the same template. adv_attr_accessor :template + # The mail and action_name instances referenced by this mailer. + attr_reader :mail, :action_name + + # Where the response body is stored. + attr_internal :response_body + # Override the mailer name, which defaults to an inflected version of the # mailer's class name. If you want to use a template in a non-standard # location, you can use this to specify that location. + attr_writer :mailer_name + def mailer_name(value = nil) if value - self.mailer_name = value + @mailer_name = value else - self.class.mailer_name + @mailer_name || self.class.mailer_name end end - def mailer_name=(value) - self.class.mailer_name = value - end - - # The mail object instance referenced by this mailer. - attr_reader :mail - attr_reader :template_name, :default_template_name, :action_name - - def controller_path - self.class.controller_path - end - - def formats - @template.formats - end + # Alias controller_path to mailer_name so render :partial in views work. + alias :controller_path :mailer_name class << self attr_writer :mailer_name + delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::File, :prefix => :file + delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Sendmail, :prefix => :sendmail + delegate :settings, :settings=, :to => ActionMailer::DeliveryMethod::Smtp, :prefix => :smtp + def mailer_name @mailer_name ||= name.underscore end - # for ActionView compatibility - alias_method :controller_name, :mailer_name - alias_method :controller_path, :mailer_name + def delivery_method=(method_name) + @delivery_method = ActionMailer::DeliveryMethod.lookup_method(method_name) + end def respond_to?(method_symbol, include_private = false) #:nodoc: matches_dynamic_method?(method_symbol) || super @@ -459,6 +430,7 @@ module ActionMailer #:nodoc: self.view_paths && self.view_paths.first end + # Should template root overwrite the whole view_paths? def template_root=(root) self.view_paths = ActionView::Base.process_view_paths(root) end @@ -470,73 +442,36 @@ module ActionMailer #:nodoc: end end + # Configure delivery method. Check ActionMailer::DeliveryMethod for more + # instructions. + superclass_delegating_reader :delivery_method + self.delivery_method = :smtp + # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer # will be initialized according to the named method. If not, the mailer will # remain uninitialized (useful when you only need to invoke the "receive" # method, for instance). def initialize(method_name=nil, *parameters) #:nodoc: + @_response_body = nil + super() create!(method_name, *parameters) if method_name end # Initialize the mailer via the given +method_name+. The body will be # rendered and a new TMail::Mail object created. def create!(method_name, *parameters) #:nodoc: - ActiveSupport::Orchestra.instrument(:create_mail, :name => method_name) do - initialize_defaults(method_name) - __send__(method_name, *parameters) - - # If an explicit, textual body has not been set, we check assumptions. - unless String === @body - # First, we look to see if there are any likely templates that match, - # which include the content-type in their file name (i.e., - # "the_template_file.text.html.erb", etc.). Only do this if parts - # have not already been specified manually. - # if @parts.empty? - template_root.find_all(@template, {}, template_path).each do |template| - @parts << Part.new( - :content_type => template.mime_type ? template.mime_type.to_s : "text/plain", - :disposition => "inline", - :charset => charset, - :body => render_template(template, @body) - ) - end - - if @parts.size > 1 - @content_type = "multipart/alternative" if @content_type !~ /^multipart/ - @parts = sort_parts(@parts, @implicit_parts_order) - end - # end - - # Then, if there were such templates, we check to see if we ought to - # also render a "normal" template (without the content type). If a - # normal template exists (or if there were no implicit parts) we render - # it. - # ==== - # TODO: Revisit this - # template_exists = @parts.empty? - # template_exists ||= template_root.find("#{mailer_name}/#{@template}") - # @body = render_message(@template, @body) if template_exists - - # Finally, if there are other message parts and a textual body exists, - # we shift it onto the front of the parts and set the body to nil (so - # that create_mail doesn't try to render it in addition to the parts). - # ==== - # TODO: Revisit this - # if !@parts.empty? && String === @body - # @parts.unshift Part.new(:charset => charset, :body => @body) - # @body = nil - # end - end + initialize_defaults(method_name) + __send__(method_name, *parameters) - # If this is a multipart e-mail add the mime_version if it is not - # already set. - @mime_version ||= "1.0" if !@parts.empty? + # Create e-mail parts + create_parts - # build the mail object itself - @mail = create_mail - end + # Set the subject if not set yet + @subject ||= I18n.t(:subject, :scope => [:actionmailer, mailer_name, method_name], + :default => method_name.humanize) - @mail + # build the mail object itself + @mail = create_mail end # Delivers a TMail::Mail object. By default, it delivers the cached mail @@ -545,14 +480,14 @@ module ActionMailer #:nodoc: def deliver!(mail = @mail) raise "no mail object available for delivery!" unless mail - unless logger.nil? + if logger logger.info "Sent mail to #{Array(recipients).join(', ')}" logger.debug "\n#{mail.encoded}" end - ActiveSupport::Orchestra.instrument(:deliver_mail, :mail => @mail) do + ActiveSupport::Notifications.instrument(:deliver_mail, :mail => @mail) do begin - __send__("perform_delivery_#{delivery_method}", mail) if perform_deliveries + self.delivery_method.perform_delivery(mail) if perform_deliveries rescue Exception => e # Net::SMTP errors or sendmail pipe errors raise e if raise_delivery_errors end @@ -562,93 +497,61 @@ module ActionMailer #:nodoc: end private + # Set up the default values for the various instance variables of this # mailer. Subclasses may override this method to provide different # defaults. def initialize_defaults(method_name) - @charset ||= @@default_charset.dup - @content_type ||= @@default_content_type.dup + @charset ||= @@default_charset.dup + @content_type ||= @@default_content_type.dup @implicit_parts_order ||= @@default_implicit_parts_order.dup - @template ||= method_name - @default_template_name = @action_name = @template - @mailer_name ||= self.class.name.underscore - @parts ||= [] + @mime_version ||= @@default_mime_version.dup if @@default_mime_version + + @mailer_name ||= self.class.mailer_name + @template ||= method_name + @action_name = @template + + @parts ||= [] @headers ||= {} - @body ||= {} - @mime_version = @@default_mime_version.dup if @@default_mime_version @sent_on ||= Time.now - end - def render_template(template, body) - if template.respond_to?(:mime_type) - @current_template_content_type = template.mime_type && template.mime_type.to_sym.to_s - end - - @template = initialize_template_class(body) - layout = _pick_layout(layout, true) unless - ActionController::Base.exempt_from_layout.include?(template.handler) - @template._render_template(template, layout, {}) - ensure - @current_template_content_type = nil + super # Run deprecation hooks end - def render_message(method_name, body) - render :file => method_name, :body => body - ensure - @current_template_content_type = nil - end + def create_parts + super # Run deprecation hooks - def render(opts) - layout, file = opts.delete(:layout), opts[:file] - - begin - @template = initialize_template_class(opts.delete(:body)) - - if file - prefix = mailer_name unless file =~ /\// - template = view_paths.find(file, {:formats => formats}, prefix) + if String === response_body + @parts.unshift Part.new( + :content_type => "text/plain", + :disposition => "inline", + :charset => charset, + :body => response_body + ) + else + self.class.template_root.find_all(@template, {}, mailer_name).each do |template| + @parts << Part.new( + :content_type => template.mime_type ? template.mime_type.to_s : "text/plain", + :disposition => "inline", + :charset => charset, + :body => render_to_body(:_template => template) + ) end - layout = _pick_layout(layout, - !template || ActionController::Base.exempt_from_layout.include?(template.handler)) - - if template - @template._render_template(template, layout, opts) - elsif inline = opts[:inline] - @template._render_inline(inline, layout, opts) + if @parts.size > 1 + @content_type = "multipart/alternative" if @content_type !~ /^multipart/ + @parts = sort_parts(@parts, @implicit_parts_order) end - end - end - def default_template_format - if @current_template_content_type - Mime::Type.lookup(@current_template_content_type).to_sym - else - :html + # If this is a multipart e-mail add the mime_version if it is not + # already set. + @mime_version ||= "1.0" if !@parts.empty? end end - def template_root - self.class.template_root - end - - def template_root=(root) - self.class.template_root = root - end - - def template_path - "#{mailer_name}" - end - - def initialize_template_class(assigns) - template = ActionView::Base.new(self.class.view_paths, assigns, self) - template.formats = [default_template_format] - template - end - def sort_parts(parts, order = []) order = order.collect { |s| s.downcase } - + parts = parts.sort do |a, b| a_ct = a.content_type.downcase b_ct = b.content_type.downcase @@ -697,14 +600,6 @@ module ActionMailer #:nodoc: m.set_content_type(real_content_type, nil, ctype_attrs) m.body = normalize_new_lines(@parts.first.body) else - if String === body - part = TMail::Mail.new - part.body = normalize_new_lines(body) - part.set_content_type(real_content_type, nil, ctype_attrs) - part.set_content_disposition "inline" - m.parts << part - end - @parts.each do |p| part = (TMail::Mail === p ? p : p.to_mail(self)) m.parts << part @@ -719,43 +614,5 @@ module ActionMailer #:nodoc: @mail = m end - def perform_delivery_smtp(mail) - destinations = mail.destinations - mail.ready_to_send - sender = (mail['return-path'] && mail['return-path'].spec) || mail['from'] - - smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port]) - smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto) - smtp.start(smtp_settings[:domain], smtp_settings[:user_name], smtp_settings[:password], - smtp_settings[:authentication]) do |smtp| - smtp.sendmail(mail.encoded, sender, destinations) - end - end - - def perform_delivery_sendmail(mail) - sendmail_args = sendmail_settings[:arguments] - sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path'] - IO.popen("#{sendmail_settings[:location]} #{sendmail_args}","w+") do |sm| - sm.print(mail.encoded.gsub(/\r/, '')) - sm.flush - end - end - - def perform_delivery_test(mail) - deliveries << mail - end - - def perform_delivery_file(mail) - FileUtils.mkdir_p file_settings[:location] - - (mail.to + mail.cc + mail.bcc).uniq.each do |to| - File.open(File.join(file_settings[:location], to), 'a') { |f| f.write(mail) } - end - end - end - - Base.class_eval do - include Helpers - helper MailHelper end end diff --git a/actionmailer/lib/action_mailer/delivery_method.rb b/actionmailer/lib/action_mailer/delivery_method.rb new file mode 100644 index 0000000000..29a51afdc3 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method.rb @@ -0,0 +1,57 @@ +require "active_support/core_ext/class" +module ActionMailer + module DeliveryMethod + + autoload :File, 'action_mailer/delivery_method/file' + autoload :Sendmail, 'action_mailer/delivery_method/sendmail' + autoload :Smtp, 'action_mailer/delivery_method/smtp' + autoload :Test, 'action_mailer/delivery_method/test' + + # Creates a new DeliveryMethod object according to the given options. + # + # If no arguments are passed to this method, then a new + # ActionMailer::DeliveryMethod::Stmp object will be returned. + # + # If you pass a Symbol as the first argument, then a corresponding + # delivery method class under the ActionMailer::DeliveryMethod namespace + # will be created. + # For example: + # + # ActionMailer::DeliveryMethod.lookup_method(:sendmail) + # # => returns a new ActionMailer::DeliveryMethod::Sendmail object + # + # If the first argument is not a Symbol, then it will simply be returned: + # + # ActionMailer::DeliveryMethod.lookup_method(MyOwnDeliveryMethod.new) + # # => returns MyOwnDeliveryMethod.new + def self.lookup_method(delivery_method) + case delivery_method + when Symbol + method_name = delivery_method.to_s.camelize + method_class = ActionMailer::DeliveryMethod.const_get(method_name) + method_class.new + when nil # default + Smtp.new + else + delivery_method + end + end + + # An abstract delivery method class. There are multiple delivery method classes. + # See the classes under the ActionMailer::DeliveryMethod, e.g. + # ActionMailer::DeliveryMethod::Smtp. + # Smtp is the default delivery method for production + # while Test is used in testing. + # + # each delivery method exposes just one method + # + # delivery_method = ActionMailer::DeliveryMethod::Smtp.new + # delivery_method.perform_delivery(mail) # send the mail via smtp + # + class Method + superclass_delegating_accessor :settings + self.settings = {} + end + + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/file.rb b/actionmailer/lib/action_mailer/delivery_method/file.rb new file mode 100644 index 0000000000..587ae37ffa --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/file.rb @@ -0,0 +1,21 @@ +require 'tmpdir' + +module ActionMailer + module DeliveryMethod + + # A delivery method implementation which writes all mails to a file. + class File < Method + self.settings = { + :location => defined?(Rails) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails" + } + + def perform_delivery(mail) + FileUtils.mkdir_p settings[:location] + + (mail.to + mail.cc + mail.bcc).uniq.each do |to| + ::File.open(::File.join(settings[:location], to), 'a') { |f| f.write(mail) } + end + end + end + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/sendmail.rb b/actionmailer/lib/action_mailer/delivery_method/sendmail.rb new file mode 100644 index 0000000000..db55af79f1 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/sendmail.rb @@ -0,0 +1,22 @@ +module ActionMailer + module DeliveryMethod + + # A delivery method implementation which sends via sendmail. + class Sendmail < Method + self.settings = { + :location => '/usr/sbin/sendmail', + :arguments => '-i -t' + } + + def perform_delivery(mail) + sendmail_args = settings[:arguments] + sendmail_args += " -f \"#{mail['return-path']}\"" if mail['return-path'] + IO.popen("#{settings[:location]} #{sendmail_args}","w+") do |sm| + sm.print(mail.encoded.gsub(/\r/, '')) + sm.flush + end + end + end + + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/smtp.rb b/actionmailer/lib/action_mailer/delivery_method/smtp.rb new file mode 100644 index 0000000000..86b0ae8329 --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/smtp.rb @@ -0,0 +1,31 @@ +module ActionMailer + module DeliveryMethod + # A delivery method implementation which sends via smtp. + class Smtp < Method + + self.settings = { + :address => "localhost", + :port => 25, + :domain => 'localhost.localdomain', + :user_name => nil, + :password => nil, + :authentication => nil, + :enable_starttls_auto => true, + } + + def perform_delivery(mail) + destinations = mail.destinations + mail.ready_to_send + sender = (mail['return-path'] && mail['return-path'].spec) || mail['from'] + + smtp = Net::SMTP.new(settings[:address], settings[:port]) + smtp.enable_starttls_auto if settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto) + smtp.start(settings[:domain], settings[:user_name], settings[:password], + settings[:authentication]) do |smtp| + smtp.sendmail(mail.encoded, sender, destinations) + end + end + end + + end +end diff --git a/actionmailer/lib/action_mailer/delivery_method/test.rb b/actionmailer/lib/action_mailer/delivery_method/test.rb new file mode 100644 index 0000000000..6e3239d52a --- /dev/null +++ b/actionmailer/lib/action_mailer/delivery_method/test.rb @@ -0,0 +1,12 @@ +module ActionMailer + module DeliveryMethod + + # A delivery method implementation designed for testing, which just appends each record to the :deliveries array + class Test < Method + def perform_delivery(mail) + ActionMailer::Base.deliveries << mail + end + end + + end +end diff --git a/actionmailer/lib/action_mailer/deprecated_body.rb b/actionmailer/lib/action_mailer/deprecated_body.rb new file mode 100644 index 0000000000..50ff262432 --- /dev/null +++ b/actionmailer/lib/action_mailer/deprecated_body.rb @@ -0,0 +1,44 @@ +module ActionMailer + # TODO Remove this module all together in a next release. Ensure that super + # hooks in ActionMailer::Base are removed as well. + module DeprecatedBody + def self.included(base) + base.class_eval do + # 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 + end + end + + def initialize_defaults(method_name) + @body ||= {} + end + + def create_parts + if String === @body + ActiveSupport::Deprecation.warn('body is deprecated. To set the body with a text ' << + 'call render(:text => "body").', caller[0,10]) + self.response_body = @body + elsif @body.is_a?(Hash) && !@body.empty? + ActiveSupport::Deprecation.warn('body is deprecated. To set assigns simply ' << + 'use instance variables', caller[0,10]) + @body.each { |k, v| instance_variable_set(:"@#{k}", v) } + end + end + + def render(*args) + options = args.last.is_a?(Hash) ? args.last : {} + if options[:body] + ActiveSupport::Deprecation.warn(':body is deprecated. To set assigns simply ' << + 'use instance variables', caller[0,1]) + + options.delete(:body).each do |k, v| + instance_variable_set(:"@#{k}", v) + end + end + + super + end + end +end diff --git a/actionmailer/lib/action_mailer/helpers.rb b/actionmailer/lib/action_mailer/helpers.rb deleted file mode 100644 index ecd8f0f5b6..0000000000 --- a/actionmailer/lib/action_mailer/helpers.rb +++ /dev/null @@ -1,114 +0,0 @@ -require 'active_support/dependencies' - -module ActionMailer - module Helpers #:nodoc: - def self.included(base) #:nodoc: - # Initialize the base module to aggregate its helpers. - base.class_inheritable_accessor :master_helper_module - base.master_helper_module = Module.new - - # Extend base with class methods to declare helpers. - base.extend(ClassMethods) - - base.class_eval do - # Wrap inherited to create a new master helper module for subclasses. - class << self - alias_method_chain :inherited, :helper - end - - # Wrap initialize_template_class to extend new template class - # instances with the master helper module. - alias_method_chain :initialize_template_class, :helper - end - end - - module ClassMethods - # Makes all the (instance) methods in the helper module available to templates rendered through this controller. - # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules - # available to the templates. - def add_template_helper(helper_module) #:nodoc: - master_helper_module.module_eval "include #{helper_module}" - end - - # Declare a helper: - # helper :foo - # requires 'foo_helper' and includes FooHelper in the template class. - # helper FooHelper - # includes FooHelper in the template class. - # helper { def foo() "#{bar} is the very best" end } - # evaluates the block in the template class, adding method +foo+. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # does all three. - def helper(*args, &block) - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - when String, Symbol - file_name = arg.to_s.underscore + '_helper' - class_name = file_name.camelize - - require_dependency(file_name, "Missing helper file helpers/%s.rb") - # begin - # require_dependency(file_name) - # rescue LoadError => load_error - # requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] - # msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}" - # raise LoadError.new(msg).copy_blame!(load_error) - # end - - add_template_helper(class_name.constantize) - else - raise ArgumentError, 'helper expects String, Symbol, or Module argument' - end - end - - # Evaluate block in template class if given. - master_helper_module.module_eval(&block) if block_given? - end - - # Declare a controller method as a helper. For example, - # helper_method :link_to - # def link_to(name, options) ... end - # makes the link_to controller method available in the view. - def helper_method(*methods) - methods.flatten.each do |method| - master_helper_module.module_eval <<-end_eval - def #{method}(*args, &block) - controller.__send__(%(#{method}), *args, &block) - end - end_eval - end - end - - # Declare a controller attribute as a helper. For example, - # helper_attr :name - # attr_accessor :name - # makes the name and name= controller methods available in the view. - # The is a convenience wrapper for helper_method. - def helper_attr(*attrs) - attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } - end - - private - def inherited_with_helper(child) - inherited_without_helper(child) - begin - child.master_helper_module = Module.new - child.master_helper_module.__send__(:include, master_helper_module) - child.helper child.name.to_s.underscore - rescue MissingSourceFile => e - raise unless e.is_missing?("#{child.name.to_s.underscore}_helper") - end - end - end - - private - # Extend the template class instance with our controller's helper module. - def initialize_template_class_with_helper(assigns) - initialize_template_class_without_helper(assigns).tap do |template| - template.extend self.class.master_helper_module - end - end - end -end diff --git a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb index ab1a828471..0ddc525213 100644 --- a/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb +++ b/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/parser.rb @@ -43,6 +43,7 @@ module Racc class Parser + old_verbose, $VERBOSE = $VERBOSE, nil Racc_Runtime_Version = '1.4.5' Racc_Runtime_Revision = '$Revision: 1.7 $'.split[1] @@ -71,6 +72,7 @@ module Racc Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R Racc_Runtime_Type = 'ruby' end + $VERBOSE = old_verbose def Parser.racc_runtime_type Racc_Runtime_Type diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 3d4d0fb995..e84b3b0d23 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -1,9 +1,17 @@ +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") + $:.unshift("#{root}/actionpack/lib") +end + +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'rubygems' require 'test/unit' -$:.unshift "#{File.dirname(__FILE__)}/../lib" -$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib" -$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib" require 'action_mailer' require 'action_mailer/test_case' @@ -14,6 +22,8 @@ ActiveSupport::Deprecation.debug = true ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect } ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect } +ActionView::Base::DEFAULT_CONFIG = { :assets_dir => '/nowhere' } + $:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers" FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') diff --git a/actionmailer/test/delivery_method_test.rb b/actionmailer/test/delivery_method_test.rb index 1b8c3ba523..8f8c6b0275 100644 --- a/actionmailer/test/delivery_method_test.rb +++ b/actionmailer/test/delivery_method_test.rb @@ -11,6 +11,21 @@ class FileDeliveryMethodMailer < ActionMailer::Base self.delivery_method = :file end +class CustomDeliveryMethod + attr_accessor :custom_deliveries + def initialize() + @customer_deliveries = [] + end + + def self.perform_delivery(mail) + self.custom_deliveries << mail + end +end + +class CustomerDeliveryMailer < ActionMailer::Base + self.delivery_method = CustomDeliveryMethod.new +end + class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase def setup set_delivery_method :smtp @@ -21,7 +36,7 @@ class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_default_smtp - assert_equal :smtp, ActionMailer::Base.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Smtp, ActionMailer::Base.delivery_method end end @@ -35,7 +50,7 @@ class DefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_default_smtp - assert_equal :smtp, DefaultDeliveryMethodMailer.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Smtp, DefaultDeliveryMethodMailer.delivery_method end end @@ -49,7 +64,7 @@ class NonDefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_set_delivery_method - assert_equal :sendmail, NonDefaultDeliveryMethodMailer.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Sendmail, NonDefaultDeliveryMethodMailer.delivery_method end end @@ -63,7 +78,7 @@ class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end def test_should_be_the_set_delivery_method - assert_equal :file, FileDeliveryMethodMailer.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::File, FileDeliveryMethodMailer.delivery_method end def test_should_default_location_to_the_tmpdir @@ -71,3 +86,16 @@ class FileDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase end end +class CustomDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase + def setup + set_delivery_method :smtp + end + + def teardown + restore_delivery_method + end + + def test_should_be_the_set_delivery_method + assert_instance_of CustomDeliveryMethod, CustomerDeliveryMailer.delivery_method + end +end diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index e94aeff074..f8b002e0a7 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -20,28 +20,29 @@ class HelperMailer < ActionMailer::Base recipients recipient subject "using helpers" from "tester@example.com" - self.body = { :text => "emphasize me!" } + + @text = "emphasize me!" end def use_mail_helper(recipient) recipients recipient subject "using mailing helpers" from "tester@example.com" - self.body = { :text => - "But soft! What light through yonder window breaks? It is the east, " + - "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + - "which is sick and pale with grief that thou, her maid, art far more " + - "fair than she. Be not her maid, for she is envious! Her vestal " + - "livery is but sick and green, and none but fools do wear it. Cast " + - "it off!" - } + + @text = "But soft! What light through yonder window breaks? It is the east, " + + "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + + "which is sick and pale with grief that thou, her maid, art far more " + + "fair than she. Be not her maid, for she is envious! Her vestal " + + "livery is but sick and green, and none but fools do wear it. Cast " + + "it off!" end def use_helper_method(recipient) recipients recipient subject "using helpers" from "tester@example.com" - self.body = { :text => "emphasize me!" } + + @text = "emphasize me!" end private diff --git a/actionmailer/test/mail_layout_test.rb b/actionmailer/test/mail_layout_test.rb index 50901f52ec..f37c26ff69 100644 --- a/actionmailer/test/mail_layout_test.rb +++ b/actionmailer/test/mail_layout_test.rb @@ -11,14 +11,18 @@ class AutoLayoutMailer < ActionMailer::Base recipients recipient subject "You have a mail" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" }) + + @world = "Earth" + render(:inline => "Hello, <%= @world %>", :layout => 'spam') end def nolayout(recipient) recipients recipient subject "You have a mail" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" }) + + @world = "Earth" + render(:inline => "Hello, <%= @world %>", :layout => false) end def multipart(recipient, type = nil) diff --git a/actionmailer/test/mail_render_test.rb b/actionmailer/test/mail_render_test.rb index 45811612eb..514f7ed798 100644 --- a/actionmailer/test/mail_render_test.rb +++ b/actionmailer/test/mail_render_test.rb @@ -5,14 +5,27 @@ class RenderMailer < ActionMailer::Base recipients recipient subject "using helpers" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" }) + + @world = "Earth" + render :inline => "Hello, <%= @world %>" end def file_template(recipient) recipients recipient subject "using helpers" from "tester@example.com" - body render(:file => "signed_up", :body => { :recipient => recipient }) + + @recipient = recipient + render :file => "templates/signed_up" + end + + def implicit_body(recipient) + recipients recipient + subject "using helpers" + from "tester@example.com" + + @recipient = recipient + render :template => "templates/signed_up" end def rxml_template(recipient) @@ -31,7 +44,9 @@ class RenderMailer < ActionMailer::Base recipients recipient subject "Including another template in the one being rendered" from "tester@example.com" - body render(:inline => "Hello, <%= render \"subtemplate\" %>", :body => { :world => "Earth" }) + + @world = "Earth" + render :inline => "Hello, <%= render \"subtemplate\" %>" end def initialize_defaults(method_name) @@ -69,6 +84,11 @@ class RenderHelperTest < Test::Unit::TestCase restore_delivery_method end + def test_implicit_body + mail = RenderMailer.create_implicit_body(@recipient) + assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip + end + def test_inline_template mail = RenderMailer.create_inline_template(@recipient) assert_equal "Hello, Earth", mail.body.strip diff --git a/actionmailer/test/mail_service_test.rb b/actionmailer/test/mail_service_test.rb index 5584afa8be..c98f0a7601 100644 --- a/actionmailer/test/mail_service_test.rb +++ b/actionmailer/test/mail_service_test.rb @@ -15,18 +15,20 @@ end class TestMailer < ActionMailer::Base def signed_up(recipient) - @recipients = recipient - @subject = "[Signed up] Welcome #{recipient}" - @from = "system@loudthinking.com" - @body["recipient"] = recipient + recipients recipient + subject "[Signed up] Welcome #{recipient}" + from "system@loudthinking.com" + + @recipient = recipient end def cancelled_account(recipient) - self.recipients = recipient - self.subject = "[Cancelled] Goodbye #{recipient}" - self.from = "system@loudthinking.com" - self.sent_on = Time.local(2004, 12, 12) - self.body = "Goodbye, Mr. #{recipient}" + recipients recipient + subject "[Cancelled] Goodbye #{recipient}" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + + render :text => "Goodbye, Mr. #{recipient}" end def cc_bcc(recipient) @@ -36,7 +38,8 @@ class TestMailer < ActionMailer::Base sent_on Time.local(2004, 12, 12) cc "nobody@loudthinking.com" bcc "root@loudthinking.com" - body "Nothing to see here." + + render :text => "Nothing to see here." end def different_reply_to(recipient) @@ -45,50 +48,55 @@ class TestMailer < ActionMailer::Base from "system@loudthinking.com" sent_on Time.local(2008, 5, 23) reply_to "atraver@gmail.com" - body "Nothing to see here." + + render :text => "Nothing to see here." end def iso_charset(recipient) - @recipients = recipient - @subject = "testing isø charsets" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." - @charset = "iso-8859-1" + recipients recipient + subject "testing isø charsets" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + cc "nobody@loudthinking.com" + bcc "root@loudthinking.com" + charset "iso-8859-1" + + render :text => "Nothing to see here." end def unencoded_subject(recipient) - @recipients = recipient - @subject = "testing unencoded subject" - @from = "system@loudthinking.com" - @sent_on = Time.local 2004, 12, 12 - @cc = "nobody@loudthinking.com" - @bcc = "root@loudthinking.com" - @body = "Nothing to see here." + recipients recipient + subject "testing unencoded subject" + from "system@loudthinking.com" + sent_on Time.local(2004, 12, 12) + cc "nobody@loudthinking.com" + bcc "root@loudthinking.com" + + render :text => "Nothing to see here." end def extended_headers(recipient) - @recipients = recipient - @subject = "testing extended headers" - @from = "Grytøyr <stian1@example.net>" - @sent_on = Time.local 2004, 12, 12 - @cc = "Grytøyr <stian2@example.net>" - @bcc = "Grytøyr <stian3@example.net>" - @body = "Nothing to see here." - @charset = "iso-8859-1" + recipients recipient + subject "testing extended headers" + from "Grytøyr <stian1@example.net>" + sent_on Time.local(2004, 12, 12) + cc "Grytøyr <stian2@example.net>" + bcc "Grytøyr <stian3@example.net>" + charset "iso-8859-1" + + render :text => "Nothing to see here." end def utf8_body(recipient) - @recipients = recipient - @subject = "testing utf-8 body" - @from = "Foo áëô îü <extended@example.net>" - @sent_on = Time.local 2004, 12, 12 - @cc = "Foo áëô îü <extended@example.net>" - @bcc = "Foo áëô îü <extended@example.net>" - @body = "åœö blah" - @charset = "utf-8" + recipients recipient + subject "testing utf-8 body" + from "Foo áëô îü <extended@example.net>" + sent_on Time.local(2004, 12, 12) + cc "Foo áëô îü <extended@example.net>" + bcc "Foo áëô îü <extended@example.net>" + charset "utf-8" + + render :text => "åœö blah" end def multipart_with_mime_version(recipient) @@ -128,7 +136,6 @@ class TestMailer < ActionMailer::Base subject "multipart example" from "test@example.com" sent_on Time.local(2004, 12, 12) - body "plain text default" content_type ct if ct part "text/html" do |p| @@ -138,15 +145,18 @@ class TestMailer < ActionMailer::Base attachment :content_type => "image/jpeg", :filename => "foo.jpg", :body => "123456789" + + render :text => "plain text default" end def implicitly_multipart_example(recipient, cs = nil, order = nil) - @recipients = recipient - @subject = "multipart example" - @from = "test@example.com" - @sent_on = Time.local 2004, 12, 12 - @body = { "recipient" => recipient } - @charset = cs if cs + recipients recipient + subject "multipart example" + from "test@example.com" + sent_on Time.local(2004, 12, 12) + + @charset = cs if cs + @recipient = recipient @implicit_parts_order = order if order end @@ -155,20 +165,22 @@ class TestMailer < ActionMailer::Base subject "Foo áëô îü" from "some.one@somewhere.test" template "implicitly_multipart_example" - body ({ "recipient" => "no.one@nowhere.test" }) + + @recipient = "no.one@nowhere.test" end def html_mail(recipient) recipients recipient subject "html mail" from "test@example.com" - body "<em>Emphasize</em> <strong>this</strong>" content_type "text/html" + + render :text => "<em>Emphasize</em> <strong>this</strong>" end def html_mail_with_underscores(recipient) subject "html mail with underscores" - body %{<a href="http://google.com" target="_blank">_Google</a>} + render :text => %{<a href="http://google.com" target="_blank">_Google</a>} end def custom_template(recipient) @@ -178,7 +190,7 @@ class TestMailer < ActionMailer::Base sent_on Time.local(2004, 12, 12) template "signed_up" - body["recipient"] = recipient + @recipient = recipient end def custom_templating_extension(recipient) @@ -187,15 +199,16 @@ class TestMailer < ActionMailer::Base from "system@loudthinking.com" sent_on Time.local(2004, 12, 12) - body["recipient"] = recipient + @recipient = recipient end def various_newlines(recipient) recipients recipient subject "various newlines" from "test@example.com" - body "line #1\nline #2\rline #3\r\nline #4\r\r" + - "line #5\n\nline#6\r\n\r\nline #7" + + render :text => "line #1\nline #2\rline #3\r\nline #4\r\r" + + "line #5\n\nline#6\r\n\r\nline #7" end def various_newlines_multipart(recipient) @@ -203,6 +216,7 @@ class TestMailer < ActionMailer::Base subject "various newlines multipart" from "test@example.com" content_type "multipart/alternative" + part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r" part :content_type => "text/html", :body => "<p>line #1</p>\n<p>line #2</p>\r<p>line #3</p>\r\n<p>line #4</p>\r\r" end @@ -212,10 +226,12 @@ class TestMailer < ActionMailer::Base subject "nested multipart" from "test@example.com" content_type "multipart/mixed" + part :content_type => "multipart/alternative", :content_disposition => "inline", :headers => { "foo" => "bar" } do |p| p.part :content_type => "text/plain", :body => "test text\nline #2" p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2" end + attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz" end @@ -224,6 +240,7 @@ class TestMailer < ActionMailer::Base subject "nested multipart with body" from "test@example.com" content_type "multipart/mixed" + part :content_type => "multipart/alternative", :content_disposition => "inline", :body => "Nothing to see here." do |p| p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>" end @@ -253,7 +270,7 @@ class TestMailer < ActionMailer::Base from "One: Two <test@example.com>" cc "Three: Four <test@example.com>" bcc "Five: Six <test@example.com>" - body "testing" + render :text => "testing" end def custom_content_type_attributes @@ -261,15 +278,15 @@ class TestMailer < ActionMailer::Base subject "custom content types" from "some.one@somewhere.test" content_type "text/plain; format=flowed" - body "testing" + render :text => "testing" end def return_path recipients "no.one@nowhere.test" subject "return path test" from "some.one@somewhere.test" - body "testing" headers "return-path" => "another@somewhere.test" + render :text => "testing" end def body_ivar(recipient) @@ -279,7 +296,13 @@ class TestMailer < ActionMailer::Base body :body => "foo", :bar => "baz" end - class <<self + def subject_with_i18n(recipient) + recipients recipient + from "system@loudthinking.com" + render :text => "testing" + end + + class << self attr_accessor :received_body end @@ -375,6 +398,15 @@ class ActionMailerTest < Test::Unit::TestCase assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded end + def test_subject_with_i18n + assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) } + assert_equal "Subject with i18n", ActionMailer::Base.deliveries.first.subject + + I18n.backend.store_translations('en', :actionmailer => {:test_mailer => {:subject_with_i18n => {:subject => "New Subject!"}}}) + assert_nothing_raised { TestMailer.deliver_subject_with_i18n(@recipient) } + assert_equal "New Subject!", ActionMailer::Base.deliveries.last.subject + end + def test_custom_template expected = new_mail expected.to = @recipient @@ -554,7 +586,7 @@ class ActionMailerTest < Test::Unit::TestCase def test_doesnt_raise_errors_when_raise_delivery_errors_is_false ActionMailer::Base.raise_delivery_errors = false - TestMailer.any_instance.expects(:perform_delivery_test).raises(Exception) + TestMailer.delivery_method.expects(:perform_delivery).raises(Exception) assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) } end @@ -1024,7 +1056,7 @@ end class MethodNamingTest < Test::Unit::TestCase class TestMailer < ActionMailer::Base def send - body 'foo' + render :text => 'foo' end end diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 65b07a71b8..34c5243936 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -4,13 +4,15 @@ class TestHelperMailer < ActionMailer::Base def test recipients "test@example.com" from "tester@example.com" - body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" }) + + @world = "Earth" + render(:inline => "Hello, <%= @world %>") end end class TestHelperMailerTest < ActionMailer::TestCase def test_setup_sets_right_action_mailer_options - assert_equal :test, ActionMailer::Base.delivery_method + assert_instance_of ActionMailer::DeliveryMethod::Test, ActionMailer::Base.delivery_method assert ActionMailer::Base.perform_deliveries assert_equal [], ActionMailer::Base.deliveries end diff --git a/actionmailer/test/url_test.rb b/actionmailer/test/url_test.rb index 71286bd1cf..2224f6321c 100644 --- a/actionmailer/test/url_test.rb +++ b/actionmailer/test/url_test.rb @@ -1,9 +1,9 @@ require 'abstract_unit' class TestMailer < ActionMailer::Base - + default_url_options[:host] = 'www.basecamphq.com' - + def signed_up_with_url(recipient) @recipients = recipient @subject = "[Signed up] Welcome #{recipient}" @@ -52,25 +52,27 @@ class ActionMailerUrlTest < Test::Unit::TestCase end def test_signed_up_with_url - ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' - map.welcome 'welcome', :controller=>"foo", :action=>"bar" - end + ActionController::Routing.use_controllers! ['welcome'] do + ActionController::Routing::Routes.draw do |map| + map.connect ':controller/:action/:id' + map.welcome 'welcome', :controller=>"foo", :action=>"bar" + end - expected = new_mail - expected.to = @recipient - expected.subject = "[Signed up] Welcome #{@recipient}" - expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />" - expected.from = "system@loudthinking.com" - expected.date = Time.local(2004, 12, 12) - - created = nil - assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) } - assert_not_nil created - assert_equal expected.encoded, created.encoded - - assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) } - assert_not_nil ActionMailer::Base.deliveries.first - assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded + expected = new_mail + expected.to = @recipient + expected.subject = "[Signed up] Welcome #{@recipient}" + expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />" + expected.from = "system@loudthinking.com" + expected.date = Time.local(2004, 12, 12) + + created = nil + assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) } + assert_not_nil created + assert_equal expected.encoded, created.encoded + + assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) } + assert_not_nil ActionMailer::Base.deliveries.first + assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded + end end end diff --git a/actionpack/Gemfile b/actionpack/Gemfile deleted file mode 100644 index 60d24104d2..0000000000 --- a/actionpack/Gemfile +++ /dev/null @@ -1,15 +0,0 @@ -rails_root = Pathname.new(File.dirname(__FILE__)).join("..") - -gem "rack", "~> 1.0.0" -gem "rack-test", "~> 0.5.0" -gem "activesupport", "3.0.pre", :vendored_at => rails_root.join("activesupport") -gem "activemodel", "3.0.pre", :vendored_at => rails_root.join("activemodel") -gem "erubis", "~> 2.6.0" - -only :test do - gem "mocha" - gem "sqlite3-ruby" - gem "RedCloth" -end - -disable_system_gems diff --git a/actionpack/Rakefile b/actionpack/Rakefile index e186037aeb..99bdcc95fa 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -19,16 +19,6 @@ RUBY_FORGE_USER = "webster132" desc "Default Task" task :default => :test -task :bundle do - puts "Checking if the bundled testing requirements are up to date..." - result = system "gem bundle" - unless result - puts "The gem bundler is not installed. Installing." - system "gem install bundler" - system "gem bundle" - end -end - # Run the unit tests desc "Run all unit tests" @@ -66,7 +56,7 @@ Rake::RDocTask.new { |rdoc| rdoc.options << '--line-numbers' << '--inline-source' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - if ENV['DOC_FILES'] + if ENV['DOC_FILES'] rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/)) else rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG') @@ -80,8 +70,6 @@ spec = eval(File.read('actionpack.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do @@ -98,10 +86,10 @@ task :lines do codelines += 1 end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - + total_lines += lines total_codelines += codelines - + lines, codelines = 0, 0 end @@ -122,14 +110,14 @@ task :update_js => [ :update_scriptaculous ] # Publishing ------------------------------------------------------ desc "Publish the API documentation" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload end diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 1930416358..2c534642ab 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -16,8 +16,10 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', '= 3.0.pre') s.add_dependency('activemodel', '= 3.0.pre') - s.add_dependency('rack', '~> 1.0.0') - s.add_dependency('rack-test', '~> 0.5.0') + s.add_dependency('rack', '~> 1.0.1') + s.add_dependency('rack-test', '~> 0.5.0') + s.add_dependency('rack-mount', '~> 0.0.1') + s.add_dependency('erubis', '~> 2.6.5') s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/abstract_controller.rb b/actionpack/lib/abstract_controller.rb index 76c5845f5b..1a6c4278c9 100644 --- a/actionpack/lib/abstract_controller.rb +++ b/actionpack/lib/abstract_controller.rb @@ -6,6 +6,7 @@ module AbstractController autoload :Callbacks, "abstract_controller/callbacks" autoload :Helpers, "abstract_controller/helpers" autoload :Layouts, "abstract_controller/layouts" + autoload :LocalizedCache, "abstract_controller/localized_cache" autoload :Logger, "abstract_controller/logger" autoload :RenderingController, "abstract_controller/rendering_controller" # === Exceptions diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 379eaf6d8e..ee496dadc5 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,13 +1,11 @@ -require "active_support/new_callbacks" - module AbstractController module Callbacks extend ActiveSupport::Concern - # Uses ActiveSupport::NewCallbacks as the base functionality. For + # Uses ActiveSupport::Callbacks as the base functionality. For # more details on the whole callback system, read the documentation - # for ActiveSupport::NewCallbacks. - include ActiveSupport::NewCallbacks + # for ActiveSupport::Callbacks. + include ActiveSupport::Callbacks included do define_callbacks :process_action, :terminator => "response_body" @@ -16,7 +14,7 @@ module AbstractController # Override AbstractController::Base's process_action to run the # process_action callbacks around the normal behavior. def process_action(method_name) - _run_process_action_callbacks(method_name) do + run_callbacks(:process_action, method_name) do super end end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index f3072fad74..d3b492ad09 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,3 +1,5 @@ +require 'active_support/dependencies' + module AbstractController module Helpers extend ActiveSupport::Concern @@ -57,23 +59,48 @@ module AbstractController end end - # Make a number of helper modules part of this class' default - # helpers. + # The +helper+ class method can take a series of helper module names, a block, or both. # # ==== Parameters - # *args<Array[Module]>:: Modules to be included - # block<Block>:: Evalulate the block in the context - # of the helper module. Any methods defined in the block - # will be helpers. + # *args<Array[Module, Symbol, String, :all]> + # block<Block>:: A block defining helper methods + # + # ==== Examples + # When the argument is a module it will be included directly in the template class. + # helper FooHelper # => includes FooHelper + # + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file + # and include the module in the template class. The second form illustrates how to include custom helpers + # when working with namespaced controllers, or other cases where the file containing the helper definition is not + # in one of Rails' standard load paths: + # helper :foo # => requires 'foo_helper' and includes FooHelper + # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper + # + # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available + # to the template. + # + # # One line + # helper { def hello() "Hello, world!" end } + # + # # Multi-line + # helper do + # def foo(bar) + # "#{bar} is the very best" + # end + # end + # + # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of + # +symbols+, +strings+, +modules+ and blocks. + # + # helper(:three, BlindHelper) { def mice() 'mice' end } + # def helper(*args, &block) self._helper_serial = AbstractController::Helpers.next_serial + 1 - args.flatten.each do |arg| - case arg - when Module - add_template_helper(arg) - end + _modules_for_helpers(args).each do |mod| + add_template_helper(mod) end + _helpers.module_eval(&block) if block_given? end @@ -87,6 +114,38 @@ module AbstractController def add_template_helper(mod) _helpers.module_eval { include mod } end + + # Returns a list of modules, normalized from the acceptable kinds of + # helpers with the following behavior: + # + # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", + # and "foo_bar_helper.rb" is loaded using require_dependency. + # + # Module:: No further processing + # + # After loading the appropriate files, the corresponding modules + # are returned. + # + # ==== Parameters + # args<Array[String, Symbol, Module]>:: A list of helpers + # + # ==== Returns + # Array[Module]:: A normalized list of modules for the list of + # helpers provided. + def _modules_for_helpers(args) + args.flatten.map! do |arg| + case arg + when String, Symbol + file_name = "#{arg.to_s.underscore}_helper" + require_dependency(file_name, "Missing helper file helpers/%s.rb") + file_name.camelize.constantize + when Module + arg + else + raise ArgumentError, "helper must be a String, Symbol, or Module" + end + end + end end end end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 796ef40584..c71cef42b2 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -89,7 +89,7 @@ module AbstractController # ==== Returns # String:: A template name def _implied_layout_name - name.underscore + name && name.underscore end # Takes the specified layout and creates a _layout method to be called @@ -100,7 +100,7 @@ module AbstractController # name, return that string. Otherwise, use the superclass' # layout (which might also be implied) def _write_layout_method - case @_layout + case defined?(@_layout) ? @_layout : nil when String self.class_eval %{def _layout(details) #{@_layout.inspect} end} when Symbol @@ -119,17 +119,19 @@ module AbstractController when true raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" when nil - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout(details) - self.class.cache_layout(details) do - if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts") - "#{_implied_layout_name}" - else - super + if name + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def _layout(details) + self.class.cache_layout(details) do + if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts") + "#{_implied_layout_name}" + else + super + end end end - end - RUBY + RUBY + end end self.class_eval { private :_layout } end @@ -157,6 +159,7 @@ module AbstractController end private + # This will be overwritten by _write_layout_method def _layout(details) end @@ -171,6 +174,34 @@ module AbstractController name && _find_layout(name, details) end + # Determine the layout for a given name and details, taking into account + # the name type. + # + # ==== Parameters + # name<String|TrueClass|FalseClass|Symbol>:: The name of the template + # details<Hash{Symbol => Object}>:: A list of details to restrict + # the lookup to. By default, layout lookup is limited to the + # formats specified for the current request. + def _layout_for_option(name, details) + case name + when String then _layout_for_name(name, details) + when true then _default_layout(details, true) + when :default then _default_layout(details, false) + when false, nil then nil + else + raise ArgumentError, + "String, true, or false, expected for `layout'; you passed #{name.inspect}" + end + end + + def _determine_template(options) + super + + return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) + layout = options.key?(:layout) ? options[:layout] : :default + options[:_layout] = _layout_for_option(layout, options[:_template].details) + end + # Take in the name and details and find a Template. # # ==== Parameters diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb new file mode 100644 index 0000000000..ee7b43cb9f --- /dev/null +++ b/actionpack/lib/abstract_controller/localized_cache.rb @@ -0,0 +1,49 @@ +module AbstractController + class HashKey + @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } + + def self.get(klass, formats, locale) + @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) + end + + attr_accessor :hash + def initialize(klass, formats, locale) + @formats, @locale = formats, locale + @hash = [formats, locale].hash + end + + alias_method :eql?, :equal? + + def inspect + "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>" + end + end + + module LocalizedCache + extend ActiveSupport::Concern + + module ClassMethods + def clear_template_caches! + ActionView::Partials::PartialRenderer::TEMPLATES.clear + template_cache.clear + super + end + + def template_cache + @template_cache ||= Hash.new {|h,k| h[k] = {} } + end + end + + def render(options) + Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) + super + end + + private + + def with_template_cache(name) + self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super + end + + end +end diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index f4d017b8e5..27ba5be45f 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/logger' +require 'active_support/benchmarkable' module AbstractController module Logger @@ -6,22 +7,7 @@ module AbstractController included do cattr_accessor :logger - end - - module ClassMethods #:nodoc: - # Logs a message appending the value measured. - def log_with_time(message, time, log_level=::Logger::DEBUG) - return unless logger && logger.level >= log_level - logger.add(log_level, "#{message} (%.1fms)" % time) - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, ::Logger::ERROR if logge - yield - ensure - logger.level = old_logger_level if logger - end + extend ActiveSupport::Benchmarkable end # A class that allows you to defer expensive processing @@ -47,7 +33,7 @@ module AbstractController # Override process_action in the AbstractController::Base # to log details about the method. def process_action(action) - event = ActiveSupport::Orchestra.instrument(:process_action, + result = ActiveSupport::Notifications.instrument(:process_action, :controller => self, :action => action) do super end @@ -56,13 +42,13 @@ module AbstractController log = DelayedLog.new do "\n\nProcessing #{self.class.name}\##{action_name} " \ "to #{request.formats} (for #{request_origin}) " \ - "(%.1fms) [#{request.method.to_s.upcase}]" % event.duration + "[#{request.method.to_s.upcase}]" end logger.info(log) end - event.result + result end private diff --git a/actionpack/lib/abstract_controller/rendering_controller.rb b/actionpack/lib/abstract_controller/rendering_controller.rb index bbf941aa32..7054b9cf26 100644 --- a/actionpack/lib/abstract_controller/rendering_controller.rb +++ b/actionpack/lib/abstract_controller/rendering_controller.rb @@ -8,12 +8,16 @@ module AbstractController included do attr_internal :formats - extlib_inheritable_accessor :_view_paths - self._view_paths ||= ActionView::PathSet.new end + # Initialize controller with nil formats. + def initialize(*) #:nodoc: + @_formats = nil + super + end + # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: @@ -99,6 +103,7 @@ module AbstractController end private + # Take in a set of options and determine the template to render # # ==== Options @@ -109,6 +114,18 @@ module AbstractController # to a directory. # _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial def _determine_template(options) + if options.key?(:text) + options[:_template] = ActionView::TextTemplate.new(options[:text], format_for_text) + elsif options.key?(:inline) + handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") + template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) + options[:_template] = template + elsif options.key?(:template) + options[:_template_name] = options[:template] + elsif options.key?(:file) + options[:_template_name] = options[:file] + end + name = (options[:_template_name] || action_name).to_s options[:_template] ||= with_template_cache(name) do @@ -128,6 +145,10 @@ module AbstractController yield end + def format_for_text + Mime[:text] + end + module ClassMethods def clear_template_caches! end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 6702cb47f8..03a40e4fce 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -2,6 +2,8 @@ module ActionController autoload :Base, "action_controller/base" autoload :Benchmarking, "action_controller/metal/benchmarking" autoload :ConditionalGet, "action_controller/metal/conditional_get" + autoload :Configuration, "action_controller/metal/configuration" + autoload :Head, "action_controller/metal/head" autoload :Helpers, "action_controller/metal/helpers" autoload :HideActions, "action_controller/metal/hide_actions" autoload :Layouts, "action_controller/metal/layouts" @@ -18,22 +20,20 @@ module ActionController autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" - # Ported modules - # require 'action_controller/routing' autoload :Caching, 'action_controller/caching' autoload :Dispatcher, 'action_controller/dispatch/dispatcher' autoload :Integration, 'action_controller/deprecated/integration_test' autoload :IntegrationTest, 'action_controller/deprecated/integration_test' autoload :MimeResponds, 'action_controller/metal/mime_responds' autoload :PerformanceTest, 'action_controller/deprecated/performance_test' - autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes' + autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' autoload :RecordIdentifier, 'action_controller/record_identifier' - autoload :Resources, 'action_controller/routing/resources' + autoload :Routing, 'action_controller/deprecated' autoload :SessionManagement, 'action_controller/metal/session_management' autoload :TestCase, 'action_controller/testing/test_case' autoload :TestProcess, 'action_controller/testing/process' - autoload :UrlRewriter, 'action_controller/routing/generation/url_rewriter' - autoload :UrlWriter, 'action_controller/routing/generation/url_rewriter' + autoload :UrlRewriter, 'action_controller/url_rewriter' + autoload :UrlWriter, 'action_controller/url_rewriter' autoload :Verification, 'action_controller/metal/verification' autoload :Flash, 'action_controller/metal/flash' @@ -54,8 +54,6 @@ module ActionController autoload :RenderError, 'action_controller/metal/exceptions' autoload :SessionOverflowError, 'action_controller/metal/exceptions' autoload :UnknownHttpMethod, 'action_controller/metal/exceptions' - - autoload :Routing, 'action_controller/routing' end autoload :HTML, 'action_controller/vendor/html-scanner' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 5338a70104..4c026fe5f7 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -15,6 +15,7 @@ module ActionController include ActionController::ConditionalGet include ActionController::RackConvenience include ActionController::Benchmarking + include ActionController::Configuration # Legacy modules include SessionManagement diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 38cf1da6a8..3caf759032 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -3,26 +3,31 @@ require 'uri' require 'set' module ActionController #:nodoc: - # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls - # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment. + # Caching is a cheap way of speeding up slow applications by keeping the result of + # calculations, renderings, and database calls around for subsequent requests. + # Action Controller affords you three approaches in varying levels of granularity: + # Page, Action, Fragment. # - # You can read more about each approach and the sweeping assistance by clicking the modules below. - # - # Note: To turn off all caching and sweeping, set Base.perform_caching = false. + # You can read more about each approach and the sweeping assistance by clicking the + # modules below. # + # Note: To turn off all caching and sweeping, set + # config.action_controller.perform_caching = false. # # == Caching stores # - # All the caching stores from ActiveSupport::Cache are available to be used as backends for Action Controller caching. This setting only - # affects action and fragment caching as page caching is always written to disk. + # All the caching stores from ActiveSupport::Cache are available to be used as backends + # for Action Controller caching. This setting only affects action and fragment caching + # as page caching is always written to disk. # # Configuration examples (MemoryStore is the default): # - # ActionController::Base.cache_store = :memory_store - # ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" - # ActionController::Base.cache_store = :drb_store, "druby://localhost:9192" - # ActionController::Base.cache_store = :mem_cache_store, "localhost" - # ActionController::Base.cache_store = MyOwnStore.new("parameter") + # config.action_controller.cache_store = :memory_store + # config.action_controller.cache_store = :file_store, "/path/to/cache/directory" + # config.action_controller.cache_store = :drb_store, "druby://localhost:9192" + # config.action_controller.cache_store = :mem_cache_store, "localhost" + # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new("localhost:11211") + # config.action_controller.cache_store = MyOwnStore.new("parameter") module Caching extend ActiveSupport::Concern @@ -46,25 +51,31 @@ module ActionController #:nodoc: @@perform_caching = true cattr_accessor :perform_caching + end - def self.cache_configured? + module ClassMethods + def cache_configured? perform_caching && cache_store end end - protected - # Convenience accessor - def cache(key, options = {}, &block) - if cache_configured? - cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) - else - yield - end - end + def caching_allowed? + request.get? && response.status == 200 + end - private - def cache_configured? - self.class.cache_configured? + protected + # Convenience accessor + def cache(key, options = {}, &block) + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield end + end + + private + def cache_configured? + self.class.cache_configured? + end end end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index cb0c3a1384..35111a4b92 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -2,9 +2,12 @@ require 'set' module ActionController #:nodoc: module Caching - # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching, - # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which - # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example: + # Action caching is similar to page caching by the fact that the entire + # output of the response is cached, but unlike page caching, every + # request still goes through the Action Pack. The key benefit + # of this is that filters are run before the cache is served, which + # allows for authentication and other restrictions on whether someone + # is allowed to see the cache. Example: # # class ListsController < ApplicationController # before_filter :authenticate, :except => :public @@ -12,45 +15,64 @@ module ActionController #:nodoc: # 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 - # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches. + # In this example, the public action doesn't require authentication, + # so it's possible to use the faster page caching method. But both + # the show and feed action are to be shielded behind the authenticate + # filter, so we need to implement those as action caches. # - # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both - # the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named - # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and - # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern. + # Action caching internally uses the fragment caching and an around + # filter to do the job. The fragment cache is named according to both + # the current host and the path. So a page that is accessed at + # http://david.somewhere.com/lists/show/1 will result in a fragment named + # "david.somewhere.com/lists/show/1". This allows the cacher to + # differentiate between "david.somewhere.com/lists/" and + # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting + # the subdomain-as-account-key pattern. # - # Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt> - # are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same - # as <tt>:action => 'list', :format => :xml</tt>. + # Different representations of the same resource, e.g. + # <tt>http://david.somewhere.com/lists</tt> and + # <tt>http://david.somewhere.com/lists.xml</tt> + # are treated like separate requests and so are cached separately. + # Keep in mind when expiring an action cache that + # <tt>:action => 'lists'</tt> is not the same as + # <tt>:action => 'list', :format => :xml</tt>. # - # 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. + # 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 (or :unless) to pass a Proc that specifies when the action should be cached. + # And you can also use :if (or :unless) to pass a Proc that + # specifies when the action should be cached. # # Finally, if you are using memcached, you can also pass :expires_in. # # 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 }, :expires_in => 1.hour - # caches_action :feed, :cache_path => Proc.new { |controller| - # controller.params[:user_id] ? - # controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) : - # controller.send(:list_url, controller.params[:id]) } + # caches_action :index, :if => proc do |c| + # !c.request.format.json? # cache if is not a JSON request + # end + # + # caches_action :show, :cache_path => { :project => 1 }, + # :expires_in => 1.hour + # + # caches_action :feed, :cache_path => proc do |controller| + # if controller.params[:user_id] + # controller.send(:user_list_url, + # controller.params[:user_id], controller.params[:id]) + # else + # controller.send(:list_url, controller.params[:id]) + # end + # end # end # - # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information. + # If you pass :layout => false, it will only cache your action + # content. It is useful when your layout has dynamic information. # module Actions - def self.included(base) #:nodoc: - base.extend(ClassMethods) - base.class_eval do - attr_accessor :rendered_action_cache, :action_cache_path - end - end + extend ActiveSupport::Concern module ClassMethods # Declares that +actions+ should be cached. @@ -58,120 +80,83 @@ module ActionController #:nodoc: def caches_action(*actions) return unless cache_configured? options = actions.extract_options! - filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) } - - cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options) + filter_options = options.extract!(:if, :unless).merge(:only => actions) + cache_options = options.extract!(:layout, :cache_path).merge(:store_options => options) - around_filter cache_filter, filter_options + around_filter ActionCacheFilter.new(cache_options), filter_options end end - protected - def expire_action(options = {}) - return unless cache_configured? + def _render_cache_fragment(cache, extension, layout) + render :text => cache, :layout => layout, :content_type => Mime[extension || :html] + end - if options[:action].is_a?(Array) - options[:action].dup.each do |action| - expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action }), false)) - end - else - expire_fragment(ActionCachePath.path_for(self, options, false)) - end + def _save_fragment(name, layout, options) + return unless caching_allowed? + + content = layout ? view_context.content_for(:layout) : response_body + write_fragment(name, content, options) + end + + protected + def expire_action(options = {}) + return unless cache_configured? + + actions = options[:action] + if actions.is_a?(Array) + actions.each {|action| expire_action(options.merge(:action => action)) } + else + expire_fragment(ActionCachePath.new(self, options, false).path) end + end class ActionCacheFilter #:nodoc: def initialize(options, &block) - @options = options + @cache_path, @store_options, @layout = + options.values_at(:cache_path, :store_options, :layout) end def filter(controller) - should_continue = before(controller) - yield if should_continue - after(controller) - end - - def before(controller) - cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path))) - - if cache = controller.read_fragment(cache_path.path, @options[:store_options]) - controller.rendered_action_cache = true - set_content_type!(controller, cache_path.extension) - options = { :text => cache } - options.merge!(:layout => true) if cache_layout? - controller.__send__(:render, options) - false + path_options = if @cache_path.respond_to?(:call) + controller.instance_exec(controller, &@cache_path) else - controller.action_cache_path = cache_path - end - end - - def after(controller) - return if controller.rendered_action_cache || !caching_allowed(controller) - action_content = cache_layout? ? content_for_layout(controller) : controller.response.body - controller.write_fragment(controller.action_cache_path.path, action_content, @options[:store_options]) - end - - private - def set_content_type!(controller, extension) - controller.response.content_type = Mime::Type.lookup_by_extension(extension).to_s if extension + @cache_path end - def path_options_for(controller, options) - ((path_options = options[:cache_path]).respond_to?(:call) ? path_options.call(controller) : path_options) || {} - end - - def caching_allowed(controller) - controller.request.get? && controller.response.status.to_i == 200 - end - - def cache_layout? - @options[:layout] == false - end + cache_path = ActionCachePath.new(controller, path_options || {}) - def content_for_layout(controller) - template = controller.view_context - template.layout && template.instance_variable_get('@cached_content_for_layout') + if cache = controller.read_fragment(cache_path.path, @store_options) + controller._render_cache_fragment(cache, cache_path.extension, @layout == false) + else + yield + controller._save_fragment(cache_path.path, @layout == false, @store_options) end + end end class ActionCachePath attr_reader :path, :extension - class << self - def path_for(controller, options, infer_extension = true) - new(controller, options, infer_extension).path - end - end - - # If +infer_extension+ is true, the cache path extension is looked up from the request's path & format. - # This is desirable when reading and writing the cache, but not when expiring the cache - - # expire_action should expire the same files regardless of the request format. + # If +infer_extension+ is true, the cache path extension is looked up from the request's + # path & format. This is desirable when reading and writing the cache, but not when + # expiring the cache - expire_action should expire the same files regardless of the + # request format. def initialize(controller, options = {}, infer_extension = true) if infer_extension - extract_extension(controller.request) - options = options.reverse_merge(:format => @extension) if options.is_a?(Hash) + @extension = controller.params[:format] + options.reverse_merge!(:format => @extension) if options.is_a?(Hash) end - path = controller.url_for(options).split('://').last - normalize!(path) - add_extension!(path, @extension) - @path = URI.unescape(path) + path = controller.url_for(options).split(%r{://}).last + @path = normalize!(path) end - private - def normalize!(path) - path << 'index' if path[-1] == ?/ - end - - def add_extension!(path, extension) - path << ".#{extension}" if extension and !path.ends_with?(extension) - end - - def extract_extension(request) - # Don't want just what comes after the last '.' to accommodate multi part extensions - # such as tar.gz. - @extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format - end + private + def normalize!(path) + path << 'index' if path[-1] == ?/ + path << ".#{extension}" if extension and !path.ends_with?(extension) + URI.unescape(path) + end end end end diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 59e24619e3..8c1167d526 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -51,40 +51,32 @@ module ActionController #:nodoc: # Writes <tt>content</tt> to the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) def write_fragment(key, content, options = nil) return content unless cache_configured? - key = fragment_cache_key(key) - event = ActiveSupport::Orchestra.instrument(:write_fragment, :key => key) do + + ActiveSupport::Notifications.instrument(:write_fragment, :key => key) do cache_store.write(key, content, options) end - - self.class.log_with_time("Cached fragment miss: #{key}", event.duration) content end # Reads a cached fragment from the location signified by <tt>key</tt> (see <tt>expire_fragment</tt> for acceptable formats) def read_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) - event = ActiveSupport::Orchestra.instrument(:read_fragment, :key => key) do + + ActiveSupport::Notifications.instrument(:read_fragment, :key => key) do cache_store.read(key, options) end - - self.class.log_with_time("Cached fragment hit: #{key}", event.duration) - event.result end # Check if a cached fragment from the location signified by <tt>key</tt> exists (see <tt>expire_fragment</tt> for acceptable formats) def fragment_exist?(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) - event = ActiveSupport::Orchestra.instrument(:fragment_exist?, :key => key) do + + ActiveSupport::Notifications.instrument(:fragment_exist?, :key => key) do cache_store.exist?(key, options) end - - self.class.log_with_time("Cached fragment exists?: #{key}", event.duration) - event.result end # Removes fragments from the cache. @@ -106,11 +98,10 @@ module ActionController #:nodoc: # method (or <tt>delete_matched</tt>, for Regexp keys.) def expire_fragment(key, options = nil) return unless cache_configured? - key = fragment_cache_key(key) unless key.is_a?(Regexp) message = nil - event = ActiveSupport::Orchestra.instrument(:expire_fragment, :key => key) do + ActiveSupport::Notifications.instrument(:expire_fragment, :key => key) do if key.is_a?(Regexp) message = "Expired fragments matching: #{key.source}" cache_store.delete_matched(key, options) @@ -119,9 +110,6 @@ module ActionController #:nodoc: cache_store.delete(key, options) end end - - self.class.log_with_time(message, event.duration) - event.result end end end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 4fb154470f..d46f528c7e 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -64,12 +64,9 @@ module ActionController #:nodoc: return unless perform_caching path = page_cache_path(path) - event = ActiveSupport::Orchestra.instrument(:expire_page, :path => path) do + ActiveSupport::Notifications.instrument(:expire_page, :path => path) do File.delete(path) if File.exist?(path) end - - log_with_time("Expired page: #{path}", event.duration) - event.result end # Manually cache the +content+ in the key determined by +path+. Example: @@ -78,13 +75,10 @@ module ActionController #:nodoc: return unless perform_caching path = page_cache_path(path) - event = ActiveSupport::Orchestra.instrument(:cache_page, :path => path) do + ActiveSupport::Notifications.instrument(:cache_page, :path => path) do FileUtils.makedirs(File.dirname(path)) File.open(path, "wb+") { |f| f.write(content) } end - - log_with_time("Cached page: #{path}", event.duration) - event.result end # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index c1be264ffb..871f41bfdd 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -70,7 +70,7 @@ module ActionController #:nodoc: protected # gets the action cache path for the given options. def action_path_for(options) - ActionController::Caching::Actions::ActionCachePath.path_for(controller, options) + Actions::ActionCachePath.new(controller, options).path end # Retrieve instance variables set in the controller. diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb index d98e9ac7bd..589061e77c 100644 --- a/actionpack/lib/action_controller/deprecated.rb +++ b/actionpack/lib/action_controller/deprecated.rb @@ -1,2 +1,4 @@ ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response +ActionController::Routing = ActionDispatch::Routing +ActionController::Routing::Routes = ActionDispatch::Routing::RouteSet.new diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb index 008fb54715..e04da42637 100644 --- a/actionpack/lib/action_controller/dispatch/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb @@ -50,7 +50,7 @@ module ActionController def new # DEPRECATE Rails application fallback - Rails.application.new + Rails.application end end end diff --git a/actionpack/lib/action_controller/legacy/layout.rb b/actionpack/lib/action_controller/legacy/layout.rb deleted file mode 100644 index 53762158fc..0000000000 --- a/actionpack/lib/action_controller/legacy/layout.rb +++ /dev/null @@ -1,256 +0,0 @@ -require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/class' -require 'active_support/core_ext/class/delegating_attributes' -require 'active_support/core_ext/class/inheritable_attributes' - -module ActionController #:nodoc: - # MegasuperultraHAX - # plz refactor ActionMailer - class Base - @@exempt_from_layout = [ActionView::TemplateHandlers::RJS] - cattr_accessor :exempt_from_layout - end - - module Layout #:nodoc: - def self.included(base) - base.extend(ClassMethods) - base.class_inheritable_accessor :layout_name, :layout_conditions - end - - # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in - # repeated setups. The inclusion pattern has pages that look like this: - # - # <%= render "shared/header" %> - # Hello World - # <%= render "shared/footer" %> - # - # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose - # and if you ever want to change the structure of these two includes, you'll have to change all the templates. - # - # With layouts, you can flip it around and have the common structure know where to insert changing content. This means - # that the header and footer are only mentioned in one place, like this: - # - # // The header part of this layout - # <%= yield %> - # // The footer part of this layout - # - # And then you have content pages that look like this: - # - # hello world - # - # At rendering time, the content page is computed and then inserted in the layout, like this: - # - # // The header part of this layout - # hello world - # // The footer part of this layout - # - # == Accessing shared variables - # - # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with - # references that won't materialize before rendering time: - # - # <h1><%= @page_title %></h1> - # <%= yield %> - # - # ...and content pages that fulfill these references _at_ rendering time: - # - # <% @page_title = "Welcome" %> - # Off-world colonies offers you a chance to start a new life - # - # The result after rendering is: - # - # <h1>Welcome</h1> - # Off-world colonies offers you a chance to start a new life - # - # == Automatic layout assignment - # - # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically - # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named - # <tt>app/views/layouts/weblog.erb</tt> or <tt>app/views/layouts/weblog.builder</tt> exists then it will be automatically set as - # the layout for your WeblogController. You can create a layout with the name <tt>application.erb</tt> or <tt>application.builder</tt> - # and this will be set as the default controller if there is no layout with the same name as the current controller and there is - # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout. - # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.erb</tt>. - # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set. - # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignment if the child - # class has a layout with the same name. - # - # == Inheritance for layouts - # - # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples: - # - # class BankController < ActionController::Base - # layout "bank_standard" - # - # class InformationController < BankController - # - # class VaultController < BankController - # layout :access_level_layout - # - # class EmployeeController < BankController - # layout nil - # - # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites - # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all. - # - # == Types of layouts - # - # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes - # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can - # be done either by specifying a method reference as a symbol or using an inline method (as a proc). - # - # The method reference is the preferred approach to variable layouts and is used like this: - # - # class WeblogController < ActionController::Base - # layout :writers_and_readers - # - # def index - # # fetching posts - # end - # - # private - # def writers_and_readers - # logged_in? ? "writer_layout" : "reader_layout" - # end - # - # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing - # is logged in or not. - # - # If you want to use an inline method, such as a proc, do something like this: - # - # class WeblogController < ActionController::Base - # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } - # - # Of course, the most common way of specifying a layout is still just as a plain template name: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>. - # Otherwise, it will be looked up relative to the template root. - # - # == Conditional layouts - # - # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering - # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The - # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard", :except => :rss - # - # # ... - # - # end - # - # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout - # around the rendered view. - # - # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so - # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>. - # - # == Using a different layout in the action render call - # - # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above. - # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller. - # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example: - # - # class WeblogController < ActionController::Base - # layout "weblog_standard" - # - # def help - # render :action => "help", :layout => "help" - # end - # end - # - # This will render the help action with the "help" layout instead of the controller-wide "weblog_standard" layout. - module ClassMethods - extend ActiveSupport::Memoizable - - # If a layout is specified, all rendered actions will have their result rendered - # when the layout <tt>yield</tt>s. This layout can itself depend on instance variables assigned during action - # performance and have access to them as any normal template would. - def layout(template_name, conditions = {}, auto = false) - add_layout_conditions(conditions) - self.layout_name = template_name - end - - def memoized_default_layout(formats) #:nodoc: - self.layout_name || begin - layout = default_layout_name - layout.is_a?(String) ? find_layout(layout, formats) : layout - rescue ActionView::MissingTemplate - end - end - - def default_layout(*args) - memoized_default_layout(*args) - @_memoized_default_layout ||= ::ActiveSupport::ConcurrentHash.new - @_memoized_default_layout[args] ||= memoized_default_layout(*args) - end - - def memoized_find_layout(layout, formats) #:nodoc: - return layout if layout.nil? || layout.respond_to?(:render) - prefix = layout.to_s =~ /layouts\// ? nil : "layouts" - find_template(layout.to_s, {:formats => formats}, :_prefix => prefix) - end - - def find_layout(*args) - @_memoized_find_layout ||= ::ActiveSupport::ConcurrentHash.new - @_memoized_find_layout[args] ||= memoized_find_layout(*args) - end - - def layout_list #:nodoc: - Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } - end - memoize :layout_list - - def default_layout_name - layout_match = name.underscore.sub(/_controller$/, '') - if layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? - superclass.default_layout_name if superclass.respond_to?(:default_layout_name) - else - layout_match - end - end - memoize :default_layout_name - - private - def add_layout_conditions(conditions) - # :except => :foo == :except => [:foo] == :except => "foo" == :except => ["foo"] - conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } - write_inheritable_hash(:layout_conditions, conditions) - end - end - - def active_layout(name) - name = self.class.default_layout(formats) if name == true - - layout_name = case name - when Symbol then __send__(name) - when Proc then name.call(self) - else name - end - - self.class.find_layout(layout_name, formats) - end - - def _pick_layout(layout_name = nil, implicit = false) - return unless layout_name || implicit - layout_name = true if layout_name.nil? - active_layout(layout_name) if action_has_layout? && layout_name - end - - private - def action_has_layout? - if conditions = self.class.layout_conditions - if only = conditions[:only] - return only.include?(action_name) - elsif except = conditions[:except] - return !except.include?(action_name) - end - end - true - end - - end -end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index e9007d3631..60b3f9a89b 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -49,7 +49,7 @@ module ActionController # and response object available. You might wish to control the # environment and response manually for performance reasons. - attr_internal :status, :headers, :content_type, :app, :response + attr_internal :status, :headers, :content_type, :response def initialize(*) @_headers = {} @@ -69,7 +69,7 @@ module ActionController end # :api: private - def call(name, env) + def dispatch(name, env) @_env = env process(name) to_a @@ -95,7 +95,7 @@ module ActionController end def call(env) - controller = @controller.new.call(@action, env) + @controller.new.dispatch(@action, env) end end @@ -109,6 +109,10 @@ module ActionController middleware_stack end + def self.call(env) + action(env['action_dispatch.request.path_parameters'][:action]).call(env) + end + # Return a rack endpoint for the given action. Memoize the endpoint, so # multiple calls into MyController.action will return the same object # for the same action. diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb index d4cb1e122d..e58df69172 100644 --- a/actionpack/lib/action_controller/metal/benchmarking.rb +++ b/actionpack/lib/action_controller/metal/benchmarking.rb @@ -1,4 +1,4 @@ -require 'benchmark' +require 'active_support/core_ext/benchmark' module ActionController #:nodoc: # The benchmarking module times the performance of actions and reports to the logger. If the Active Record @@ -6,25 +6,6 @@ module ActionController #:nodoc: module Benchmarking #:nodoc: extend ActiveSupport::Concern - module ClassMethods - # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it - # (unless <tt>use_silence</tt> is set to false). - # - # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it - # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark - # will only be conducted if the log level is low enough. - def benchmark(title, log_level = Logger::DEBUG, use_silence = true) - if logger && logger.level == log_level - result = nil - ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } - logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)") - result - else - yield - end - end - end - protected def render(*args, &block) if logger @@ -45,7 +26,7 @@ module ActionController #:nodoc: else super end - end + end private def process_action(*args) diff --git a/actionpack/lib/action_controller/metal/compatibility.rb b/actionpack/lib/action_controller/metal/compatibility.rb index 22f9ab219c..c251d79f4e 100644 --- a/actionpack/lib/action_controller/metal/compatibility.rb +++ b/actionpack/lib/action_controller/metal/compatibility.rb @@ -25,9 +25,11 @@ module ActionController # cattr_reader :protected_instance_variables cattr_accessor :protected_instance_variables - self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_headers @_params - @_flash @_response) + self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render + @variables_added @request_origin @url + @parent_controller @action_name + @before_filter_chain_aborted @_headers @_params + @_flash @_response) # Indicates whether or not optimise the generated named # route helper methods diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 8575d30335..5156fbc1d5 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -3,6 +3,7 @@ module ActionController extend ActiveSupport::Concern include RackConvenience + include Head # Sets the etag, last_modified, or both on the response and renders a # "304 Not Modified" response if the request is already fresh. @@ -27,43 +28,9 @@ module ActionController response.etag = options[:etag] if options[:etag] response.last_modified = options[:last_modified] if options[:last_modified] + response.cache_control[:public] = true if options[:public] - if options[:public] - response.cache_control[:public] = true - end - - if request.fresh?(response) - head :not_modified - end - end - - # Return a response that has no content (merely headers). The options - # argument is interpreted to be a hash of header names and values. - # This allows you to easily return a response that consists only of - # significant headers: - # - # head :created, :location => person_path(@person) - # - # It can also be used to return exceptional conditions: - # - # return head(:method_not_allowed) unless request.post? - # return head(:bad_request) unless valid_request? - # render - def head(*args) - if args.length > 2 - raise ArgumentError, "too many arguments to head" - elsif args.empty? - raise ArgumentError, "too few arguments to head" - end - options = args.extract_options! - status = args.shift || options.delete(:status) || :ok - location = options.delete(:location) - - options.each do |key, value| - headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s - end - - render :nothing => true, :status => status, :location => location + head :not_modified if request.fresh?(response) end # Sets the etag and/or last_modified on the response and checks it against @@ -113,7 +80,7 @@ module ActionController # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or # intermediate caches (like caching proxy servers). def expires_now #:doc: - response.headers["Cache-Control"] = "no-cache" + response.cache_control.replace(:no_cache => true) end end end diff --git a/actionpack/lib/action_controller/metal/configuration.rb b/actionpack/lib/action_controller/metal/configuration.rb new file mode 100644 index 0000000000..5c829853b7 --- /dev/null +++ b/actionpack/lib/action_controller/metal/configuration.rb @@ -0,0 +1,28 @@ +module ActionController + module Configuration + extend ActiveSupport::Concern + + def config + @config ||= self.class.config + end + + def config=(config) + @config = config + end + + module ClassMethods + def default_config + @default_config ||= {} + end + + def config + self.config ||= default_config + end + + def config=(config) + @config = ActiveSupport::OrderedHash.new + @config.merge!(config) + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index d4806623c3..6855ca1478 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -44,24 +44,31 @@ module ActionController #:nodoc: # * <tt>:httponly</tt> - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. module Cookies - def self.included(base) - base.helper_method :cookies + extend ActiveSupport::Concern + + include RackConvenience + + included do + helper_method :cookies end - protected - # Returns the cookie container, which operates as described above. - def cookies - @cookies ||= CookieJar.new(self) - end + protected + # Returns the cookie container, which operates as described above. + def cookies + @cookies ||= CookieJar.build(request, response) + end end class CookieJar < Hash #:nodoc: - def initialize(controller) - @controller, @cookies = controller, controller.request.cookies - super() - update(@cookies) + def self.build(request, response) + new.tap do |hash| + hash.update(request.cookies) + hash.response = response + end end + attr_accessor :response + # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) super(name.to_s) @@ -72,13 +79,16 @@ module ActionController #:nodoc: def []=(key, options) if options.is_a?(Hash) options.symbolize_keys! + value = options[:value] else - options = { :value => options } + value = options + options = { :value => value } end - options[:path] = "/" unless options.has_key?(:path) - super(key.to_s, options[:value]) - @controller.response.set_cookie(key, options) + super(key.to_s, value) + + options[:path] ||= "/" + response.set_cookie(key, options) end # Removes the cookie on the client machine by setting the value to an empty string @@ -86,9 +96,10 @@ module ActionController #:nodoc: # an options hash to delete cookies with extra data such as a <tt>:path</tt>. def delete(key, options = {}) options.symbolize_keys! - options[:path] = "/" unless options.has_key?(:path) - super(key.to_s) - @controller.response.delete_cookie(key, options) + options[:path] ||= "/" + value = super(key.to_s) + response.delete_cookie(key, options) + value end end end diff --git a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb index 4259d9de19..a53c052075 100644 --- a/actionpack/lib/action_controller/metal/filter_parameter_logging.rb +++ b/actionpack/lib/action_controller/metal/filter_parameter_logging.rb @@ -4,10 +4,6 @@ module ActionController include AbstractController::Logger - included do - include InstanceMethodsForNewBase - end - module ClassMethods # Replace sensitive parameter data from the request log. # Filters parameters that have any of the arguments as a substring. @@ -17,8 +13,6 @@ module ActionController # can be replaced using String#replace or similar method. # # Examples: - # filter_parameter_logging - # => Does nothing, just slows the logging process down # # filter_parameter_logging :password # => replaces the value to all keys matching /password/i with "[FILTERED]" @@ -33,64 +27,51 @@ module ActionController # => reverses the value to all keys matching /secret/i, and # replaces the value to all keys matching /foo|bar/i with "[FILTERED]" def filter_parameter_logging(*filter_words, &block) - parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0 + raise "You must filter at least one word from logging" if filter_words.empty? + + parameter_filter = Regexp.new(filter_words.join('|'), true) - define_method(:filter_parameters) do |unfiltered_parameters| - filtered_parameters = {} + define_method(:filter_parameters) do |original_params| + filtered_params = {} - unfiltered_parameters.each do |key, value| + original_params.each do |key, value| if key =~ parameter_filter - filtered_parameters[key] = '[FILTERED]' + value = '[FILTERED]' elsif value.is_a?(Hash) - filtered_parameters[key] = filter_parameters(value) + value = filter_parameters(value) elsif value.is_a?(Array) - filtered_parameters[key] = value.collect do |item| - filter_parameters(item) - end + value = value.map { |item| filter_parameters(item) } elsif block_given? key = key.dup value = value.dup if value.duplicable? yield key, value - filtered_parameters[key] = value - else - filtered_parameters[key] = value end + + filtered_params[key] = value end - filtered_parameters + filtered_params end protected :filter_parameters end end - module InstanceMethodsForNewBase - # TODO : Fix the order of information inside such that it's exactly same as the old base - def process(*) - ret = super - - if logger - parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup - parameters = parameters.except!(:controller, :action, :format, :_method, :only_path) + INTERNAL_PARAMS = [:controller, :action, :format, :_method, :only_path] - unless parameters.empty? - # TODO : Move DelayedLog to AS - log = AbstractController::Logger::DelayedLog.new { " Parameters: #{parameters.inspect}" } - logger.info(log) - end - end - - ret + def process(*) + response = super + if logger + parameters = filter_parameters(params).except!(*INTERNAL_PARAMS) + logger.info { " Parameters: #{parameters.inspect}" } unless parameters.empty? end + response end - private + protected - # TODO : This method is not needed for the new base - def log_processing_for_parameters - parameters = respond_to?(:filter_parameters) ? filter_parameters(params) : params.dup - parameters = parameters.except!(:controller, :action, :format, :_method) - - logger.info " Parameters: #{parameters.inspect}" unless parameters.empty? + def filter_parameters(params) + params.dup end + end end diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 8753253dc6..feb066a6f6 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -49,7 +49,7 @@ module ActionController #:nodoc: class FlashHash < Hash def initialize #:nodoc: super - @used = {} + @used = Set.new end def []=(k, v) #:nodoc: @@ -65,7 +65,7 @@ module ActionController #:nodoc: alias :merge! :update def replace(h) #:nodoc: - @used = {} + @used = Set.new super end @@ -104,8 +104,8 @@ module ActionController #:nodoc: # This method is called automatically by filters, so you generally don't need to care about it. def sweep #:nodoc: keys.each do |k| - unless @used[k] - use(k) + unless @used.include?(k) + @used << k else delete(k) @used.delete(k) @@ -113,47 +113,45 @@ module ActionController #:nodoc: end # clean up after keys that could have been left over by calling reject! or shift on the flash - (@used.keys - keys).each{ |k| @used.delete(k) } + (@used - keys).each{ |k| @used.delete(k) } end - def store(session, key = "flash") + def store(session) return if self.empty? - session[key] = self + session["flash"] = self end - private - # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods - # use() # marks the entire flash as used - # use('msg') # marks the "msg" entry as used - # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) - # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) - # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself - # if no key is passed. - def use(key = nil, used = true) - Array(key || keys).each { |k| @used[k] = used } - return key ? self[key] : self - end + private + # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods + # use() # marks the entire flash as used + # use('msg') # marks the "msg" entry as used + # use(nil, false) # marks the entire flash as unused (keeps it around for one more action) + # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action) + # Returns the single value for the key you asked to be marked (un)used or the FlashHash itself + # if no key is passed. + def use(key = nil, used = true) + Array(key || keys).each { |k| used ? @used << k : @used.delete(k) } + return key ? self[key] : self + end end protected def process_action(method_name) super - if defined? @_flash - @_flash.store(session) - remove_instance_variable(:@_flash) - end + @_flash.store(session) if @_flash + @_flash = nil end def reset_session super - remove_instance_variable(:@_flash) if defined?(@_flash) + @_flash = nil end # Access the contents of the flash. Use <tt>flash["notice"]</tt> to # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash #:doc: - if !defined?(@_flash) + unless @_flash @_flash = session["flash"] || FlashHash.new @_flash.sweep end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb new file mode 100644 index 0000000000..68fa0a0402 --- /dev/null +++ b/actionpack/lib/action_controller/metal/head.rb @@ -0,0 +1,27 @@ +module ActionController + module Head + # Return a response that has no content (merely headers). The options + # argument is interpreted to be a hash of header names and values. + # This allows you to easily return a response that consists only of + # significant headers: + # + # head :created, :location => person_path(@person) + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + def head(status, options = {}) + options, status = status, nil if status.is_a?(Hash) + status ||= options.delete(:status) || :ok + location = options.delete(:location) + + options.each do |key, value| + headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s + end + + render :nothing => true, :status => status, :location => location + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 7c52779064..b4325e24ad 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,5 +1,3 @@ -require 'active_support/dependencies' - module ActionController # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, # +numbers+ and model objects, to name a few. These helpers are available to all templates @@ -54,7 +52,7 @@ module ActionController included do # Set the default directory for helpers extlib_inheritable_accessor(:helpers_dir) do - defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers" + defined?(Rails) ? "#{Rails.root}/app/helpers" : "app/helpers" end end @@ -64,46 +62,6 @@ module ActionController super end - # The +helper+ class method can take a series of helper module names, a block, or both. - # - # ==== Parameters - # *args<Array[Module, Symbol, String, :all]> - # block<Block>:: A block defining helper methods - # - # ==== Examples - # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file - # and include the module in the template class. The second form illustrates how to include custom helpers - # when working with namespaced controllers, or other cases where the file containing the helper definition is not - # in one of Rails' standard load paths: - # helper :foo # => requires 'foo_helper' and includes FooHelper - # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper - # - # When the argument is a module it will be included directly in the template class. - # helper FooHelper # => includes FooHelper - # - # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers beneath - # <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT). - # helper :all - # - # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available - # to the template. - # # One line - # helper { def hello() "Hello, world!" end } - # # Multi-line - # helper do - # def foo(bar) - # "#{bar} is the very best" - # end - # end - # - # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of - # +symbols+, +strings+, +modules+ and blocks. - # helper(:three, BlindHelper) { def mice() 'mice' end } - # - def helper(*args, &block) - super(*_modules_for_helpers(args), &block) - end - # Declares helper accessors for controller attributes. For example, the # following adds new +name+ and <tt>name=</tt> instance methods to a # controller and makes them available to the view: @@ -123,15 +81,8 @@ module ActionController end private - # Returns a list of modules, normalized from the acceptable kinds of - # helpers with the following behavior: - # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper", - # and "foo_bar_helper.rb" is loaded using require_dependency. - # :all:: Loads all modules in the #helpers_dir - # Module:: No further processing - # - # After loading the appropriate files, the corresponding modules - # are returned. + # Overwrite _modules_for_helpers to accept :all as argument, which loads + # all helpers in helpers_dir. # # ==== Parameters # args<Array[String, Symbol, Module, all]>:: A list of helpers @@ -140,20 +91,8 @@ module ActionController # Array[Module]:: A normalized list of modules for the list of # helpers provided. def _modules_for_helpers(args) - args.flatten.map! do |arg| - case arg - when :all - _modules_for_helpers all_application_helpers - when String, Symbol - file_name = "#{arg.to_s.underscore}_helper" - require_dependency(file_name, "Missing helper file helpers/%s.rb") - file_name.camelize.constantize - when Module - arg - else - raise ArgumentError, "helper must be a String, Symbol, or Module" - end - end + args += all_application_helpers if args.delete(:all) + super(args) end def default_helper_module! diff --git a/actionpack/lib/action_controller/metal/layouts.rb b/actionpack/lib/action_controller/metal/layouts.rb index cac529b1ae..cc7088248a 100644 --- a/actionpack/lib/action_controller/metal/layouts.rb +++ b/actionpack/lib/action_controller/metal/layouts.rb @@ -167,26 +167,5 @@ module ActionController controller_path end end - - private - def _determine_template(options) - super - - return if (options.key?(:text) || options.key?(:inline) || options.key?(:partial)) && !options.key?(:layout) - layout = options.key?(:layout) ? options[:layout] : :default - options[:_layout] = _layout_for_option(layout, options[:_template].details) - end - - def _layout_for_option(name, details) - case name - when String then _layout_for_name(name, details) - when true then _default_layout(details, true) - when :default then _default_layout(details, false) - when false, nil then nil - else - raise ArgumentError, - "String, true, or false, expected for `layout'; you passed #{name.inspect}" - end - end end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 3026067868..468c5f4fae 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -3,7 +3,8 @@ module ActionController #:nodoc: extend ActiveSupport::Concern included do - class_inheritable_reader :mimes_for_respond_to + extlib_inheritable_accessor :responder, :mimes_for_respond_to, :instance_writer => false + self.responder = ActionController::Responder clear_respond_to end @@ -46,7 +47,7 @@ module ActionController #:nodoc: # Clear all mimes in respond_to. # def clear_respond_to - write_inheritable_attribute(:mimes_for_respond_to, ActiveSupport::OrderedHash.new) + self.mimes_for_respond_to = ActiveSupport::OrderedHash.new end end @@ -221,10 +222,6 @@ module ActionController #:nodoc: end end - def responder - ActionController::Responder - end - protected # Collect mimes declared in the class method respond_to valid for the diff --git a/actionpack/lib/action_controller/metal/rack_convenience.rb b/actionpack/lib/action_controller/metal/rack_convenience.rb index a80569c530..131d20114d 100644 --- a/actionpack/lib/action_controller/metal/rack_convenience.rb +++ b/actionpack/lib/action_controller/metal/rack_convenience.rb @@ -8,7 +8,7 @@ module ActionController attr_internal :request end - def call(name, env) + def dispatch(action, env) @_request = ActionDispatch::Request.new(env) @_response = ActionDispatch::Response.new @_response.request = request diff --git a/actionpack/lib/action_controller/metal/redirector.rb b/actionpack/lib/action_controller/metal/redirector.rb index f79fd54acd..b55f5e7bfc 100644 --- a/actionpack/lib/action_controller/metal/redirector.rb +++ b/actionpack/lib/action_controller/metal/redirector.rb @@ -16,7 +16,7 @@ module ActionController logger.info("Redirected to #{url}") if logger && logger.info? self.status = status self.location = url.gsub(/[\r\n]/, '') - self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>" + self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(url)}\">redirected</a>.</body></html>" end end end diff --git a/actionpack/lib/action_controller/metal/rendering_controller.rb b/actionpack/lib/action_controller/metal/rendering_controller.rb index 4da32ca1b3..237299cd30 100644 --- a/actionpack/lib/action_controller/metal/rendering_controller.rb +++ b/actionpack/lib/action_controller/metal/rendering_controller.rb @@ -1,54 +1,18 @@ module ActionController - class HashKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } - - def self.get(klass, formats, locale) - @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) - end - - attr_accessor :hash - def initialize(klass, formats, locale) - @formats, @locale = formats, locale - @hash = [formats, locale].hash - end - - alias_method :eql?, :equal? - - def inspect - "#<HashKey -- formats: #{@formats} locale: #{@locale}>" - end - end - module RenderingController extend ActiveSupport::Concern - include AbstractController::RenderingController - - module ClassMethods - def clear_template_caches! - ActionView::Partials::PartialRenderer::TEMPLATES.clear - template_cache.clear - super - end - - def template_cache - @template_cache ||= Hash.new {|h,k| h[k] = {} } - end + included do + include AbstractController::RenderingController + include AbstractController::LocalizedCache end def process_action(*) self.formats = request.formats.map {|x| x.to_sym} - - super - end - - def _determine_template(*) super end def render(options) - Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) - super self.content_type ||= options[:_template].mime_type.to_s response_body @@ -70,29 +34,19 @@ module ActionController controller_path end - def with_template_cache(name) - self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super - end - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] - elsif !options.key?(:partial) - options[:_template_name] = (options[:action] || action_name).to_s + if (options.keys & [:partial, :file, :template, :text, :inline]).empty? + options[:_template_name] ||= options[:action] options[:_prefix] = _prefix end super end + def format_for_text + formats.first + end + def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) self.status = status if status diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index ad06657f86..113c20a758 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -5,7 +5,6 @@ module ActionController #:nodoc: module RequestForgeryProtection extend ActiveSupport::Concern - # TODO : Remove the defined? check when new base is the main base include AbstractController::Helpers, Session included do @@ -21,26 +20,26 @@ module ActionController #:nodoc: helper_method :protect_against_forgery? end - # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current web application, not a - # forged link from another site, is done by embedding a token based on a random string stored in the session (which an attacker wouldn't know) in all - # forms and Ajax requests generated by Rails and then verifying the authenticity of that token in the controller. Only - # HTML/JavaScript requests are checked, so this will not protect your XML API (presumably you'll have a different authentication - # scheme there anyway). Also, GET requests are not protected as these should be idempotent anyway. + # Protecting controller actions from CSRF attacks by ensuring that all forms are coming from the current + # web application, not a forged link from another site, is done by embedding a token based on a random + # string stored in the session (which an attacker wouldn't know) in all forms and Ajax requests generated + # by Rails and then verifying the authenticity of that token in the controller. Only HTML/JavaScript + # requests are checked, so this will not protect your XML API (presumably you'll have a different + # authentication scheme there anyway). Also, GET requests are not protected as these should be + # idempotent anyway. # # This is turned on with the <tt>protect_from_forgery</tt> method, which will check the token and raise an - # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the error message in - # production by editing public/422.html. A call to this method in ApplicationController is generated by default in post-Rails 2.0 - # applications. + # ActionController::InvalidAuthenticityToken if it doesn't match what was expected. You can customize the + # error message in production by editing public/422.html. A call to this method in ApplicationController is + # generated by default in post-Rails 2.0 applications. # - # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form manually (without the - # use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to include a hidden field named like that and - # set its value to what is returned by <tt>form_authenticity_token</tt>. Same applies to manually constructed Ajax requests. To - # make the token available through a global variable to scripts on a certain page, you could add something like this to a view: + # The token parameter is named <tt>authenticity_token</tt> by default. If you are generating an HTML form + # manually (without the use of Rails' <tt>form_for</tt>, <tt>form_tag</tt> or other helpers), you have to + # include a hidden field named like that and set its value to what is returned by + # <tt>form_authenticity_token</tt>. # - # <%= javascript_tag "window._token = '#{form_authenticity_token}'" %> - # - # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails 1.x, add this to - # config/environments/test.rb: + # Request forgery protection is disabled by default in test environment. If you are upgrading from Rails + # 1.x, add this to config/environments/test.rb: # # # Disable request forgery protection in test environment # config.action_controller.allow_forgery_protection = false @@ -57,7 +56,8 @@ module ActionController #:nodoc: # * Keep your GET requests safe and idempotent. More reading material: # * http://www.xml.com/pub/a/2002/04/24/deviant.html # * http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 - # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look for "Expires: at end of session" + # * Make sure the session cookies that Rails creates are non-persistent. Check in Firefox and look + # for "Expires: at end of session" # module ClassMethods # Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked. @@ -76,10 +76,7 @@ module ActionController #:nodoc: # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified. def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token - before_filter :verify_authenticity_token, :only => options.delete(:only), :except => options.delete(:except) - if options[:secret] || options[:digest] - ActiveSupport::Deprecation.warn("protect_from_forgery only takes :only and :except options now. :digest and :secret have no effect", caller) - end + before_filter :verify_authenticity_token, options end end @@ -88,31 +85,24 @@ module ActionController #:nodoc: def verify_authenticity_token verified_request? || raise(ActionController::InvalidAuthenticityToken) end - + # Returns true or false if a request is verified. Checks: # # * is the format restricted? By default, only HTML requests are checked. # * is it a GET request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? def verified_request? - !protect_against_forgery? || - request.method == :get || - request.xhr? || - !verifiable_request_format? || + !protect_against_forgery? || request.forgery_whitelisted? || form_authenticity_token == params[request_forgery_protection_token] end - - def verifiable_request_format? - !request.content_type.nil? && request.content_type.verify_request? - end - + # Sets the token value for the current session. def form_authenticity_token session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32) end def protect_against_forgery? - allow_forgery_protection && request_forgery_protection_token + allow_forgery_protection end end end diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index a16ed97131..e8e88e7479 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -14,12 +14,11 @@ module ActionController #:nodoc: # # When a request comes, for example with format :xml, three steps happen: # - # 1) respond_with searches for a template at people/index.xml; + # 1) responder searches for a template at people/index.xml; # - # 2) if the template is not available, it will create a responder, passing - # the controller and the resource and invoke :to_xml on it; + # 2) if the template is not available, it will invoke :to_xml in the given resource; # - # 3) if the responder does not respond_to :to_xml, call to_format on it. + # 3) if the responder does not respond_to :to_xml, call :to_format on it. # # === Builtin HTTP verb semantics # @@ -88,14 +87,16 @@ module ActionController #:nodoc: @resource = resources.is_a?(Array) ? resources.last : resources @resources = resources @options = options + @action = options.delete(:action) @default_response = options.delete(:default_response) end delegate :head, :render, :redirect_to, :to => :controller delegate :get?, :post?, :put?, :delete?, :to => :request - # Undefine :to_json since it's defined on Object - undef_method :to_json + # Undefine :to_json and :to_yaml since it's defined on Object + undef_method(:to_json) if method_defined?(:to_json) + undef_method(:to_yaml) if method_defined?(:to_yaml) # Initializes a new responder an invoke the proper format. If the format is # not defined, call to_format. @@ -111,14 +112,8 @@ module ActionController #:nodoc: # def to_html default_render - rescue ActionView::MissingTemplate - if get? - raise - elsif has_errors? - render :action => default_action - else - redirect_to resource_location - end + rescue ActionView::MissingTemplate => e + navigation_behavior(e) end # All others formats follow the procedure below. First we try to render a @@ -127,9 +122,26 @@ module ActionController #:nodoc: # def to_format default_render - rescue ActionView::MissingTemplate + rescue ActionView::MissingTemplate => e raise unless resourceful? + api_behavior(e) + end + protected + + # This is the common behavior for "navigation" requests, like :html, :iphone and so forth. + def navigation_behavior(error) + if get? + raise error + elsif has_errors? + render :action => default_action + else + redirect_to resource_location + end + end + + # This is the common behavior for "API" requests, like :xml and :json. + def api_behavior(error) if get? display resource elsif has_errors? @@ -141,8 +153,6 @@ module ActionController #:nodoc: end end - protected - # Checks whether the resource responds to the current format or not. # def resourceful? @@ -194,7 +204,7 @@ module ActionController #:nodoc: # the verb is post. # def default_action - request.post? ? :new : :edit + @action || (request.post? ? :new : :edit) end end end diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb index 654aa08cd3..d70f40ce7a 100644 --- a/actionpack/lib/action_controller/metal/session_management.rb +++ b/actionpack/lib/action_controller/metal/session_management.rb @@ -1,10 +1,8 @@ module ActionController #:nodoc: module SessionManagement #:nodoc: - def self.included(base) - base.class_eval do - extend ClassMethods - end - end + extend ActiveSupport::Concern + + include ActionController::Configuration module ClassMethods # Set the session store to be used for keeping the session data between requests. @@ -35,13 +33,6 @@ module ActionController #:nodoc: session_options.merge!(options) end - # Returns the hash used to configure the session. Example use: - # - # ActionController::Base.session_options[:secure] = true # session only available over HTTPS - def session_options - @session_options ||= {} - end - def session(*args) ActiveSupport::Deprecation.warn( "Disabling sessions for a single controller has been deprecated. " + diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 4761763a26..43c661bef4 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/bytesize' - module ActionController #:nodoc: # Methods for sending arbitrary data and for streaming files to the browser, # instead of rendering. diff --git a/actionpack/lib/action_controller/metal/verification.rb b/actionpack/lib/action_controller/metal/verification.rb index d3d78e3749..500cced539 100644 --- a/actionpack/lib/action_controller/metal/verification.rb +++ b/actionpack/lib/action_controller/metal/verification.rb @@ -79,8 +79,8 @@ module ActionController #:nodoc: # do not apply this verification to the actions specified in the associated # array (may also be a single value). def verify(options={}) - before_filter :only => options[:only], :except => options[:except] do |c| - c.__send__ :verify_action, options + before_filter :only => options[:only], :except => options[:except] do + verify_action options end end end diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb index fac0ed2645..17275793b7 100644 --- a/actionpack/lib/action_controller/middleware.rb +++ b/actionpack/lib/action_controller/middleware.rb @@ -1,34 +1,34 @@ module ActionController class Middleware < Metal class ActionMiddleware - def initialize(controller) - @controller = controller + def initialize(controller, app) + @controller, @app = controller, app end def call(env) - controller = @controller.allocate - controller.send(:initialize) - controller.app = @app - controller._call(env) + @controller.build(@app).dispatch(:index, env) end + end + + class << self + alias build new - def app=(app) - @app = app + def new(app) + ActionMiddleware.new(self, app) end end - - def self.new(app) - middleware = ActionMiddleware.new(self) - middleware.app = app - middleware + + attr_internal :app + + def process(action) + response = super + self.status, self.headers, self.response_body = response if response.is_a?(Array) + response end - - def _call(env) - @_env = env - @_request = ActionDispatch::Request.new(env) - @_response = ActionDispatch::Response.new - @_response.request = @_request - process(:index) + + def initialize(app) + super() + @_app = app end def index diff --git a/actionpack/lib/action_controller/notifications.rb b/actionpack/lib/action_controller/notifications.rb new file mode 100644 index 0000000000..1a4f29e0e2 --- /dev/null +++ b/actionpack/lib/action_controller/notifications.rb @@ -0,0 +1,10 @@ +require 'active_support/notifications' + +ActiveSupport::Notifications.subscribe(/(read|write|cache|expire|exist)_(fragment|page)\??/) do |*args| + event = ActiveSupport::Notifications::Event.new(*args) + + if logger = ActionController::Base.logger + human_name = event.name.to_s.humanize + logger.info("#{human_name} (%.1fms)" % event.duration) + end +end diff --git a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 2adf3575a7..eaed00cfb7 100644 --- a/actionpack/lib/action_controller/routing/generation/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -80,9 +80,8 @@ module ActionController record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1 end - record = extract_record(record_or_hash_or_array) - record = record.to_model if record.respond_to?(:to_model) - namespace = extract_namespace(record_or_hash_or_array) + record = extract_record(record_or_hash_or_array) + record = record.to_model if record.respond_to?(:to_model) args = case record_or_hash_or_array when Hash; [ record_or_hash_or_array ] @@ -105,8 +104,7 @@ module ActionController end args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)} - - named_route = build_named_route_call(record_or_hash_or_array, namespace, inflection, options) + named_route = build_named_route_call(record_or_hash_or_array, inflection, options) url_options = options.except(:action, :routing_type) unless url_options.empty? @@ -138,18 +136,6 @@ module ActionController EOT end - def formatted_polymorphic_url(record_or_hash, options = {}) - ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller) - options[:format] = record_or_hash.pop if Array === record_or_hash - polymorphic_url(record_or_hash, options) - end - - def formatted_polymorphic_path(record_or_hash, options = {}) - ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller) - options[:format] = record_or_hash.pop if record_or_hash === Array - polymorphic_url(record_or_hash, options.merge(:routing_type => :path)) - end - private def action_prefix(options) options[:action] ? "#{options[:action]}_" : '' @@ -159,7 +145,7 @@ module ActionController options[:routing_type] || :url end - def build_named_route_call(records, namespace, inflection, options = {}) + def build_named_route_call(records, inflection, options = {}) unless records.is_a?(Array) record = extract_record(records) route = '' @@ -169,7 +155,7 @@ module ActionController if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize + string << RecordIdentifier.__send__("plural_class_name", parent).singularize string << "_" end end @@ -178,12 +164,12 @@ module ActionController if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << "#{RecordIdentifier.__send__("plural_class_name", record)}" + route << RecordIdentifier.__send__("plural_class_name", record) route = route.singularize if inflection == :singular route << "_" end - action_prefix(options) + namespace + route + routing_type(options).to_s + action_prefix(options) + route + routing_type(options).to_s end def extract_record(record_or_hash_or_array) @@ -193,18 +179,5 @@ module ActionController else record_or_hash_or_array end end - - # Remove the first symbols from the array and return the url prefix - # implied by those symbols. - def extract_namespace(record_or_hash_or_array) - return "" unless record_or_hash_or_array.is_a?(Array) - - namespace_keys = [] - while (key = record_or_hash_or_array.first) && key.is_a?(String) || key.is_a?(Symbol) - namespace_keys << record_or_hash_or_array.shift - end - - namespace_keys.map {|k| "#{k}_"}.join - end end end diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb deleted file mode 100644 index 42ad12e1ea..0000000000 --- a/actionpack/lib/action_controller/routing/builder.rb +++ /dev/null @@ -1,199 +0,0 @@ -require 'active_support/core_ext/hash/except' - -module ActionController - module Routing - class RouteBuilder #:nodoc: - attr_reader :separators, :optional_separators - attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp - - def initialize - @separators = Routing::SEPARATORS - @optional_separators = %w( / ) - - @separator_regexp = /[#{Regexp.escape(separators.join)}]/ - @nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/ - @interval_regexp = /(.*?)(#{separator_regexp}|$)/ - end - - # Accepts a "route path" (a string defining a route), and returns the array - # of segments that corresponds to it. Note that the segment array is only - # partially initialized--the defaults and requirements, for instance, need - # to be set separately, via the +assign_route_options+ method, and the - # <tt>optional?</tt> method for each segment will not be reliable until after - # +assign_route_options+ is called, as well. - def segments_for_route_path(path) - rest, segments = path, [] - - until rest.empty? - segment, rest = segment_for(rest) - segments << segment - end - segments - end - - # A factory method that returns a new segment instance appropriate for the - # format of the given string. - def segment_for(string) - segment = - case string - when /\A\.(:format)?\// - OptionalFormatSegment.new - when /\A:(\w+)/ - key = $1.to_sym - key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key) - when /\A\*(\w+)/ - PathSegment.new($1.to_sym, :optional => true) - when /\A\?(.*?)\?/ - StaticSegment.new($1, :optional => true) - when nonseparator_regexp - StaticSegment.new($1) - when separator_regexp - DividerSegment.new($&, :optional => optional_separators.include?($&)) - end - [segment, $~.post_match] - end - - # Split the given hash of options into requirement and default hashes. The - # segments are passed alongside in order to distinguish between default values - # and requirements. - def divide_route_options(segments, options) - options = options.except(:path_prefix, :name_prefix) - - if options[:namespace] - options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}" - end - - requirements = (options.delete(:requirements) || {}).dup - defaults = (options.delete(:defaults) || {}).dup - conditions = (options.delete(:conditions) || {}).dup - - validate_route_conditions(conditions) - - path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact - options.each do |key, value| - hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements - hash[key] = value - end - - [defaults, requirements, conditions] - end - - # Takes a hash of defaults and a hash of requirements, and assigns them to - # the segments. Any unused requirements (which do not correspond to a segment) - # are returned as a hash. - def assign_route_options(segments, defaults, requirements) - route_requirements = {} # Requirements that do not belong to a segment - - segment_named = Proc.new do |key| - segments.detect { |segment| segment.key == key if segment.respond_to?(:key) } - end - - requirements.each do |key, requirement| - segment = segment_named[key] - if segment - raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp) - if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} - raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" - end - if requirement.multiline? - raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" - end - segment.regexp = requirement - else - route_requirements[key] = requirement - end - end - - defaults.each do |key, default| - segment = segment_named[key] - raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment - segment.is_optional = true - segment.default = default.to_param if default - end - - assign_default_route_options(segments) - ensure_required_segments(segments) - route_requirements - end - - # Assign default options, such as 'index' as a default for <tt>:action</tt>. This - # method must be run *after* user supplied requirements and defaults have - # been applied to the segments. - def assign_default_route_options(segments) - segments.each do |segment| - next unless segment.is_a? DynamicSegment - case segment.key - when :action - if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index' - segment.default ||= 'index' - segment.is_optional = true - end - when :id - if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ '' - segment.is_optional = true - end - end - end - end - - # Makes sure that there are no optional segments that precede a required - # segment. If any are found that precede a required segment, they are - # made required. - def ensure_required_segments(segments) - allow_optional = true - segments.reverse_each do |segment| - allow_optional &&= segment.optional? - if !allow_optional && segment.optional? - unless segment.optionality_implied? - warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required." - end - segment.is_optional = false - elsif allow_optional && segment.respond_to?(:default) && segment.default - # if a segment has a default, then it is optional - segment.is_optional = true - end - end - end - - # Construct and return a route with the given path and options. - def build(path, options) - # Wrap the path with slashes - path = "/#{path}" unless path[0] == ?/ - path = "#{path}/" unless path[-1] == ?/ - - prefix = options[:path_prefix].to_s.gsub(/^\//,'') - path = "/#{prefix}#{path}" unless prefix.blank? - - segments = segments_for_route_path(path) - defaults, requirements, conditions = divide_route_options(segments, options) - requirements = assign_route_options(segments, defaults, requirements) - - # TODO: Segments should be frozen on initialize - segments.each { |segment| segment.freeze } - - route = Route.new(segments, requirements, conditions) - - if !route.significant_keys.include?(:controller) - raise ArgumentError, "Illegal route: the :controller must be specified!" - end - - route.freeze - end - - private - def validate_route_conditions(conditions) - if method = conditions[:method] - [method].flatten.each do |m| - if m == :head - raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers" - end - - unless HTTP_METHODS.include?(m.to_sym) - raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}" - end - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb deleted file mode 100644 index 714cf97861..0000000000 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ /dev/null @@ -1,130 +0,0 @@ -module ActionController - module Routing - # Much of the slow performance from routes comes from the - # complexity of expiry, <tt>:requirements</tt> matching, defaults providing - # and figuring out which url pattern to use. With named routes - # we can avoid the expense of finding the right route. So if - # they've provided the right number of arguments, and have no - # <tt>:requirements</tt>, we can just build up a string and return it. - # - # To support building optimisations for other common cases, the - # generation code is separated into several classes - module Optimisation - def generate_optimisation_block(route, kind) - return "" unless route.optimise? - OPTIMISERS.inject("") do |memo, klazz| - memo << klazz.new(route, kind).source_code - memo - end - end - - class Optimiser - attr_reader :route, :kind - GLOBAL_GUARD_CONDITIONS = [ - "(!defined?(default_url_options) || default_url_options.blank?)", - "(!defined?(controller.default_url_options) || controller.default_url_options.blank?)", - "defined?(request)", - "request" - ] - - def initialize(route, kind) - @route = route - @kind = kind - end - - def guard_conditions - ["false"] - end - - def generation_code - 'nil' - end - - def source_code - if applicable? - guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ") - "return #{generation_code} if #{guard_condition}\n" - else - "\n" - end - end - - # Temporarily disabled <tt>:url</tt> optimisation pending proper solution to - # Issues around request.host etc. - def applicable? - true - end - end - - # Given a route - # - # map.person '/people/:id' - # - # If the user calls <tt>person_url(@person)</tt>, we can simply - # return a string like "/people/#{@person.to_param}" - # rather than triggering the expensive logic in +url_for+. - class PositionalArguments < Optimiser - def guard_conditions - number_of_arguments = route.required_segment_keys.size - # if they're using foo_url(:id=>2) it's one - # argument, but we don't want to generate /foos/id2 - if number_of_arguments == 1 - ["args.size == 1", "!args.first.is_a?(Hash)"] - else - ["args.size == #{number_of_arguments}"] - end - end - - def generation_code - elements = [] - idx = 0 - - if kind == :url - elements << '#{request.protocol}' - elements << '#{request.host_with_port}' - end - - elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}' - - # The last entry in <tt>route.segments</tt> appears to *always* be a - # 'divider segment' for '/' but we have assertions to ensure that - # we don't include the trailing slashes, so skip them. - (route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment| - if segment.is_a?(DynamicSegment) - elements << segment.interpolation_chunk("args[#{idx}].to_param") - idx += 1 - else - elements << segment.interpolation_chunk - end - end - %("#{elements * ''}") - end - end - - # This case is mostly the same as the positional arguments case - # above, but it supports additional query parameters as the last - # argument - class PositionalArgumentsWithAdditionalParams < PositionalArguments - def guard_conditions - ["args.size == #{route.segment_keys.size + 1}"] + - UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" } - end - - # This case uses almost the same code as positional arguments, - # but add a question mark and args.last.to_query on the end, - # unless the last arg is empty - def generation_code - super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}') - end - - # To avoid generating "http://localhost/?host=foo.example.com" we - # can't use this optimisation on routes without any segments - def applicable? - super && route.segment_keys.size > 0 - end - end - - OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams] - end - end -end diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb deleted file mode 100644 index 9bfebff0c0..0000000000 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ /dev/null @@ -1,167 +0,0 @@ -module ActionController - module Routing - # BEFORE: 0.191446860631307 ms/url - # AFTER: 0.029847304022858 ms/url - # Speed up: 6.4 times - # - # Route recognition is slow due to one-by-one iterating over - # a whole routeset (each map.resources generates at least 14 routes) - # and matching weird regexps on each step. - # - # We optimize this by skipping all URI segments that 100% sure can't - # be matched, moving deeper in a tree of routes (where node == segment) - # until first possible match is accured. In such case, we start walking - # a flat list of routes, matching them with accurate matcher. - # So, first step: search a segment tree for the first relevant index. - # Second step: iterate routes starting with that index. - # - # How tree is walked? We can do a recursive tests, but it's smarter: - # We just create a tree of if-s and elsif-s matching segments. - # - # We have segments of 3 flavors: - # 1) nil (no segment, route finished) - # 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg") - # 3) const (like "/posts", "/comments") - # 4) dynamic ("/:id", "file.:size.:extension") - # - # We split incoming string into segments and iterate over them. - # When segment is nil, we drop immediately, on a current node index. - # When segment is equal to some const, we step into branch. - # If none constants matched, we step into 'dynamic' branch (it's a last). - # If we can't match anything, we drop to last index on a level. - # - # Note: we maintain the original routes order, so we finish building - # steps on a first dynamic segment. - # - # - # Example. Given the routes: - # 0 /posts/ - # 1 /posts/:id - # 2 /posts/:id/comments - # 3 /posts/blah - # 4 /users/ - # 5 /users/:id - # 6 /users/:id/profile - # - # request_uri = /users/123 - # - # There will be only 4 iterations: - # 1) segm test for /posts prefix, skip all /posts/* routes - # 2) segm test for /users/ - # 3) segm test for /users/:id - # (jump to list index = 5) - # 4) full test for /users/:id => here we are! - class RouteSet - def recognize_path(path, environment={}) - result = recognize_optimized(path, environment) and return result - - # Route was not recognized. Try to find out why (maybe wrong verb). - allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } } - - if environment[:method] && !HTTP_METHODS.include?(environment[:method]) - raise NotImplemented.new(*allows) - elsif !allows.empty? - raise MethodNotAllowed.new(*allows) - else - raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}" - end - end - - def segment_tree(routes) - tree = [0] - - i = -1 - routes.each do |route| - i += 1 - # not fast, but runs only once - segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s }) - - node = tree - segments.each do |seg| - seg = :dynamic if seg && seg[0] == ?: - node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg - node = node[node.size - 1][1] - end - end - tree - end - - def generate_code(list, padding=' ', level = 0) - # a digit - return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0]) - - body = padding + "(seg = segments[#{level}]; \n" - - i = 0 - was_nil = false - list.each do |item| - if Array === item - i += 1 - start = (i == 1) - tag, sub = item - if tag == :dynamic - body += padding + "#{start ? 'if' : 'elsif'} true\n" - body += generate_code(sub, padding + " ", level + 1) - break - elsif tag == nil && !was_nil - was_nil = true - body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n" - body += generate_code(sub, padding + " ", level + 1) - else - body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n" - body += generate_code(sub, padding + " ", level + 1) - end - end - end - body += padding + "else\n" - body += padding + " #{list[0]}\n" - body += padding + "end)\n" - body - end - - # this must be really fast - def to_plain_segments(str) - str = str.dup - str.sub!(/^\/+/,'') - str.sub!(/\/+$/,'') - segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also - segments << nil - segments - end - - private - def write_recognize_optimized! - tree = segment_tree(routes) - body = generate_code(tree) - - remove_recognize_optimized! - - instance_eval %{ - def recognize_optimized(path, env) - segments = to_plain_segments(path) - index = #{body} - return nil unless index - while index < routes.size - result = routes[index].recognize(path, env) and return result - index += 1 - end - nil - end - }, '(recognize_optimized)', 1 - end - - def clear_recognize_optimized! - remove_recognize_optimized! - write_recognize_optimized! - end - - def remove_recognize_optimized! - if respond_to?(:recognize_optimized) - class << self - remove_method :recognize_optimized - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/resources.rb b/actionpack/lib/action_controller/routing/resources.rb deleted file mode 100644 index 06506435a2..0000000000 --- a/actionpack/lib/action_controller/routing/resources.rb +++ /dev/null @@ -1,685 +0,0 @@ -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/try' - -module ActionController - # == Overview - # - # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms, - # is something that can be pointed at and it will respond with a representation of the data requested. - # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application - # requests XML data. - # - # RESTful design is based on the assumption that there are four generic verbs that a user of an - # application can request from a \resource (the noun). - # - # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used - # denotes the type of action that should take place. - # - # === The Different Methods and their Usage - # - # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request. - # * POST - Creation of \resources. - # * PUT - Editing of attributes on a \resource. - # * DELETE - Deletion of a \resource. - # - # === Examples - # - # # A GET request on the Posts resource is asking for all Posts - # GET /posts - # - # # A GET request on a single Post resource is asking for that particular Post - # GET /posts/1 - # - # # A POST request on the Posts resource is asking for a Post to be created with the supplied details - # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } } - # - # # A PUT request on a single Post resource is asking for a Post to be updated - # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } } - # - # # A DELETE request on a single Post resource is asking for it to be deleted - # DELETE /posts # with => { :id => 1 } - # - # By using the REST convention, users of our application can assume certain things about how the data - # is requested and how it is returned. Rails simplifies the routing part of RESTful design by - # supplying you with methods to create them in your routes.rb file. - # - # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer - module Resources - INHERITABLE_OPTIONS = :namespace, :shallow - - class Resource #:nodoc: - DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy - - attr_reader :collection_methods, :member_methods, :new_methods - attr_reader :path_prefix, :name_prefix, :path_segment - attr_reader :plural, :singular - attr_reader :options - - def initialize(entities, options) - @plural ||= entities - @singular ||= options[:singular] || plural.to_s.singularize - @path_segment = options.delete(:as) || @plural - - @options = options - - arrange_actions - add_default_actions - set_allowed_actions - set_prefixes - end - - def controller - @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}" - end - - def requirements(with_id = false) - @requirements ||= @options[:requirements] || {} - @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ } - - with_id ? @requirements.merge(@id_requirement) : @requirements - end - - def conditions - @conditions ||= @options[:conditions] || {} - end - - def path - @path ||= "#{path_prefix}/#{path_segment}" - end - - def new_path - new_action = self.options[:path_names][:new] if self.options[:path_names] - new_action ||= Base.resources_path_names[:new] - @new_path ||= "#{path}/#{new_action}" - end - - def shallow_path_prefix - @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix - end - - def member_path - @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id" - end - - def nesting_path_prefix - @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id" - end - - def shallow_name_prefix - @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix - end - - def nesting_name_prefix - "#{shallow_name_prefix}#{singular}_" - end - - def action_separator - @action_separator ||= Base.resource_action_separator - end - - def uncountable? - @singular.to_s == @plural.to_s - end - - def has_action?(action) - !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) - end - - protected - def arrange_actions - @collection_methods = arrange_actions_by_methods(options.delete(:collection)) - @member_methods = arrange_actions_by_methods(options.delete(:member)) - @new_methods = arrange_actions_by_methods(options.delete(:new)) - end - - def add_default_actions - add_default_action(member_methods, :get, :edit) - add_default_action(new_methods, :get, :new) - end - - def set_allowed_actions - only, except = @options.values_at(:only, :except) - @allowed_actions ||= {} - - if only == :all || except == :none - only = nil - except = [] - elsif only == :none || except == :all - only = [] - except = nil - end - - if only - @allowed_actions[:only] = Array(only).map {|a| a.to_sym } - elsif except - @allowed_actions[:except] = Array(except).map {|a| a.to_sym } - end - end - - def action_allowed?(action) - only, except = @allowed_actions.values_at(:only, :except) - (!only || only.include?(action)) && (!except || !except.include?(action)) - end - - def set_prefixes - @path_prefix = options.delete(:path_prefix) - @name_prefix = options.delete(:name_prefix) - end - - def arrange_actions_by_methods(actions) - (actions || {}).inject({}) do |flipped_hash, (key, value)| - (flipped_hash[value] ||= []) << key - flipped_hash - end - end - - def add_default_action(collection, method, action) - (collection[method] ||= []).unshift(action) - end - end - - class SingletonResource < Resource #:nodoc: - def initialize(entity, options) - @singular = @plural = entity - options[:controller] ||= @singular.to_s.pluralize - super - end - - alias_method :shallow_path_prefix, :path_prefix - alias_method :shallow_name_prefix, :name_prefix - alias_method :member_path, :path - alias_method :nesting_path_prefix, :path - end - - # Creates named routes for implementing verb-oriented controllers - # for a collection \resource. - # - # For example: - # - # map.resources :messages - # - # will map the following actions in the corresponding controller: - # - # class MessagesController < ActionController::Base - # # GET messages_url - # def index - # # return all messages - # end - # - # # GET new_message_url - # def new - # # return an HTML form for describing a new message - # end - # - # # POST messages_url - # def create - # # create a new message - # end - # - # # GET message_url(:id => 1) - # def show - # # find and return a specific message - # end - # - # # GET edit_message_url(:id => 1) - # def edit - # # return an HTML form for editing a specific message - # end - # - # # PUT message_url(:id => 1) - # def update - # # find and update a specific message - # end - # - # # DELETE message_url(:id => 1) - # def destroy - # # delete a specific message - # end - # end - # - # Along with the routes themselves, +resources+ generates named routes for use in - # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers: - # - # Named Route Helpers - # ============ ===================================================== - # messages messages_url, hash_for_messages_url, - # messages_path, hash_for_messages_path - # - # message message_url(id), hash_for_message_url(id), - # message_path(id), hash_for_message_path(id) - # - # new_message new_message_url, hash_for_new_message_url, - # new_message_path, hash_for_new_message_path - # - # edit_message edit_message_url(id), hash_for_edit_message_url(id), - # edit_message_path(id), hash_for_edit_message_path(id) - # - # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example: - # - # redirect_to :controller => 'messages', :action => 'index' - # # and - # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %> - # - # now become: - # - # redirect_to messages_url - # # and - # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically - # - # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your - # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object: - # - # <%= form_tag message_path(@message), :method => :put %> - # - # or - # - # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %> - # - # or - # - # <% form_for @message do |f| %> - # - # which takes into account whether <tt>@message</tt> is a new record or not and generates the - # path and method accordingly. - # - # The +resources+ method accepts the following options to customize the resulting routes: - # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection. - # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>, - # an array of any of the previous, or <tt>:any</tt> if the method does not matter. - # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+. - # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member. - # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action. - # * <tt>:controller</tt> - Specify the controller name for the routes. - # * <tt>:singular</tt> - Specify the singular name used in the member routes. - # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either - # regular expressions (which must match for the route to match) or extra parameters. For example: - # - # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' } - # - # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller. - # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes. - # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example: - # # products_path == '/productos' - # map.resources :products, :as => 'productos' do |product| - # # product_reviews_path(product) == '/productos/1234/comentarios' - # product.resources :product_reviews, :as => 'comentarios' - # end - # - # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current. - # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources. - # - # You may directly specify the routing association with +has_one+ and +has_many+ like: - # - # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments] - # - # This is the same as: - # - # map.resources :notes do |notes| - # notes.resource :author - # notes.resources :comments - # notes.resources :attachments - # end - # - # * <tt>:path_names</tt> - Specify different path names for the actions. For example: - # # new_products_path == '/productos/nuevo' - # # bids_product_path(1) == '/productos/1/licitacoes' - # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } - # - # You can also set default action names from an environment, like this: - # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } - # - # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables. - # - # Weblog comments usually belong to a post, so you might use +resources+ like: - # - # map.resources :articles - # map.resources :comments, :path_prefix => '/articles/:article_id' - # - # You can nest +resources+ calls to set this automatically: - # - # map.resources :articles do |article| - # article.resources :comments - # end - # - # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>. - # - # article_comments_url(@article) - # article_comment_url(@article, @comment) - # - # article_comments_url(:article_id => @article) - # article_comment_url(:article_id => @article, :id => @comment) - # - # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly: - # - # articles_comments_url(@comment.article_id, @comment) - # - # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore. - # Use this if you have named routes that may clash. - # - # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_' - # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' - # - # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource: - # - # map.resources :articles do |article| - # article.resources :comments, :name_prefix => nil - # end - # - # This will yield named \resources like so: - # - # comments_url(@article) - # comment_url(@article, @comment) - # - # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member - # (ie. those with an :id parameter) will not use the parent path prefix or name prefix. - # - # The <tt>:shallow</tt> option is inherited by any nested resource(s). - # - # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources: - # - # map.resources :users, :shallow => true do |user| - # user.resources :posts do |post| - # post.resources :comments - # end - # end - # # --> GET /users/1/posts (maps to the PostsController#index action as usual) - # # also adds the usual named route called "user_posts" - # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested) - # # also adds the named route called "post" - # # --> GET /posts/2/comments (maps to the CommentsController#index action) - # # also adds the named route called "post_comments" - # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested) - # # also adds the named route called "comment" - # - # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like: - # - # map.resources :users, :has_many => { :posts => :comments }, :shallow => true - # - # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to. - # - # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a - # list of action names. By default, routes are generated for all seven actions. - # - # For example: - # - # map.resources :posts, :only => [:index, :show] do |post| - # post.resources :comments, :except => [:update, :destroy] - # end - # # --> GET /posts (maps to the PostsController#index action) - # # --> POST /posts (fails) - # # --> GET /posts/1 (maps to the PostsController#show action) - # # --> DELETE /posts/1 (fails) - # # --> POST /posts/1/comments (maps to the CommentsController#create action) - # # --> PUT /posts/1/comments/1 (fails) - # - # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied. - # - # Examples: - # - # map.resources :messages, :path_prefix => "/thread/:thread_id" - # # --> GET /thread/7/messages/1 - # - # map.resources :messages, :collection => { :rss => :get } - # # --> GET /messages/rss (maps to the #rss action) - # # also adds a named route called "rss_messages" - # - # map.resources :messages, :member => { :mark => :post } - # # --> POST /messages/1/mark (maps to the #mark action) - # # also adds a named route called "mark_message" - # - # map.resources :messages, :new => { :preview => :post } - # # --> POST /messages/new/preview (maps to the #preview action) - # # also adds a named route called "preview_new_message" - # - # map.resources :messages, :new => { :new => :any, :preview => :post } - # # --> POST /messages/new/preview (maps to the #preview action) - # # also adds a named route called "preview_new_message" - # # --> /messages/new can be invoked via any request method - # - # map.resources :messages, :controller => "categories", - # :path_prefix => "/category/:category_id", - # :name_prefix => "category_" - # # --> GET /categories/7/messages/1 - # # has named route "category_message" - # - # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an - # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in - # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes. - def resources(*entities, &block) - options = entities.extract_options! - entities.each { |entity| map_resource(entity, options.dup, &block) } - end - - # Creates named routes for implementing verb-oriented controllers for a singleton \resource. - # A singleton \resource is global to its current context. For unnested singleton \resources, - # the \resource is global to the current user visiting the application, such as a user's - # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent - # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>. - # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>: - # - # map.resources :projects do |project| - # project.resource :project_manager - # end - # - # See +resources+ for general conventions. These are the main differences: - # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name. - # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option. - # * No default index route is created for the singleton \resource controller. - # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1') - # - # For example: - # - # map.resource :account - # - # maps these actions in the Accounts controller: - # - # class AccountsController < ActionController::Base - # # GET new_account_url - # def new - # # return an HTML form for describing the new account - # end - # - # # POST account_url - # def create - # # create an account - # end - # - # # GET account_url - # def show - # # find and return the account - # end - # - # # GET edit_account_url - # def edit - # # return an HTML form for editing the account - # end - # - # # PUT account_url - # def update - # # find and update the account - # end - # - # # DELETE account_url - # def destroy - # # delete the account - # end - # end - # - # Along with the routes themselves, +resource+ generates named routes for - # use in controllers and views. <tt>map.resource :account</tt> produces - # these named routes and helpers: - # - # Named Route Helpers - # ============ ============================================= - # account account_url, hash_for_account_url, - # account_path, hash_for_account_path - # - # new_account new_account_url, hash_for_new_account_url, - # new_account_path, hash_for_new_account_path - # - # edit_account edit_account_url, hash_for_edit_account_url, - # edit_account_path, hash_for_edit_account_path - def resource(*entities, &block) - options = entities.extract_options! - entities.each { |entity| map_singleton_resource(entity, options.dup, &block) } - end - - private - def map_resource(entities, options = {}, &block) - resource = Resource.new(entities, options) - - with_options :controller => resource.controller do |map| - map_associations(resource, options) - - if block_given? - with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) - end - - map_collection_actions(map, resource) - map_default_collection_actions(map, resource) - map_new_actions(map, resource) - map_member_actions(map, resource) - end - end - - def map_singleton_resource(entities, options = {}, &block) - resource = SingletonResource.new(entities, options) - - with_options :controller => resource.controller do |map| - map_associations(resource, options) - - if block_given? - with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) - end - - map_collection_actions(map, resource) - map_new_actions(map, resource) - map_member_actions(map, resource) - map_default_singleton_actions(map, resource) - end - end - - def map_associations(resource, options) - map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many] - - path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}" - name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" - - Array(options[:has_one]).each do |association| - resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix)) - end - end - - def map_has_many_associations(resource, associations, options) - case associations - when Hash - associations.each do |association,has_many| - map_has_many_associations(resource, association, options.merge(:has_many => has_many)) - end - when Array - associations.each do |association| - map_has_many_associations(resource, association, options) - end - when Symbol, String - resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many])) - else - end - end - - def map_collection_actions(map, resource) - resource.collection_methods.each do |method, actions| - actions.each do |action| - [method].flatten.each do |m| - action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) - action_path ||= action - - map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) - end - end - end - end - - def map_default_collection_actions(map, resource) - index_route_name = "#{resource.name_prefix}#{resource.plural}" - - if resource.uncountable? - index_route_name << "_index" - end - - map_resource_routes(map, resource, :index, resource.path, index_route_name) - map_resource_routes(map, resource, :create, resource.path, index_route_name) - end - - def map_default_singleton_actions(map, resource) - map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}") - end - - def map_new_actions(map, resource) - resource.new_methods.each do |method, actions| - actions.each do |action| - route_path = resource.new_path - route_name = "new_#{resource.name_prefix}#{resource.singular}" - - unless action == :new - route_path = "#{route_path}#{resource.action_separator}#{action}" - route_name = "#{action}_#{route_name}" - end - - map_resource_routes(map, resource, action, route_path, route_name, method) - end - end - end - - def map_member_actions(map, resource) - resource.member_methods.each do |method, actions| - actions.each do |action| - [method].flatten.each do |m| - action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) - action_path ||= Base.resources_path_names[action] || action - - map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) - end - end - end - - route_path = "#{resource.shallow_name_prefix}#{resource.singular}" - map_resource_routes(map, resource, :show, resource.member_path, route_path) - map_resource_routes(map, resource, :update, resource.member_path, route_path) - map_resource_routes(map, resource, :destroy, resource.member_path, route_path) - end - - def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) - if resource.has_action?(action) - action_options = action_options_for(action, resource, method, resource_options) - formatted_route_path = "#{route_path}.:format" - - if route_name && @set.named_routes[route_name.to_sym].nil? - map.named_route(route_name, formatted_route_path, action_options) - else - map.connect(formatted_route_path, action_options) - end - end - end - - def add_conditions_for(conditions, method) - returning({:conditions => conditions.dup}) do |options| - options[:conditions][:method] = method unless method == :any - end - end - - def action_options_for(action, resource, method = nil, resource_options = {}) - default_options = { :action => action.to_s } - require_id = !resource.kind_of?(SingletonResource) - force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) - - case default_options[:action] - when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) - when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements) - when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) - when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) - when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) - else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/route.rb b/actionpack/lib/action_controller/routing/route.rb deleted file mode 100644 index eba05a3c5a..0000000000 --- a/actionpack/lib/action_controller/routing/route.rb +++ /dev/null @@ -1,267 +0,0 @@ -require 'active_support/core_ext/object/misc' - -module ActionController - module Routing - class Route #:nodoc: - attr_accessor :segments, :requirements, :conditions, :optimise - - def initialize(segments = [], requirements = {}, conditions = {}) - @segments = segments - @requirements = requirements - @conditions = conditions - - if !significant_keys.include?(:action) && !requirements[:action] - @requirements[:action] = "index" - @significant_keys << :action - end - - # Routes cannot use the current string interpolation method - # if there are user-supplied <tt>:requirements</tt> as the interpolation - # code won't raise RoutingErrors when generating - has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp } - if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION - @optimise = false - else - @optimise = true - end - end - - # Indicates whether the routes should be optimised with the string interpolation - # version of the named routes methods. - def optimise? - @optimise && ActionController::Base::optimise_named_routes - end - - def segment_keys - segments.collect do |segment| - segment.key if segment.respond_to? :key - end.compact - end - - def required_segment_keys - required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) } - required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact - end - - # Build a query string from the keys of the given hash. If +only_keys+ - # is given (as an array), only the keys indicated will be used to build - # the query string. The query string will correctly build array parameter - # values. - def build_query_string(hash, only_keys = nil) - elements = [] - - (only_keys || hash.keys).each do |key| - if value = hash[key] - elements << value.to_query(key) - end - end - - elements.empty? ? '' : "?#{elements.sort * '&'}" - end - - # A route's parameter shell contains parameter values that are not in the - # route's path, but should be placed in the recognized hash. - # - # For example, +{:controller => 'pages', :action => 'show'} is the shell for the route: - # - # map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ - # - def parameter_shell - @parameter_shell ||= {}.tap do |shell| - requirements.each do |key, requirement| - shell[key] = requirement unless requirement.is_a? Regexp - end - end - end - - # Return an array containing all the keys that are used in this route. This - # includes keys that appear inside the path, and keys that have requirements - # placed upon them. - def significant_keys - @significant_keys ||= [].tap do |sk| - segments.each { |segment| sk << segment.key if segment.respond_to? :key } - sk.concat requirements.keys - sk.uniq! - end - end - - # Return a hash of key/value pairs representing the keys in the route that - # have defaults, or which are specified by non-regexp requirements. - def defaults - @defaults ||= {}.tap do |hash| - segments.each do |segment| - next unless segment.respond_to? :default - hash[segment.key] = segment.default unless segment.default.nil? - end - requirements.each do |key,req| - next if Regexp === req || req.nil? - hash[key] = req - end - end - end - - def matches_controller_and_action?(controller, action) - prepare_matching! - (@controller_requirement.nil? || @controller_requirement === controller) && - (@action_requirement.nil? || @action_requirement === action) - end - - def to_s - @to_s ||= begin - segs = segments.inject("") { |str,s| str << s.to_s } - "%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect] - end - end - - # TODO: Route should be prepared and frozen on initialize - def freeze - unless frozen? - write_generation! - write_recognition! - prepare_matching! - - parameter_shell - significant_keys - defaults - to_s - end - - super - end - - def generate(options, hash, expire_on = {}) - path, hash = generate_raw(options, hash, expire_on) - append_query_string(path, hash, extra_keys(options)) - end - - def generate_extras(options, hash, expire_on = {}) - path, hash = generate_raw(options, hash, expire_on) - [path, extra_keys(options)] - end - - private - def requirement_for(key) - return requirements[key] if requirements.key? key - segments.each do |segment| - return segment.regexp if segment.respond_to?(:key) && segment.key == key - end - nil - end - - # Write and compile a +generate+ method for this Route. - def write_generation! - # Build the main body of the generation - body = "expired = false\n#{generation_extraction}\n#{generation_structure}" - - # If we have conditions that must be tested first, nest the body inside an if - body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements - args = "options, hash, expire_on = {}" - - # Nest the body inside of a def block, and then compile it. - raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - - # expire_on.keys == recall.keys; in other words, the keys in the expire_on hash - # are the same as the keys that were recalled from the previous request. Thus, - # we can use the expire_on.keys to determine which keys ought to be used to build - # the query string. (Never use keys from the recalled request when building the - # query string.) - - raw_method - end - - # Build several lines of code that extract values from the options hash. If any - # of the values are missing or rejected then a return will be executed. - def generation_extraction - segments.collect do |segment| - segment.extraction_code - end.compact * "\n" - end - - # Produce a condition expression that will check the requirements of this route - # upon generation. - def generation_requirements - requirement_conditions = requirements.collect do |key, req| - if req.is_a? Regexp - value_regexp = Regexp.new "\\A#{req.to_s}\\Z" - "hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]" - else - "hash[:#{key}] == #{req.inspect}" - end - end - requirement_conditions * ' && ' unless requirement_conditions.empty? - end - - def generation_structure - segments.last.string_structure segments[0..-2] - end - - # Write and compile a +recognize+ method for this Route. - def write_recognition! - # Create an if structure to extract the params from a match if it occurs. - body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams" - body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend" - - # Build the method declaration and compile it - method_decl = "def recognize(path, env = {})\n#{body}\nend" - instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})" - method_decl - end - - # Plugins may override this method to add other conditions, like checks on - # host, subdomain, and so forth. Note that changes here only affect route - # recognition, not generation. - def recognition_conditions - result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"] - result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method] - result - end - - # Build the regular expression pattern that will match this route. - def recognition_pattern(wrap = true) - pattern = '' - segments.reverse_each do |segment| - pattern = segment.build_pattern pattern - end - wrap ? ("\\A" + pattern + "\\Z") : pattern - end - - # Write the code to extract the parameters from a matched route. - def recognition_extraction - next_capture = 1 - extraction = segments.collect do |segment| - x = segment.match_extraction(next_capture) - next_capture += segment.number_of_captures - x - end - extraction.compact - end - - # Generate the query string with any extra keys in the hash and append - # it to the given path, returning the new path. - def append_query_string(path, hash, query_keys = nil) - return nil unless path - query_keys ||= extra_keys(hash) - "#{path}#{build_query_string(hash, query_keys)}" - end - - # Determine which keys in the given hash are "extra". Extra keys are - # those that were not used to generate a particular route. The extra - # keys also do not include those recalled from the prior request, nor - # do they include any keys that were implied in the route (like a - # <tt>:controller</tt> that is required, but not explicitly used in the - # text of the route.) - def extra_keys(hash, recall = {}) - (hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys - end - - def prepare_matching! - unless defined? @matching_prepared - @controller_requirement = requirement_for(:controller) - @action_requirement = requirement_for(:action) - @matching_prepared = true - end - end - end - end -end diff --git a/actionpack/lib/action_controller/routing/routing_ext.rb b/actionpack/lib/action_controller/routing/routing_ext.rb deleted file mode 100644 index 5e5b22b6c2..0000000000 --- a/actionpack/lib/action_controller/routing/routing_ext.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/boolean/conversions' -require 'active_support/core_ext/nil/conversions' -require 'active_support/core_ext/regexp' diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb deleted file mode 100644 index 2603855476..0000000000 --- a/actionpack/lib/action_controller/routing/segments.rb +++ /dev/null @@ -1,343 +0,0 @@ -module ActionController - module Routing - class Segment #:nodoc: - RESERVED_PCHAR = ':@&=+$,;%' - SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" - if RUBY_VERSION >= '1.9' - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze - else - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze - end - - # TODO: Convert :is_optional accessor to read only - attr_accessor :is_optional - alias_method :optional?, :is_optional - - def initialize - @is_optional = false - end - - def number_of_captures - Regexp.new(regexp_chunk).number_of_captures - end - - def extraction_code - nil - end - - # Continue generating string for the prior segments. - def continue_string_structure(prior_segments) - if prior_segments.empty? - interpolation_statement(prior_segments) - else - new_priors = prior_segments[0..-2] - prior_segments.last.string_structure(new_priors) - end - end - - def interpolation_chunk - URI.escape(value, UNSAFE_PCHAR) - end - - # Return a string interpolation statement for this segment and those before it. - def interpolation_statement(prior_segments) - chunks = prior_segments.collect { |s| s.interpolation_chunk } - chunks << interpolation_chunk - "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}" - end - - def string_structure(prior_segments) - optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments) - end - - # Return an if condition that is true if all the prior segments can be generated. - # If there are no optional segments before this one, then nil is returned. - def all_optionals_available_condition(prior_segments) - optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact - optional_locals.empty? ? nil : " if #{optional_locals * ' && '}" - end - - # Recognition - - def match_extraction(next_capture) - nil - end - - # Warning - - # Returns true if this segment is optional? because of a default. If so, then - # no warning will be emitted regarding this segment. - def optionality_implied? - false - end - end - - class StaticSegment < Segment #:nodoc: - attr_reader :value, :raw - alias_method :raw?, :raw - - def initialize(value = nil, options = {}) - super() - @value = value - @raw = options[:raw] if options.key?(:raw) - @is_optional = options[:optional] if options.key?(:optional) - end - - def interpolation_chunk - raw? ? value : super - end - - def regexp_chunk - chunk = Regexp.escape(value) - optional? ? Regexp.optionalize(chunk) : chunk - end - - def number_of_captures - 0 - end - - def build_pattern(pattern) - escaped = Regexp.escape(value) - if optional? && ! pattern.empty? - "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})" - elsif optional? - Regexp.optionalize escaped - else - escaped + pattern - end - end - - def to_s - value - end - end - - class DividerSegment < StaticSegment #:nodoc: - def initialize(value = nil, options = {}) - super(value, {:raw => true, :optional => true}.merge(options)) - end - - def optionality_implied? - true - end - end - - class DynamicSegment < Segment #:nodoc: - attr_reader :key - - # TODO: Convert these accessors to read only - attr_accessor :default, :regexp - - def initialize(key = nil, options = {}) - super() - @key = key - @default = options[:default] if options.key?(:default) - @regexp = options[:regexp] if options.key?(:regexp) - @is_optional = true if options[:optional] || options.key?(:default) - end - - def to_s - ":#{key}" - end - - # The local variable name that the value of this segment will be extracted to. - def local_name - "#{key}_value" - end - - def extract_value - "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}" - end - - def value_check - if default # Then we know it won't be nil - "#{value_regexp.inspect} =~ #{local_name}" if regexp - elsif optional? - # If we have a regexp check that the value is not given, or that it matches. - # If we have no regexp, return nil since we do not require a condition. - "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp - else # Then it must be present, and if we have a regexp, it must match too. - "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}" - end - end - - def expiry_statement - "expired, hash = true, options if !expired && expire_on[:#{key}]" - end - - def extraction_code - s = extract_value - vc = value_check - s << "\nreturn [nil,nil] unless #{vc}" if vc - s << "\n#{expiry_statement}" - end - - def interpolation_chunk(value_code = local_name) - "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}" - end - - def string_structure(prior_segments) - if optional? # We have a conditional to do... - # If we should not appear in the url, just write the code for the prior - # segments. This occurs if our value is the default value, or, if we are - # optional, if we have nil as our value. - "if #{local_name} == #{default.inspect}\n" + - continue_string_structure(prior_segments) + - "\nelse\n" + # Otherwise, write the code up to here - "#{interpolation_statement(prior_segments)}\nend" - else - interpolation_statement(prior_segments) - end - end - - def value_regexp - Regexp.new "\\A#{regexp.to_s}\\Z" if regexp - end - - def regexp_chunk - regexp ? regexp_string : default_regexp_chunk - end - - def regexp_string - regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})" - end - - def default_regexp_chunk - "([^#{Routing::SEPARATORS.join}]+)" - end - - def number_of_captures - regexp ? regexp.number_of_captures + 1 : 1 - end - - def build_pattern(pattern) - pattern = "#{regexp_chunk}#{pattern}" - optional? ? Regexp.optionalize(pattern) : pattern - end - - def match_extraction(next_capture) - # All non code-related keys (such as :id, :slug) are URI-unescaped as - # path parameters. - default_value = default ? default.inspect : nil - %[ - value = if (m = match[#{next_capture}]) - URI.unescape(m) - else - #{default_value} - end - params[:#{key}] = value if value - ] - end - - def optionality_implied? - [:action, :id].include? key - end - - def regexp_has_modifiers? - regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0 - end - end - - class ControllerSegment < DynamicSegment #:nodoc: - def regexp_chunk - possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name } - "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))" - end - - # Don't URI.escape the controller name since it may contain slashes. - def interpolation_chunk(value_code = local_name) - "\#{#{value_code}.to_s}" - end - - # Make sure controller names like Admin/Content are correctly normalized to - # admin/content - def extract_value - "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase" - end - - def match_extraction(next_capture) - if default - "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'" - else - "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]" - end - end - end - - class PathSegment < DynamicSegment #:nodoc: - def interpolation_chunk(value_code = local_name) - "\#{#{value_code}}" - end - - def extract_value - "#{local_name} = hash[:#{key}] && Array(hash[:#{key}]).collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}" - end - - def default - '' - end - - def default=(path) - raise RoutingError, "paths cannot have non-empty default values" unless path.blank? - end - - def match_extraction(next_capture) - "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}" - end - - def default_regexp_chunk - "(.*)" - end - - def number_of_captures - regexp ? regexp.number_of_captures : 1 - end - - def optionality_implied? - true - end - - class Result < ::Array #:nodoc: - def to_s() join '/' end - def self.new_escaped(strings) - new strings.collect {|str| URI.unescape str} - end - end - end - - # The OptionalFormatSegment allows for any resource route to have an optional - # :format, which decreases the amount of routes created by 50%. - class OptionalFormatSegment < DynamicSegment - - def initialize(key = nil, options = {}) - super(:format, {:optional => true}.merge(options)) - end - - def interpolation_chunk - "." + super - end - - def regexp_chunk - '/|(\.[^/?\.]+)?' - end - - def to_s - '(.:format)?' - end - - def extract_value - "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase" - end - - #the value should not include the period (.) - def match_extraction(next_capture) - %[ - if (m = match[#{next_capture}]) - params[:#{key}] = URI.unescape(m.from(1)) - end - ] - end - end - - end -end diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index bbc7f3c8f9..323cce6a2f 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -35,7 +35,7 @@ module ActionController #:nodoc: end def cookies - @response.cookies + @request.cookies.merge(@response.cookies) end def redirect_to_url diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb index 178e3477a6..01a55fe930 100644 --- a/actionpack/lib/action_controller/testing/test_case.rb +++ b/actionpack/lib/action_controller/testing/test_case.rb @@ -10,6 +10,13 @@ module ActionController self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) end + class Result < ::Array #:nodoc: + def to_s() join '/' end + def self.new_escaped(strings) + new strings.collect {|str| URI.unescape str} + end + end + def assign_parameters(controller_path, action, parameters = {}) parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) extra_keys = ActionController::Routing::Routes.extra_keys(parameters) @@ -18,7 +25,7 @@ module ActionController if value.is_a? Fixnum value = value.to_s elsif value.is_a? Array - value = ActionController::Routing::PathSegment::Result.new(value) + value = Result.new(value) end if extra_keys.include?(key.to_sym) diff --git a/actionpack/lib/action_controller/translation.rb b/actionpack/lib/action_controller/translation.rb index 9bb63cdb15..65e9eddb0a 100644 --- a/actionpack/lib/action_controller/translation.rb +++ b/actionpack/lib/action_controller/translation.rb @@ -1,12 +1,12 @@ module ActionController module Translation def translate(*args) - I18n.translate *args + I18n.translate(*args) end alias :t :translate def localize(*args) - I18n.localize *args + I18n.localize(*args) end alias :l :localize end diff --git a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 52b66c9303..52b66c9303 100644 --- a/actionpack/lib/action_controller/routing/generation/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 11cd812695..259814a322 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -41,6 +41,8 @@ module ActionDispatch autoload :Static, 'action_dispatch/middleware/static' autoload :StringCoercion, 'action_dispatch/middleware/string_coercion' + autoload :Routing, 'action_dispatch/routing' + autoload :Assertions, 'action_dispatch/testing/assertions' autoload :Integration, 'action_dispatch/testing/integration' autoload :IntegrationTest, 'action_dispatch/testing/integration' diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index e85823d8db..c30897b32a 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -24,6 +24,7 @@ module Mime LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? } def self.[](type) + return type if type.is_a?(Type) Type.lookup_by_extension(type.to_s) end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index bff030f0e4..6a52854961 100755 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -5,7 +5,7 @@ require 'strscan' require 'active_support/memoizable' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/object/tap' +require 'active_support/core_ext/string/access' module ActionDispatch class Request < Rack::Request @@ -97,6 +97,10 @@ module ActionDispatch end end + def forgery_whitelisted? + method == :get || xhr? || content_type.nil? || !content_type.verify_request? + end + def media_type content_type.to_s end @@ -136,19 +140,16 @@ module ActionDispatch # If-Modified-Since and If-None-Match conditions. If both headers are # supplied, both must match, or the request is not considered fresh. def fresh?(response) - case - when if_modified_since && if_none_match - not_modified?(response.last_modified) && etag_matches?(response.etag) - when if_modified_since - not_modified?(response.last_modified) - when if_none_match - etag_matches?(response.etag) - else - false - end - end + last_modified = if_modified_since + etag = if_none_match - ONLY_ALL = [Mime::ALL].freeze + return false unless last_modified || etag + + success = true + success &&= not_modified?(response.last_modified) if last_modified + success &&= etag_matches?(response.etag) if etag + success + end # Returns the Mime type for the \format used in the request. # @@ -165,7 +166,7 @@ module ActionDispatch @env["action_dispatch.request.formats"] ||= if parameters[:format] - [Mime[parameters[:format]]] + Array.wrap(Mime[parameters[:format]]) elsif xhr? || (accept && !accept.include?(?,)) accepts else @@ -204,10 +205,6 @@ module ActionDispatch end end - def cache_format - parameters[:format] - end - # Returns true if the request's "X-Requested-With" header contains # "XMLHttpRequest". (The Prototype Javascript library sends this header with # every Ajax request.) @@ -492,7 +489,7 @@ EOM def self.extended(object) object.class_eval do attr_accessor :original_path, :content_type - alias_method :local_path, :path + alias_method :local_path, :path if method_defined?(:path) end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 3e3b473178..b3ed7c9d1a 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -270,6 +270,8 @@ module ActionDispatch # :nodoc: if control.empty? headers["Cache-Control"] = DEFAULT_CACHE_CONTROL + elsif @cache_control[:no_cache] + headers["Cache-Control"] = "no-cache" else extras = control[:extras] max_age = control[:max_age] diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 56d6da1706..49bc20f11f 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,6 +1,6 @@ module ActionDispatch class Callbacks - include ActiveSupport::NewCallbacks + include ActiveSupport::Callbacks define_callbacks :call, :terminator => "result == false", :rescuable => true define_callbacks :prepare, :scope => :name @@ -37,12 +37,12 @@ module ActionDispatch def initialize(app, prepare_each_request = false) @app, @prepare_each_request = app, prepare_each_request - _run_prepare_callbacks + run_callbacks(:prepare) end def call(env) - _run_call_callbacks do - _run_prepare_callbacks if @prepare_each_request + run_callbacks(:call) do + run_callbacks(:prepare) if @prepare_each_request @app.call(env) end end diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 4f71ea6165..3b27309f58 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -1,3 +1,5 @@ +require "active_support/inflector/methods" + module ActionDispatch class MiddlewareStack < Array class Middleware @@ -32,7 +34,7 @@ module ActionDispatch elsif @klass.respond_to?(:call) @klass.call else - @klass.to_s.constantize + ActiveSupport::Inflector.constantize(@klass.to_s) end end @@ -53,7 +55,7 @@ module ActionDispatch when Class klass == middleware else - klass == middleware.to_s.constantize + klass == ActiveSupport::Inflector.constantize(middleware.to_s) end end diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 5b9ded83dd..68ed1e3340 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,18 +1,7 @@ -require 'cgi' -require 'uri' -require 'set' +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/regexp' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/module/attribute_accessors' -require 'action_controller/routing/optimisations' -require 'action_controller/routing/routing_ext' -require 'action_controller/routing/route' -require 'action_controller/routing/segments' -require 'action_controller/routing/builder' -require 'action_controller/routing/route_set' -require 'action_controller/routing/recognition_optimisation' - -module ActionController +module ActionDispatch # == Routing # # The routing module provides URL rewriting in native Ruby. It's a way to @@ -197,7 +186,7 @@ module ActionController # # map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?' # - # will glob all remaining parts of the route that were not recognized earlier. + # will glob all remaining parts of the route that were not recognized earlier. # The globbed values are in <tt>params[:path]</tt> as an array of path segments. # # == Route conditions @@ -269,6 +258,11 @@ module ActionController # Run <tt>rake routes</tt>. # module Routing + autoload :DeprecatedMapper, 'action_dispatch/routing/deprecated_mapper' + autoload :Mapper, 'action_dispatch/routing/mapper' + autoload :Route, 'action_dispatch/routing/route' + autoload :RouteSet, 'action_dispatch/routing/route_set' + SEPARATORS = %w( / . ? ) HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] @@ -281,7 +275,7 @@ module ActionController # A helper module to hold URL related helpers. module Helpers - include PolymorphicRoutes + include ActionController::PolymorphicRoutes end class << self @@ -373,13 +367,11 @@ module ActionController end end - Routes = RouteSet.new - ActiveSupport::Inflector.module_eval do # Ensures that routes are reloaded when Rails inflections are updated. def inflections_with_route_reloading(&block) returning(inflections_without_route_reloading(&block)) { - ActionController::Routing::Routes.reload! if block_given? + ActionDispatch::Routing::Routes.reload! if block_given? } end diff --git a/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb new file mode 100644 index 0000000000..0564ba9797 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/deprecated_mapper.rb @@ -0,0 +1,878 @@ +module ActionDispatch + module Routing + # Mapper instances are used to build routes. The object passed to the draw + # block in config/routes.rb is a Mapper instance. + # + # Mapper instances have relatively few instance methods, in order to avoid + # clashes with named routes. + # + # == Overview + # + # ActionController::Resources are a way of defining RESTful \resources. A RESTful \resource, in basic terms, + # is something that can be pointed at and it will respond with a representation of the data requested. + # In real terms this could mean a user with a browser requests an HTML page, or that a desktop application + # requests XML data. + # + # RESTful design is based on the assumption that there are four generic verbs that a user of an + # application can request from a \resource (the noun). + # + # \Resources can be requested using four basic HTTP verbs (GET, POST, PUT, DELETE), the method used + # denotes the type of action that should take place. + # + # === The Different Methods and their Usage + # + # * GET - Requests for a \resource, no saving or editing of a \resource should occur in a GET request. + # * POST - Creation of \resources. + # * PUT - Editing of attributes on a \resource. + # * DELETE - Deletion of a \resource. + # + # === Examples + # + # # A GET request on the Posts resource is asking for all Posts + # GET /posts + # + # # A GET request on a single Post resource is asking for that particular Post + # GET /posts/1 + # + # # A POST request on the Posts resource is asking for a Post to be created with the supplied details + # POST /posts # with => { :post => { :title => "My Whizzy New Post", :body => "I've got a brand new combine harvester" } } + # + # # A PUT request on a single Post resource is asking for a Post to be updated + # PUT /posts # with => { :id => 1, :post => { :title => "Changed Whizzy Title" } } + # + # # A DELETE request on a single Post resource is asking for it to be deleted + # DELETE /posts # with => { :id => 1 } + # + # By using the REST convention, users of our application can assume certain things about how the data + # is requested and how it is returned. Rails simplifies the routing part of RESTful design by + # supplying you with methods to create them in your routes.rb file. + # + # Read more about REST at http://en.wikipedia.org/wiki/Representational_State_Transfer + class DeprecatedMapper #:doc: + def initialize(set) #:nodoc: + @set = set + end + + # Create an unnamed route with the provided +path+ and +options+. See + # ActionDispatch::Routing for an introduction to routes. + def connect(path, options = {}) + options = options.dup + + if conditions = options.delete(:conditions) + conditions = conditions.dup + method = [conditions.delete(:method)].flatten.compact + method.map! { |m| + m = m.to_s.upcase + + if m == "HEAD" + raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers" + end + + unless HTTP_METHODS.include?(m.downcase.to_sym) + raise ArgumentError, "Invalid HTTP method specified in route conditions" + end + + m + } + + if method.length > 1 + method = Regexp.union(*method) + elsif method.length == 1 + method = method.first + else + method = nil + end + end + + path_prefix = options.delete(:path_prefix) + name_prefix = options.delete(:name_prefix) + namespace = options.delete(:namespace) + + name = options.delete(:_name) + name = "#{name_prefix}#{name}" if name_prefix + + requirements = options.delete(:requirements) || {} + defaults = options.delete(:defaults) || {} + options.each do |k, v| + if v.is_a?(Regexp) + if value = options.delete(k) + requirements[k.to_sym] = value + end + else + value = options.delete(k) + defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param + end + end + + requirements.each do |_, requirement| + if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z} + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + if requirement.multiline? + raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + end + end + + possible_names = Routing.possible_controllers.collect { |n| Regexp.escape(n) } + requirements[:controller] ||= Regexp.union(*possible_names) + + if defaults[:controller] + defaults[:action] ||= 'index' + defaults[:controller] = defaults[:controller].to_s + defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace + end + + if defaults[:action] + defaults[:action] = defaults[:action].to_s + end + + if path.is_a?(String) + path = "#{path_prefix}/#{path}" if path_prefix + path = path.gsub('.:format', '(.:format)') + path = optionalize_trailing_dynamic_segments(path, requirements, defaults) + glob = $1.to_sym if path =~ /\/\*(\w+)$/ + path = ::Rack::Mount::Utils.normalize_path(path) + + if glob && !defaults[glob].blank? + raise ActionController::RoutingError, "paths cannot have non-empty default values" + end + end + + app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob) + + conditions = {} + conditions[:request_method] = method if method + conditions[:path_info] = path if path + + @set.add_route(app, conditions, requirements, defaults, name) + end + + def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc: + path = (path =~ /^\//) ? path.dup : "/#{path}" + optional, segments = true, [] + + required_segments = requirements.keys + required_segments -= defaults.keys.compact + + old_segments = path.split('/') + old_segments.shift + length = old_segments.length + + old_segments.reverse.each_with_index do |segment, index| + required_segments.each do |required| + if segment =~ /#{required}/ + optional = false + break + end + end + + if optional + if segment == ":id" && segments.include?(":action") + optional = false + elsif segment == ":controller" || segment == ":action" || segment == ":id" + # Ignore + elsif !(segment =~ /^:\w+$/) && + !(segment =~ /^:\w+\(\.:format\)$/) + optional = false + elsif segment =~ /^:(\w+)$/ + if defaults.has_key?($1.to_sym) + defaults.delete($1.to_sym) + else + optional = false + end + end + end + + if optional && index < length - 1 + segments.unshift('(/', segment) + segments.push(')') + elsif optional + segments.unshift('/(', segment) + segments.push(')') + else + segments.unshift('/', segment) + end + end + + segments.join + end + private :optionalize_trailing_dynamic_segments + + # Creates a named route called "root" for matching the root level request. + def root(options = {}) + if options.is_a?(Symbol) + if source_route = @set.named_routes.routes[options] + options = source_route.defaults.merge({ :conditions => source_route.conditions }) + end + end + named_route("root", '', options) + end + + def named_route(name, path, options = {}) #:nodoc: + options[:_name] = name + connect(path, options) + end + + # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. + # Example: + # + # map.namespace(:admin) do |admin| + # admin.resources :products, + # :has_many => [ :tags, :images, :variants ] + # end + # + # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. + # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for + # Admin::TagsController. + def namespace(name, options = {}, &block) + if options[:namespace] + with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) + else + with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) + end + end + + def method_missing(route_name, *args, &proc) #:nodoc: + super unless args.length >= 1 && proc.nil? + named_route(route_name, *args) + end + + INHERITABLE_OPTIONS = :namespace, :shallow + + class Resource #:nodoc: + DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy + + attr_reader :collection_methods, :member_methods, :new_methods + attr_reader :path_prefix, :name_prefix, :path_segment + attr_reader :plural, :singular + attr_reader :options + + def initialize(entities, options) + @plural ||= entities + @singular ||= options[:singular] || plural.to_s.singularize + @path_segment = options.delete(:as) || @plural + + @options = options + + arrange_actions + add_default_actions + set_allowed_actions + set_prefixes + end + + def controller + @controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}" + end + + def requirements(with_id = false) + @requirements ||= @options[:requirements] || {} + @id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ } + + with_id ? @requirements.merge(@id_requirement) : @requirements + end + + def conditions + @conditions ||= @options[:conditions] || {} + end + + def path + @path ||= "#{path_prefix}/#{path_segment}" + end + + def new_path + new_action = self.options[:path_names][:new] if self.options[:path_names] + new_action ||= ActionController::Base.resources_path_names[:new] + @new_path ||= "#{path}/#{new_action}" + end + + def shallow_path_prefix + @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix + end + + def member_path + @member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id" + end + + def nesting_path_prefix + @nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id" + end + + def shallow_name_prefix + @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix + end + + def nesting_name_prefix + "#{shallow_name_prefix}#{singular}_" + end + + def action_separator + @action_separator ||= ActionController::Base.resource_action_separator + end + + def uncountable? + @singular.to_s == @plural.to_s + end + + def has_action?(action) + !DEFAULT_ACTIONS.include?(action) || action_allowed?(action) + end + + protected + def arrange_actions + @collection_methods = arrange_actions_by_methods(options.delete(:collection)) + @member_methods = arrange_actions_by_methods(options.delete(:member)) + @new_methods = arrange_actions_by_methods(options.delete(:new)) + end + + def add_default_actions + add_default_action(member_methods, :get, :edit) + add_default_action(new_methods, :get, :new) + end + + def set_allowed_actions + only, except = @options.values_at(:only, :except) + @allowed_actions ||= {} + + if only == :all || except == :none + only = nil + except = [] + elsif only == :none || except == :all + only = [] + except = nil + end + + if only + @allowed_actions[:only] = Array(only).map {|a| a.to_sym } + elsif except + @allowed_actions[:except] = Array(except).map {|a| a.to_sym } + end + end + + def action_allowed?(action) + only, except = @allowed_actions.values_at(:only, :except) + (!only || only.include?(action)) && (!except || !except.include?(action)) + end + + def set_prefixes + @path_prefix = options.delete(:path_prefix) + @name_prefix = options.delete(:name_prefix) + end + + def arrange_actions_by_methods(actions) + (actions || {}).inject({}) do |flipped_hash, (key, value)| + (flipped_hash[value] ||= []) << key + flipped_hash + end + end + + def add_default_action(collection, method, action) + (collection[method] ||= []).unshift(action) + end + end + + class SingletonResource < Resource #:nodoc: + def initialize(entity, options) + @singular = @plural = entity + options[:controller] ||= @singular.to_s.pluralize + super + end + + alias_method :shallow_path_prefix, :path_prefix + alias_method :shallow_name_prefix, :name_prefix + alias_method :member_path, :path + alias_method :nesting_path_prefix, :path + end + + # Creates named routes for implementing verb-oriented controllers + # for a collection \resource. + # + # For example: + # + # map.resources :messages + # + # will map the following actions in the corresponding controller: + # + # class MessagesController < ActionController::Base + # # GET messages_url + # def index + # # return all messages + # end + # + # # GET new_message_url + # def new + # # return an HTML form for describing a new message + # end + # + # # POST messages_url + # def create + # # create a new message + # end + # + # # GET message_url(:id => 1) + # def show + # # find and return a specific message + # end + # + # # GET edit_message_url(:id => 1) + # def edit + # # return an HTML form for editing a specific message + # end + # + # # PUT message_url(:id => 1) + # def update + # # find and update a specific message + # end + # + # # DELETE message_url(:id => 1) + # def destroy + # # delete a specific message + # end + # end + # + # Along with the routes themselves, +resources+ generates named routes for use in + # controllers and views. <tt>map.resources :messages</tt> produces the following named routes and helpers: + # + # Named Route Helpers + # ============ ===================================================== + # messages messages_url, hash_for_messages_url, + # messages_path, hash_for_messages_path + # + # message message_url(id), hash_for_message_url(id), + # message_path(id), hash_for_message_path(id) + # + # new_message new_message_url, hash_for_new_message_url, + # new_message_path, hash_for_new_message_path + # + # edit_message edit_message_url(id), hash_for_edit_message_url(id), + # edit_message_path(id), hash_for_edit_message_path(id) + # + # You can use these helpers instead of +url_for+ or methods that take +url_for+ parameters. For example: + # + # redirect_to :controller => 'messages', :action => 'index' + # # and + # <%= link_to "edit this message", :controller => 'messages', :action => 'edit', :id => @message.id %> + # + # now become: + # + # redirect_to messages_url + # # and + # <%= link_to "edit this message", edit_message_url(@message) # calls @message.id automatically + # + # Since web browsers don't support the PUT and DELETE verbs, you will need to add a parameter '_method' to your + # form tags. The form helpers make this a little easier. For an update form with a <tt>@message</tt> object: + # + # <%= form_tag message_path(@message), :method => :put %> + # + # or + # + # <% form_for :message, @message, :url => message_path(@message), :html => {:method => :put} do |f| %> + # + # or + # + # <% form_for @message do |f| %> + # + # which takes into account whether <tt>@message</tt> is a new record or not and generates the + # path and method accordingly. + # + # The +resources+ method accepts the following options to customize the resulting routes: + # * <tt>:collection</tt> - Add named routes for other actions that operate on the collection. + # Takes a hash of <tt>#{action} => #{method}</tt>, where method is <tt>:get</tt>/<tt>:post</tt>/<tt>:put</tt>/<tt>:delete</tt>, + # an array of any of the previous, or <tt>:any</tt> if the method does not matter. + # These routes map to a URL like /messages/rss, with a route of +rss_messages_url+. + # * <tt>:member</tt> - Same as <tt>:collection</tt>, but for actions that operate on a specific member. + # * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action. + # * <tt>:controller</tt> - Specify the controller name for the routes. + # * <tt>:singular</tt> - Specify the singular name used in the member routes. + # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either + # regular expressions (which must match for the route to match) or extra parameters. For example: + # + # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' } + # + # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller. + # * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes. + # * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example: + # # products_path == '/productos' + # map.resources :products, :as => 'productos' do |product| + # # product_reviews_path(product) == '/productos/1234/comentarios' + # product.resources :product_reviews, :as => 'comentarios' + # end + # + # * <tt>:has_one</tt> - Specify nested \resources, this is a shorthand for mapping singleton \resources beneath the current. + # * <tt>:has_many</tt> - Same has <tt>:has_one</tt>, but for plural \resources. + # + # You may directly specify the routing association with +has_one+ and +has_many+ like: + # + # map.resources :notes, :has_one => :author, :has_many => [:comments, :attachments] + # + # This is the same as: + # + # map.resources :notes do |notes| + # notes.resource :author + # notes.resources :comments + # notes.resources :attachments + # end + # + # * <tt>:path_names</tt> - Specify different path names for the actions. For example: + # # new_products_path == '/productos/nuevo' + # # bids_product_path(1) == '/productos/1/licitacoes' + # map.resources :products, :as => 'productos', :member => { :bids => :get }, :path_names => { :new => 'nuevo', :bids => 'licitacoes' } + # + # You can also set default action names from an environment, like this: + # config.action_controller.resources_path_names = { :new => 'nuevo', :edit => 'editar' } + # + # * <tt>:path_prefix</tt> - Set a prefix to the routes with required route variables. + # + # Weblog comments usually belong to a post, so you might use +resources+ like: + # + # map.resources :articles + # map.resources :comments, :path_prefix => '/articles/:article_id' + # + # You can nest +resources+ calls to set this automatically: + # + # map.resources :articles do |article| + # article.resources :comments + # end + # + # The comment \resources work the same, but must now include a value for <tt>:article_id</tt>. + # + # article_comments_url(@article) + # article_comment_url(@article, @comment) + # + # article_comments_url(:article_id => @article) + # article_comment_url(:article_id => @article, :id => @comment) + # + # If you don't want to load all objects from the database you might want to use the <tt>article_id</tt> directly: + # + # articles_comments_url(@comment.article_id, @comment) + # + # * <tt>:name_prefix</tt> - Define a prefix for all generated routes, usually ending in an underscore. + # Use this if you have named routes that may clash. + # + # map.resources :tags, :path_prefix => '/books/:book_id', :name_prefix => 'book_' + # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' + # + # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested \resource: + # + # map.resources :articles do |article| + # article.resources :comments, :name_prefix => nil + # end + # + # This will yield named \resources like so: + # + # comments_url(@article) + # comment_url(@article, @comment) + # + # * <tt>:shallow</tt> - If true, paths for nested resources which reference a specific member + # (ie. those with an :id parameter) will not use the parent path prefix or name prefix. + # + # The <tt>:shallow</tt> option is inherited by any nested resource(s). + # + # For example, 'users', 'posts' and 'comments' all use shallow paths with the following nested resources: + # + # map.resources :users, :shallow => true do |user| + # user.resources :posts do |post| + # post.resources :comments + # end + # end + # # --> GET /users/1/posts (maps to the PostsController#index action as usual) + # # also adds the usual named route called "user_posts" + # # --> GET /posts/2 (maps to the PostsController#show action as if it were not nested) + # # also adds the named route called "post" + # # --> GET /posts/2/comments (maps to the CommentsController#index action) + # # also adds the named route called "post_comments" + # # --> GET /comments/2 (maps to the CommentsController#show action as if it were not nested) + # # also adds the named route called "comment" + # + # You may also use <tt>:shallow</tt> in combination with the +has_one+ and +has_many+ shorthand notations like: + # + # map.resources :users, :has_many => { :posts => :comments }, :shallow => true + # + # * <tt>:only</tt> and <tt>:except</tt> - Specify which of the seven default actions should be routed to. + # + # <tt>:only</tt> and <tt>:except</tt> may be set to <tt>:all</tt>, <tt>:none</tt>, an action name or a + # list of action names. By default, routes are generated for all seven actions. + # + # For example: + # + # map.resources :posts, :only => [:index, :show] do |post| + # post.resources :comments, :except => [:update, :destroy] + # end + # # --> GET /posts (maps to the PostsController#index action) + # # --> POST /posts (fails) + # # --> GET /posts/1 (maps to the PostsController#show action) + # # --> DELETE /posts/1 (fails) + # # --> POST /posts/1/comments (maps to the CommentsController#create action) + # # --> PUT /posts/1/comments/1 (fails) + # + # If <tt>map.resources</tt> is called with multiple resources, they all get the same options applied. + # + # Examples: + # + # map.resources :messages, :path_prefix => "/thread/:thread_id" + # # --> GET /thread/7/messages/1 + # + # map.resources :messages, :collection => { :rss => :get } + # # --> GET /messages/rss (maps to the #rss action) + # # also adds a named route called "rss_messages" + # + # map.resources :messages, :member => { :mark => :post } + # # --> POST /messages/1/mark (maps to the #mark action) + # # also adds a named route called "mark_message" + # + # map.resources :messages, :new => { :preview => :post } + # # --> POST /messages/new/preview (maps to the #preview action) + # # also adds a named route called "preview_new_message" + # + # map.resources :messages, :new => { :new => :any, :preview => :post } + # # --> POST /messages/new/preview (maps to the #preview action) + # # also adds a named route called "preview_new_message" + # # --> /messages/new can be invoked via any request method + # + # map.resources :messages, :controller => "categories", + # :path_prefix => "/category/:category_id", + # :name_prefix => "category_" + # # --> GET /categories/7/messages/1 + # # has named route "category_message" + # + # The +resources+ method sets HTTP method restrictions on the routes it generates. For example, making an + # HTTP POST on <tt>new_message_url</tt> will raise a RoutingError exception. The default route in + # <tt>config/routes.rb</tt> overrides this and allows invalid HTTP methods for \resource routes. + def resources(*entities, &block) + options = entities.extract_options! + entities.each { |entity| map_resource(entity, options.dup, &block) } + end + + # Creates named routes for implementing verb-oriented controllers for a singleton \resource. + # A singleton \resource is global to its current context. For unnested singleton \resources, + # the \resource is global to the current user visiting the application, such as a user's + # <tt>/account</tt> profile. For nested singleton \resources, the \resource is global to its parent + # \resource, such as a <tt>projects</tt> \resource that <tt>has_one :project_manager</tt>. + # The <tt>project_manager</tt> should be mapped as a singleton \resource under <tt>projects</tt>: + # + # map.resources :projects do |project| + # project.resource :project_manager + # end + # + # See +resources+ for general conventions. These are the main differences: + # * A singular name is given to <tt>map.resource</tt>. The default controller name is still taken from the plural name. + # * To specify a custom plural name, use the <tt>:plural</tt> option. There is no <tt>:singular</tt> option. + # * No default index route is created for the singleton \resource controller. + # * When nesting singleton \resources, only the singular name is used as the path prefix (example: 'account/messages/1') + # + # For example: + # + # map.resource :account + # + # maps these actions in the Accounts controller: + # + # class AccountsController < ActionController::Base + # # GET new_account_url + # def new + # # return an HTML form for describing the new account + # end + # + # # POST account_url + # def create + # # create an account + # end + # + # # GET account_url + # def show + # # find and return the account + # end + # + # # GET edit_account_url + # def edit + # # return an HTML form for editing the account + # end + # + # # PUT account_url + # def update + # # find and update the account + # end + # + # # DELETE account_url + # def destroy + # # delete the account + # end + # end + # + # Along with the routes themselves, +resource+ generates named routes for + # use in controllers and views. <tt>map.resource :account</tt> produces + # these named routes and helpers: + # + # Named Route Helpers + # ============ ============================================= + # account account_url, hash_for_account_url, + # account_path, hash_for_account_path + # + # new_account new_account_url, hash_for_new_account_url, + # new_account_path, hash_for_new_account_path + # + # edit_account edit_account_url, hash_for_edit_account_url, + # edit_account_path, hash_for_edit_account_path + def resource(*entities, &block) + options = entities.extract_options! + entities.each { |entity| map_singleton_resource(entity, options.dup, &block) } + end + + private + def map_resource(entities, options = {}, &block) + resource = Resource.new(entities, options) + + with_options :controller => resource.controller do |map| + map_associations(resource, options) + + if block_given? + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) + end + + map_collection_actions(map, resource) + map_default_collection_actions(map, resource) + map_new_actions(map, resource) + map_member_actions(map, resource) + end + end + + def map_singleton_resource(entities, options = {}, &block) + resource = SingletonResource.new(entities, options) + + with_options :controller => resource.controller do |map| + map_associations(resource, options) + + if block_given? + with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block) + end + + map_collection_actions(map, resource) + map_new_actions(map, resource) + map_member_actions(map, resource) + map_default_singleton_actions(map, resource) + end + end + + def map_associations(resource, options) + map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many] + + path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}" + name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}" + + Array(options[:has_one]).each do |association| + resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix)) + end + end + + def map_has_many_associations(resource, associations, options) + case associations + when Hash + associations.each do |association,has_many| + map_has_many_associations(resource, association, options.merge(:has_many => has_many)) + end + when Array + associations.each do |association| + map_has_many_associations(resource, association, options) + end + when Symbol, String + resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many])) + else + end + end + + def map_collection_actions(map, resource) + resource.collection_methods.each do |method, actions| + actions.each do |action| + [method].flatten.each do |m| + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= action + + map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m) + end + end + end + end + + def map_default_collection_actions(map, resource) + index_route_name = "#{resource.name_prefix}#{resource.plural}" + + if resource.uncountable? + index_route_name << "_index" + end + + map_resource_routes(map, resource, :index, resource.path, index_route_name) + map_resource_routes(map, resource, :create, resource.path, index_route_name) + end + + def map_default_singleton_actions(map, resource) + map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}") + end + + def map_new_actions(map, resource) + resource.new_methods.each do |method, actions| + actions.each do |action| + route_path = resource.new_path + route_name = "new_#{resource.name_prefix}#{resource.singular}" + + unless action == :new + route_path = "#{route_path}#{resource.action_separator}#{action}" + route_name = "#{action}_#{route_name}" + end + + map_resource_routes(map, resource, action, route_path, route_name, method) + end + end + end + + def map_member_actions(map, resource) + resource.member_methods.each do |method, actions| + actions.each do |action| + [method].flatten.each do |m| + action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) + action_path ||= ActionController::Base.resources_path_names[action] || action + + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) + end + end + end + + route_path = "#{resource.shallow_name_prefix}#{resource.singular}" + map_resource_routes(map, resource, :show, resource.member_path, route_path) + map_resource_routes(map, resource, :update, resource.member_path, route_path) + map_resource_routes(map, resource, :destroy, resource.member_path, route_path) + end + + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) + if resource.has_action?(action) + action_options = action_options_for(action, resource, method, resource_options) + formatted_route_path = "#{route_path}.:format" + + if route_name && @set.named_routes[route_name.to_sym].nil? + map.named_route(route_name, formatted_route_path, action_options) + else + map.connect(formatted_route_path, action_options) + end + end + end + + def add_conditions_for(conditions, method) + returning({:conditions => conditions.dup}) do |options| + options[:conditions][:method] = method unless method == :any + end + end + + def action_options_for(action, resource, method = nil, resource_options = {}) + default_options = { :action => action.to_s } + require_id = !resource.kind_of?(SingletonResource) + force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) + + case default_options[:action] + when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) + when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements) + when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) + when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) + when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) + else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) + end + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb new file mode 100644 index 0000000000..7d770dedd0 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -0,0 +1,327 @@ +module ActionDispatch + module Routing + class Mapper + module Resources + def resource(*resources, &block) + options = resources.last.is_a?(Hash) ? resources.pop : {} + + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resource(r, options) } + return self + end + + resource = resources.pop + + if @scope[:scope_level] == :resources + member do + resource(resource, options, &block) + end + return self + end + + singular = resource.to_s + plural = singular.pluralize + + controller(plural) do + namespace(resource) do + with_scope_level(:resource) do + yield if block_given? + + get "", :to => :show, :as => "#{singular}" + post "", :to => :create + put "", :to => :update + delete "", :to => :destroy + get "new", :to => :new, :as => "new_#{singular}" + get "edit", :to => :edit, :as => "edit_#{singular}" + end + end + end + + self + end + + def resources(*resources, &block) + options = resources.last.is_a?(Hash) ? resources.pop : {} + + if resources.length > 1 + raise ArgumentError if block_given? + resources.each { |r| resources(r, options) } + return self + end + + resource = resources.pop + + if @scope[:scope_level] == :resources + member do + resources(resource, options, &block) + end + return self + end + + plural = resource.to_s + singular = plural.singularize + + controller(resource) do + namespace(resource) do + with_scope_level(:resources) do + yield if block_given? + + member do + get "", :to => :show, :as => "#{singular}" + put "", :to => :update + delete "", :to => :destroy + get "edit", :to => :edit, :as => "edit_#{singular}" + end + + collection do + get "", :to => :index, :as => "#{plural}" + post "", :to => :create + get "new", :to => :new, :as => "new_#{singular}" + end + end + end + end + + self + end + + def collection + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use collection outside resources scope" + end + + with_scope_level(:collection) do + yield + end + end + + def member + unless @scope[:scope_level] == :resources + raise ArgumentError, "can't use member outside resources scope" + end + + with_scope_level(:member) do + scope(":id") do + yield + end + end + end + + def match(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + args.push(options) + + case options.delete(:on) + when :collection + return collection { match(*args) } + when :member + return member { match(*args) } + end + + if @scope[:scope_level] == :resources + raise ArgumentError, "can't define route directly in resources scope" + end + + super + end + + private + def with_scope_level(kind) + old, @scope[:scope_level] = @scope[:scope_level], kind + yield + ensure + @scope[:scope_level] = old + end + end + + module Scoping + def scope(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + + constraints = options.delete(:constraints) || {} + unless constraints.is_a?(Hash) + block, constraints = constraints, {} + end + constraints, @scope[:constraints] = @scope[:constraints], (@scope[:constraints] || {}).merge(constraints) + blocks, @scope[:blocks] = @scope[:blocks], (@scope[:blocks] || []) + [block] + + options, @scope[:options] = @scope[:options], (@scope[:options] || {}).merge(options) + + path_set = controller_set = false + + case args.first + when String + path_set = true + path = args.first + path, @scope[:path] = @scope[:path], "#{@scope[:path]}#{Rack::Mount::Utils.normalize_path(path)}" + when Symbol + controller_set = true + controller = args.first + controller, @scope[:controller] = @scope[:controller], controller + end + + yield + + self + ensure + @scope[:path] = path if path_set + @scope[:controller] = controller if controller_set + @scope[:options] = options + @scope[:blocks] = blocks + @scope[:constraints] = constraints + end + + def controller(controller) + scope(controller.to_sym) { yield } + end + + def namespace(path) + scope(path.to_s) { yield } + end + + def constraints(constraints = {}) + scope(:constraints => constraints) { yield } + end + end + + class Constraints + def initialize(app, constraints = []) + @app, @constraints = app, constraints + end + + def call(env) + req = Rack::Request.new(env) + + @constraints.each { |constraint| + if constraint.respond_to?(:matches?) && !constraint.matches?(req) + return [417, {}, []] + elsif constraint.respond_to?(:call) && !constraint.call(req) + return [417, {}, []] + end + } + + @app.call(env) + end + end + + def initialize(set) + @set = set + @scope = {} + + extend Scoping + extend Resources + end + + def get(*args, &block) + map_method(:get, *args, &block) + end + + def post(*args, &block) + map_method(:post, *args, &block) + end + + def put(*args, &block) + map_method(:put, *args, &block) + end + + def delete(*args, &block) + map_method(:delete, *args, &block) + end + + def root(options = {}) + match '/', options.merge(:as => :root) + end + + def match(*args) + options = args.last.is_a?(Hash) ? args.pop : {} + + if args.length > 1 + args.each { |path| match(path, options) } + return self + end + + if args.first.is_a?(Symbol) + return match(args.first.to_s, options.merge(:to => args.first.to_sym)) + end + + path = args.first + + options = (@scope[:options] || {}).merge(options) + conditions, defaults = {}, {} + + path = nil if path == "" + path = Rack::Mount::Utils.normalize_path(path) if path + path = "#{@scope[:path]}#{path}" if @scope[:path] + + raise ArgumentError, "path is required" unless path + + constraints = options[:constraints] || {} + unless constraints.is_a?(Hash) + block, constraints = constraints, {} + end + blocks = ((@scope[:blocks] || []) + [block]).compact + constraints = (@scope[:constraints] || {}).merge(constraints) + options.each { |k, v| constraints[k] = v if v.is_a?(Regexp) } + + conditions[:path_info] = path + requirements = constraints.dup + + path_regexp = Rack::Mount::Strexp.compile(path, constraints, SEPARATORS) + segment_keys = Rack::Mount::RegexpWithNamedGroups.new(path_regexp).names + constraints.reject! { |k, v| segment_keys.include?(k.to_s) } + conditions.merge!(constraints) + + if via = options[:via] + via = Array(via).map { |m| m.to_s.upcase } + conditions[:request_method] = Regexp.union(*via) + end + + defaults[:controller] = @scope[:controller].to_s if @scope[:controller] + + if options[:to].respond_to?(:call) + app = options[:to] + defaults.delete(:controller) + defaults.delete(:action) + elsif options[:to].is_a?(String) + defaults[:controller], defaults[:action] = options[:to].split('#') + elsif options[:to].is_a?(Symbol) + defaults[:action] = options[:to].to_s + end + app ||= Routing::RouteSet::Dispatcher.new(:defaults => defaults) + + if app.is_a?(Routing::RouteSet::Dispatcher) + unless defaults.include?(:controller) || segment_keys.include?("controller") + raise ArgumentError, "missing :controller" + end + unless defaults.include?(:action) || segment_keys.include?("action") + raise ArgumentError, "missing :action" + end + end + + app = Constraints.new(app, blocks) if blocks.any? + @set.add_route(app, conditions, requirements, defaults, options[:as]) + + self + end + + def redirect(path, options = {}) + status = options[:status] || 301 + lambda { |env| + req = Rack::Request.new(env) + url = req.scheme + '://' + req.host + path + [status, {'Location' => url, 'Content-Type' => 'text/html'}, ['Moved Permanently']] + } + end + + private + def map_method(method, *args, &block) + options = args.last.is_a?(Hash) ? args.pop : {} + options[:via] = method + args.push(options) + match(*args, &block) + self + end + end + end +end diff --git a/actionpack/lib/action_dispatch/routing/route.rb b/actionpack/lib/action_dispatch/routing/route.rb new file mode 100644 index 0000000000..f1431e7a37 --- /dev/null +++ b/actionpack/lib/action_dispatch/routing/route.rb @@ -0,0 +1,49 @@ +module ActionDispatch + module Routing + class Route #:nodoc: + attr_reader :app, :conditions, :defaults, :name + attr_reader :path, :requirements + + def initialize(app, conditions = {}, requirements = {}, defaults = {}, name = nil) + @app = app + @defaults = defaults + @name = name + + @requirements = requirements.merge(defaults) + @requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp) + @requirements.delete_if { |k, v| + v == Regexp.compile("[^#{SEPARATORS.join}]+") + } + + if path = conditions[:path_info] + @path = path + conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS) + end + + @conditions = conditions.inject({}) { |h, (k, v)| + h[k] = Rack::Mount::RegexpWithNamedGroups.new(v) + h + } + end + + def verb + if method = conditions[:request_method] + case method + when Regexp + method.source.upcase + else + method.to_s.upcase + end + end + end + + def segment_keys + @segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym } + end + + def to_a + [@app, @conditions, @defaults, @name] + end + end + end +end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 25fdbf480e..c28df76f3f 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,69 +1,63 @@ -module ActionController +require 'rack/mount' +require 'forwardable' + +module ActionDispatch module Routing class RouteSet #:nodoc: - # Mapper instances are used to build routes. The object passed to the draw - # block in config/routes.rb is a Mapper instance. - # - # Mapper instances have relatively few instance methods, in order to avoid - # clashes with named routes. - class Mapper #:doc: - include ActionController::Resources - - def initialize(set) #:nodoc: - @set = set - end + NotFound = lambda { |env| + raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect} with #{env.inspect}" + } + + PARAMETERS_KEY = 'action_dispatch.request.path_parameters' - # Create an unnamed route with the provided +path+ and +options+. See - # ActionController::Routing for an introduction to routes. - def connect(path, options = {}) - @set.add_route(path, options) + class Dispatcher + def initialize(options = {}) + defaults = options[:defaults] + @glob_param = options.delete(:glob) end - # Creates a named route called "root" for matching the root level request. - def root(options = {}) - if options.is_a?(Symbol) - if source_route = @set.named_routes.routes[options] - options = source_route.defaults.merge({ :conditions => source_route.conditions }) + def call(env) + params = env[PARAMETERS_KEY] + merge_default_action!(params) + split_glob_param!(params) if @glob_param + params.each do |key, value| + if value.is_a?(String) + value = value.dup.force_encoding(Encoding::BINARY) if value.respond_to?(:force_encoding) + params[key] = URI.unescape(value) end end - named_route("root", '', options) - end - - def named_route(name, path, options = {}) #:nodoc: - @set.add_named_route(name, path, options) - end - # Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model. - # Example: - # - # map.namespace(:admin) do |admin| - # admin.resources :products, - # :has_many => [ :tags, :images, :variants ] - # end - # - # This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController. - # It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for - # Admin::TagsController. - def namespace(name, options = {}, &block) - if options[:namespace] - with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block) + if env['action_controller.recognize'] + [200, {}, params] else - with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block) + controller = controller(params) + controller.action(params[:action]).call(env) end end - def method_missing(route_name, *args, &proc) #:nodoc: - super unless args.length >= 1 && proc.nil? - @set.add_named_route(route_name, *args) - end + private + def controller(params) + if params && params.has_key?(:controller) + controller = "#{params[:controller].camelize}Controller" + ActiveSupport::Inflector.constantize(controller) + end + end + + def merge_default_action!(params) + params[:action] ||= 'index' + end + + def split_glob_param!(params) + params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) } + end end + # A NamedRouteCollection instance is a collection of named routes, and also # maintains an anonymous module that can be used to install helpers for the # named routes. class NamedRouteCollection #:nodoc: include Enumerable - include ActionController::Routing::Optimisation attr_reader :routes, :helpers def initialize @@ -175,8 +169,6 @@ module ActionController named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks def #{selector}(*args) # def users_url(*args) # - #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)} - # opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first args.first || {} # args.first || {} else # else @@ -216,28 +208,18 @@ module ActionController clear! end - # Subclasses and plugins may override this method to specify a different - # RouteBuilder instance, so that other route DSL's can be created. - def builder - @builder ||= RouteBuilder.new - end - - def draw + def draw(&block) clear! - yield Mapper.new(self) + Mapper.new(self).instance_exec(DeprecatedMapper.new(self), &block) + @set.add_route(NotFound) install_helpers + @set.freeze end def clear! routes.clear named_routes.clear - - @combined_regexp = nil - @routes_by_controller = nil - - # This will force routing/recognition_optimization.rb - # to refresh optimisations. - clear_recognize_optimized! + @set = ::Rack::Mount::RouteSet.new(:parameters_key => PARAMETERS_KEY) end def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false) @@ -257,7 +239,7 @@ module ActionController def configuration_file=(path) add_configuration_file(path) end - + # Deprecated accessor def configuration_file configuration_files @@ -296,31 +278,26 @@ module ActionController def routes_changed_at routes_changed_at = nil - + configuration_files.each do |config| config_changed_at = File.stat(config).mtime if routes_changed_at.nil? || config_changed_at > routes_changed_at - routes_changed_at = config_changed_at + routes_changed_at = config_changed_at end end - + routes_changed_at end - def add_route(path, options = {}) - options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) } - route = builder.build(path, options) + def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil) + route = Route.new(app, conditions, requirements, defaults, name) + @set.add_route(*route) + named_routes[name] = route if name routes << route route end - def add_named_route(name, path, options = {}) - # TODO - is options EVER used? - name = options[:name_prefix] + name.to_s if options[:name_prefix] - named_routes[name.to_sym] = add_route(path, options) - end - def options_as_params(options) # If an explicit :controller was given, always make :action explicit # too, so that action expiry works as expected for things like @@ -356,24 +333,29 @@ module ActionController generate(options, recall, :generate_extras) end - def generate(options, recall = {}, method=:generate) - named_route_name = options.delete(:use_route) - generate_all = options.delete(:generate_all) - if named_route_name - named_route = named_routes[named_route_name] - options = named_route.parameter_shell.merge(options) - end + def generate(options, recall = {}, method = :generate) + options, recall = options.dup, recall.dup + named_route = options.delete(:use_route) options = options_as_params(options) expire_on = build_expiry(options, recall) - if options[:controller] - options[:controller] = options[:controller].to_s + recall[:action] ||= 'index' if options[:controller] || recall[:controller] + + if recall[:controller] && (!options.has_key?(:controller) || options[:controller] == recall[:controller]) + options[:controller] = recall.delete(:controller) + + if recall[:action] && (!options.has_key?(:action) || options[:action] == recall[:action]) + options[:action] = recall.delete(:action) + + if recall[:id] && (!options.has_key?(:id) || options[:id] == recall[:id]) + options[:id] = recall.delete(:id) + end + end end - # if the controller has changed, make sure it changes relative to the - # current controller module, if any. In other words, if we're currently - # on admin/get, and the new controller is 'set', the new controller - # should really be admin/set. + + options[:controller] = options[:controller].to_s if options[:controller] + if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/ old_parts = recall[:controller].split('/') new_parts = options[:controller].split('/') @@ -381,63 +363,54 @@ module ActionController options[:controller] = parts.join('/') end - # drop the leading '/' on the controller name options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/ - merged = recall.merge(options) - - if named_route - path = named_route.generate(options, merged, expire_on) - if path.nil? - raise_named_route_error(options, named_route, named_route_name) - else - return path - end - else - merged[:action] ||= 'index' - options[:action] ||= 'index' - - controller = merged[:controller] - action = merged[:action] - raise RoutingError, "Need controller and action!" unless controller && action - - if generate_all - # Used by caching to expire all paths for a resource - return routes.collect do |route| - route.__send__(method, options, merged, expire_on) - end.compact - end - - # don't use the recalled keys when determining which routes to check - routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }] - - routes.each_with_index do |route, index| - results = route.__send__(method, options, merged, expire_on) - if results && (!results.is_a?(Array) || results.first) - return results - end - end + merged = options.merge(recall) + if options.has_key?(:action) && options[:action].nil? + options.delete(:action) + recall[:action] = 'index' end - - raise RoutingError, "No route matches #{options.inspect}" - end - - # try to give a helpful error message when named route generation fails - def raise_named_route_error(options, named_route, named_route_name) - diff = named_route.requirements.diff(options) - unless diff.empty? - raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}" + recall[:action] = options.delete(:action) if options[:action] == 'index' + + path = _uri(named_route, options, recall) + if path && method == :generate_extras + uri = URI(path) + extras = uri.query ? + Rack::Utils.parse_nested_query(uri.query).keys.map { |k| k.to_sym } : + [] + [uri.path, extras] + elsif path + path else - required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) } - required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment - raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?" + raise ActionController::RoutingError, "No route matches #{options.inspect}" end + rescue Rack::Mount::RoutingError + raise ActionController::RoutingError, "No route matches #{options.inspect}" end def call(env) - request = ActionDispatch::Request.new(env) - app = Routing::Routes.recognize(request) - app.action(request.parameters[:action] || 'index').call(env) + @set.call(env) + rescue ActionController::RoutingError => e + raise e if env['action_controller.rescue_error'] == false + + method, path = env['REQUEST_METHOD'].downcase.to_sym, env['PATH_INFO'] + + # Route was not recognized. Try to find out why (maybe wrong verb). + allows = HTTP_METHODS.select { |verb| + begin + recognize_path(path, {:method => verb}, false) + rescue ActionController::RoutingError + nil + end + } + + if !HTTP_METHODS.include?(method) + raise ActionController::NotImplemented.new(*allows) + elsif !allows.empty? + raise ActionController::MethodNotAllowed.new(*allows) + else + raise e + end end def recognize(request) @@ -446,33 +419,19 @@ module ActionController "#{params[:controller].to_s.camelize}Controller".constantize end - def recognize_path(path, environment={}) - raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path." - end + def recognize_path(path, environment = {}, rescue_error = true) + method = (environment[:method] || "GET").to_s.upcase - def routes_by_controller - @routes_by_controller ||= Hash.new do |controller_hash, controller| - controller_hash[controller] = Hash.new do |action_hash, action| - action_hash[action] = Hash.new do |key_hash, keys| - key_hash[keys] = routes_for_controller_and_action_and_keys(controller, action, keys) - end - end + begin + env = Rack::MockRequest.env_for(path, {:method => method}) + rescue URI::InvalidURIError => e + raise ActionController::RoutingError, e.message end - end - - def routes_for(options, merged, expire_on) - raise "Need controller and action!" unless controller && action - controller = merged[:controller] - merged = options if expire_on[:controller] - action = merged[:action] || 'index' - routes_by_controller[controller][action][merged.keys][1] - end - - def routes_for_controller_and_action_and_keys(controller, action, keys) - routes.select do |route| - route.matches_controller_and_action? controller, action - end + env['action_controller.recognize'] = true + env['action_controller.rescue_error'] = rescue_error + status, headers, body = call(env) + body end # Subclasses and plugins may override this method to extract further attributes @@ -480,6 +439,59 @@ module ActionController def extract_request_environment(request) { :method => request.method } end + + private + def _uri(named_route, params, recall) + params = URISegment.wrap_values(params) + recall = URISegment.wrap_values(recall) + + unless result = @set.generate(:path_info, named_route, params, recall) + return + end + + uri, params = result + params.each do |k, v| + if v._value + params[k] = v._value + else + params.delete(k) + end + end + + uri << "?#{Rack::Mount::Utils.build_nested_query(params)}" if uri && params.any? + uri + end + + class URISegment < Struct.new(:_value, :_escape) + EXCLUDED = [:controller] + + def self.wrap_values(hash) + hash.inject({}) { |h, (k, v)| + h[k] = new(v, !EXCLUDED.include?(k.to_sym)) + h + } + end + + extend Forwardable + def_delegators :_value, :==, :eql?, :hash + + def to_param + @to_param ||= begin + if _value.is_a?(Array) + _value.map { |v| _escaped(v) }.join('/') + else + _escaped(_value) + end + end + end + alias_method :to_s, :to_param + + private + def _escaped(value) + v = value.respond_to?(:to_param) ? value.to_param : value + _escape ? Rack::Mount::Utils.escape_uri(v) : v.to_s + end + end end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index 96f08f2355..0e4a92048f 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -1,8 +1,21 @@ module ActionDispatch module Assertions - %w(response selector tag dom routing model).each do |kind| - require "action_dispatch/testing/assertions/#{kind}" - include const_get("#{kind.camelize}Assertions") + autoload :DomAssertions, 'action_dispatch/testing/assertions/dom' + autoload :ModelAssertions, 'action_dispatch/testing/assertions/model' + autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response' + autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing' + autoload :SelectorAssertions, 'action_dispatch/testing/assertions/selector' + autoload :TagAssertions, 'action_dispatch/testing/assertions/tag' + + extend ActiveSupport::Concern + + included do + include DomAssertions + include ModelAssertions + include ResponseAssertions + include RoutingAssertions + include SelectorAssertions + include TagAssertions end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 2c4a3a356d..40d6f97b2a 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -396,8 +396,12 @@ module ActionDispatch # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) reset! unless @integration_session - returning @integration_session.__send__(sym, *args, &block) do - copy_session_variables! + if @integration_session.respond_to?(sym) + returning @integration_session.__send__(sym, *args, &block) do + copy_session_variables! + end + else + super end end end @@ -486,7 +490,7 @@ module ActionDispatch def self.app # DEPRECATE Rails application fallback # This should be set by the initializer - @@app || (defined?(Rails.application) && Rails.application.new) || nil + @@app || (defined?(Rails.application) && Rails.application) || nil end def self.app=(app) diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 31e9c5ef9d..c33695770f 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -169,6 +169,15 @@ module ActionView #:nodoc: include Helpers, Rendering, Partials, ::ERB::Util + def config + self.config = DEFAULT_CONFIG unless @config + @config + end + + def config=(config) + @config = ActiveSupport::OrderedOptions.new.merge(config) + end + extend ActiveSupport::Memoizable attr_accessor :base_path, :assigns, :template_extension, :formats @@ -178,11 +187,11 @@ module ActionView #:nodoc: def reset_formats(formats) @formats = formats - if defined?(ActionController) + if defined?(AbstractController::HashKey) # This is expensive, but we need to reset this when the format is updated, # which currently only happens Thread.current[:format_locale_key] = - ActionController::HashKey.get(self.class, formats, I18n.locale) + AbstractController::HashKey.get(self.class, formats, I18n.locale) end end @@ -203,6 +212,11 @@ module ActionView #:nodoc: @@cache_template_loading = nil cattr_accessor :cache_template_loading + # :nodoc: + def self.xss_safe? + true + end + def self.cache_template_loading? ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading) end @@ -236,16 +250,16 @@ module ActionView #:nodoc: # they are in AC. if controller.class.respond_to?(:_helper_serial) klass = @views[controller.class._helper_serial] ||= Class.new(self) do - const_set(:CONTROLLER_CLASS, controller.class) - # Try to make stack traces clearer - def self.name - "ActionView for #{CONTROLLER_CLASS}" - end - - def inspect - "#<#{self.class.name}>" - end + class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 + def self.name + "ActionView for #{controller.class}" + end + + def inspect + "#<#{self.class.name}>" + end + ruby_eval if controller.respond_to?(:_helpers) include controller._helpers diff --git a/actionpack/lib/action_view/erb/util.rb b/actionpack/lib/action_view/erb/util.rb index f767a5e27e..aef859b3ac 100644 --- a/actionpack/lib/action_view/erb/util.rb +++ b/actionpack/lib/action_view/erb/util.rb @@ -23,6 +23,7 @@ class ERB end end + undef :h alias h html_escape module_function :html_escape diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb index d63e8602f1..3d088678fc 100644 --- a/actionpack/lib/action_view/helpers.rb +++ b/actionpack/lib/action_view/helpers.rb @@ -1,10 +1,11 @@ +require 'active_support/benchmarkable' + module ActionView #:nodoc: module Helpers #:nodoc: autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper' autoload :AjaxHelper, 'action_view/helpers/ajax_helper' autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper' autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper' - autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper' autoload :CacheHelper, 'action_view/helpers/cache_helper' autoload :CaptureHelper, 'action_view/helpers/capture_helper' autoload :DateHelper, 'action_view/helpers/date_helper' @@ -33,10 +34,11 @@ module ActionView #:nodoc: include SanitizeHelper::ClassMethods end + include ActiveSupport::Benchmarkable + include ActiveModelHelper include AssetTagHelper include AtomFeedHelper - include BenchmarkHelper include CacheHelper include CaptureHelper include DateHelper diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 7cc1e48572..c70f29f098 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -191,19 +191,19 @@ module ActionView options = params.extract_options!.symbolize_keys objects = Array.wrap(options.delete(:object) || params).map do |object| - unless object.respond_to?(:to_model) - object = instance_variable_get("@#{object}") - object = convert_to_model(object) - else - object = object.to_model - options[:object_name] ||= object.class.model_name.human + object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model) + object = convert_to_model(object) + + if object.class.respond_to?(:model_name) + options[:object_name] ||= object.class.model_name.human.downcase end + object end objects.compact! - count = objects.inject(0) {|sum, object| sum + object.errors.count } + unless count.zero? html = {} [:id, :class].each do |key| @@ -216,16 +216,20 @@ module ActionView end options[:object_name] ||= params.first - I18n.with_options :locale => options[:locale], :scope => [:activerecord, :errors, :template] do |locale| + I18n.with_options :locale => options[:locale], :scope => [:activemodel, :errors, :template] do |locale| header_message = if options.include?(:header_message) options[:header_message] else - object_name = options[:object_name].to_s.gsub('_', ' ') - object_name = I18n.t(options[:object_name].to_s, :default => object_name, :scope => [:activerecord, :models], :count => 1) - locale.t :header, :count => count, :model => object_name + locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ') end + message = options.include?(:message) ? options[:message] : locale.t(:body) - error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join + + error_messages = objects.sum do |object| + object.errors.full_messages.map do |msg| + content_tag(:li, ERB::Util.html_escape(msg)) + end + end.join contents = '' contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index faa7f2e2e9..15b70ecff5 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -133,9 +133,13 @@ module ActionView # change. You can use something like Live HTTP Headers for Firefox to verify # that the cache is indeed working. module AssetTagHelper - ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public" - JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts" - STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets" + assets_dir = defined?(Rails.public_path) ? Rails.public_path : "public" + ActionView::DEFAULT_CONFIG = { + :assets_dir => assets_dir, + :javascripts_dir => "#{assets_dir}/javascripts", + :stylesheets_dir => "#{assets_dir}/stylesheets", + } + JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) # Returns a link tag that browsers and news readers can use to auto-detect @@ -280,7 +284,7 @@ module ActionView if concat || (ActionController::Base.perform_caching && cache) joined_javascript_name = (cache == true ? "all" : cache) + ".js" - joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : JAVASCRIPTS_DIR, joined_javascript_name) + joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name) unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path) write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) @@ -431,7 +435,7 @@ module ActionView if concat || (ActionController::Base.perform_caching && cache) joined_stylesheet_name = (cache == true ? "all" : cache) + ".css" - joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : STYLESHEETS_DIR, joined_stylesheet_name) + joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name) unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path) write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) @@ -630,11 +634,11 @@ module ActionView # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. - def compute_public_path(source, dir, ext = nil, include_host = true) + def compute_public_path(source, dir, ext = nil, include_host = true) has_request = @controller.respond_to?(:request) source_ext = File.extname(source)[1..-1] - if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))) + if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) source += ".#{ext}" end @@ -700,7 +704,7 @@ module ActionView if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source]) asset_id else - path = File.join(ASSETS_DIR, source) + path = File.join(config.assets_dir, source) asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : '' if @@cache_asset_timestamps @@ -743,20 +747,20 @@ module ActionView def expand_javascript_sources(sources, recursive = false) if sources.include?(:all) - all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js') + all_javascript_files = collect_asset_files(config.javascripts_dir, ('**' if recursive), '*.js') ((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq else expanded_sources = sources.collect do |source| determine_source(source, @@javascript_expansions) end.flatten - expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js")) + expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(config.javascripts_dir, "application.js")) expanded_sources end end def expand_stylesheet_sources(sources, recursive) if sources.first == :all - collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css') + collect_asset_files(config.stylesheets_dir, ('**' if recursive), '*.css') else sources.collect do |source| determine_source(source, @@stylesheet_expansions) @@ -803,7 +807,7 @@ module ActionView end def asset_file_path(path) - File.join(ASSETS_DIR, path.split('?').first) + File.join(config.assets_dir, path.split('?').first) end def asset_file_path!(path) diff --git a/actionpack/lib/action_view/helpers/benchmark_helper.rb b/actionpack/lib/action_view/helpers/benchmark_helper.rb deleted file mode 100644 index c13a069a35..0000000000 --- a/actionpack/lib/action_view/helpers/benchmark_helper.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'active_support/core_ext/benchmark' - -module ActionView - module Helpers - # This helper offers a method to measure the execution time of a block - # in a template. - module BenchmarkHelper - # Allows you to measure the execution time of a block - # in a template and records the result to the log. Wrap this block around - # expensive operations or possible bottlenecks to get a time reading - # for the operation. For example, let's say you thought your file - # processing method was taking too long; you could wrap it in a benchmark block. - # - # <% benchmark "Process data files" do %> - # <%= expensive_files_operation %> - # <% end %> - # - # That would add something like "Process data files (345.2ms)" to the log, - # which you can then use to compare timings when optimizing your code. - # - # You may give an optional logger level as the :level option. - # (:debug, :info, :warn, :error); the default value is :info. - # - # <% benchmark "Low-level files", :level => :debug do %> - # <%= lowlevel_files_operation %> - # <% end %> - # - # Finally, you can pass true as the third argument to silence all log activity - # inside the block. This is great for boiling down a noisy block to just a single statement: - # - # <% benchmark "Process data files", :level => :info, :silence => true do %> - # <%= expensive_and_chatty_files_operation %> - # <% end %> - def benchmark(message = "Benchmarking", options = {}) - if controller.logger - if options.is_a?(Symbol) - ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller) - options = { :level => options, :silence => false } - else - options.assert_valid_keys(:level, :silence) - options[:level] ||= :info - end - - result = nil - ms = Benchmark.ms { result = options[:silence] ? controller.logger.silence { yield } : yield } - controller.logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) - result - else - yield - end - end - end - end -end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index c46b39fc23..d0c66eda60 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -963,7 +963,7 @@ module ActionView end end - (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| + (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector| src = <<-end_src def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( @@ -1022,6 +1022,11 @@ module ActionView def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end + + def hidden_field(method, options = {}) + @emitted_hidden_id = true if method == :id + @template.hidden_field(@object_name, method, objectify_options(options)) + end def error_message_on(method, *args) @template.error_message_on(@object, method, *args) @@ -1035,6 +1040,10 @@ module ActionView @template.submit_tag(value, options.reverse_merge(:id => "#{object_name}_submit")) end + def emitted_hidden_id? + @emitted_hidden_id + end + private def objectify_options(options) @default_options.merge(options.merge(:object => @object)) @@ -1069,8 +1078,8 @@ module ActionView @template.fields_for(name, object, *args, &block) else @template.fields_for(name, object, *args) do |builder| - @template.concat builder.hidden_field(:id) block.call(builder) + @template.concat builder.hidden_field(:id) unless builder.emitted_hidden_id? end end end diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 4aed10f640..564f12c955 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -21,7 +21,7 @@ module ActionView # Delegates to I18n.localize with no additional functionality. def localize(*args) - I18n.localize *args + I18n.localize(*args) end alias :l :localize diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index e651bc17a9..5b136d4f54 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -83,7 +83,7 @@ module ActionView options when Hash options = { :only_path => options[:host].nil? }.update(options.symbolize_keys) - escape = options.key?(:escape) ? options.delete(:escape) : true + escape = options.key?(:escape) ? options.delete(:escape) : false @controller.send(:url_for, options) when :back escape = false @@ -93,7 +93,7 @@ module ActionView polymorphic_path(options) end - (escape ? escape_once(url) : url).html_safe! + escape ? escape_once(url).html_safe! : url end # Creates a link tag of the given +name+ using a URL created by the set diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 84d94fd700..5e2a92b89a 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -102,7 +102,7 @@ minute: "Minute" second: "Seconds" - activerecord: + activemodel: errors: template: header: @@ -114,4 +114,4 @@ support: select: # default value for :prompt => true in FormOptionsHelper - prompt: "Please select"
\ No newline at end of file + prompt: "Please select" diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index b6f5b9b6d1..7006a5b968 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -71,12 +71,10 @@ module ActionView # In this case, the layout would receive the block passed into <tt>render :layout</tt>, # and the Struct specified in the layout would be passed into the block. The result # would be <html>Hello David</html>. - def _layout_for(name = nil) + def _layout_for(name = nil, &block) return @_content_for[name || :layout] if !block_given? || name - with_output_buffer do - return yield - end + capture(&block) end def _render_inline(inline, layout, options) @@ -123,7 +121,6 @@ module ActionView template.render(self, locals) end - @cached_content_for_layout = content @_content_for[:layout] = content if layout @@ -134,4 +131,4 @@ module ActionView content end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_view/safe_buffer.rb b/actionpack/lib/action_view/safe_buffer.rb index 8ba9cd80d6..09f44ab26f 100644 --- a/actionpack/lib/action_view/safe_buffer.rb +++ b/actionpack/lib/action_view/safe_buffer.rb @@ -5,7 +5,7 @@ module ActionView #:nodoc: if value.html_safe? super(value) else - super(CGI.escapeHTML(value)) + super(ERB::Util.h(value)) end end diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index a780ab8d85..88aeb4b053 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -37,11 +37,16 @@ module ActionView self.erb_trim_mode = '-' self.default_format = Mime::HTML + + cattr_accessor :erubis_implementation + self.erubis_implementation = Erubis def compile(template) - magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/ - erb = "#{magic}<% __in_erb_template=true %>#{template.source}" - Erubis.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + source = template.source.gsub(/\A(<%(#.*coding[:=]\s*(\S+)\s*)-?%>)\s*\n?/, '') + erb = "<% __in_erb_template=true %>#{source}" + result = self.class.erubis_implementation.new(erb, :trim=>(self.class.erb_trim_mode == "-")).src + result = "#{$2}\n#{result}" if $2 + result end end end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index f5591ead09..7336114e1b 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -121,7 +121,7 @@ module ActionView # # :api: plugin def path_to_details(path) # [:erb, :format => :html, :locale => :en, :partial => true/false] - if m = path.match(%r'(?:^|/)(_)?[\w-]+(\.[\w-]+)*\.(\w+)$') + if m = path.match(%r'(?:^|/)(_)?[\w-]+((?:\.[\w-]+)*)\.(\w+)$') partial = m[1] == '_' details = (m[2]||"").split('.').reject { |e| e.empty? } handler = Template.handler_class_for_extension(m[3]) diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index 0f64c23649..d1970ca3c7 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -27,10 +27,10 @@ module ActionView end def render(view, locals, &block) - ActiveSupport::Orchestra.instrument(:render_template, :identifier => identifier) do + ActiveSupport::Notifications.instrument(:render_template, :identifier => identifier) do method_name = compile(locals, view) view.send(method_name, locals, &block) - end.result + end rescue Exception => e if e.is_a?(TemplateError) e.sub_template_of(self) diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index f7d0df5ba0..f6e011a5ab 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,6 +1,8 @@ module ActionView #:nodoc: class TextTemplate < String #:nodoc: - def initialize(string, content_type = Mime[:html]) + HTML = Mime[:html] + + def initialize(string, content_type = HTML) super(string.to_s) @content_type = Mime[content_type] || content_type end @@ -14,11 +16,11 @@ module ActionView #:nodoc: end def inspect - 'inline template' + 'text template' end def render(*args) - self + to_s end def mime_type diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 8beda24aba..86bbad822d 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -120,6 +120,7 @@ module ActionView def _view view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller) view.class.send :include, _helpers + view.output_buffer = self.output_buffer view end diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index 5a363c9aa5..efcd68e5c8 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -1,17 +1,17 @@ require 'abstract_unit' +ActionController::Base.helpers_dir = File.dirname(__FILE__) + '/../fixtures/helpers' + module AbstractController module Testing class ControllerWithHelpers < AbstractController::Base include AbstractController::RenderingController include Helpers - - def render(string) - super(:_template_name => string) + + def with_module + render :inline => "Module <%= included_method %>" end - - append_view_path File.expand_path(File.join(File.dirname(__FILE__), "views")) end module HelperyTest @@ -20,24 +20,61 @@ module AbstractController end end - class MyHelpers1 < ControllerWithHelpers + class AbstractHelpers < ControllerWithHelpers helper(HelperyTest) do def helpery_test "World" end end - - def index - render "helper_test.erb" + + helper :abc + + def with_block + render :inline => "Hello <%= helpery_test %>" + end + + def with_symbol + render :inline => "I respond to bare_a: <%= respond_to?(:bare_a) %>" + end + end + + class AbstractHelpersBlock < ControllerWithHelpers + helper do + include ::AbstractController::Testing::HelperyTest end end - + class TestHelpers < ActiveSupport::TestCase - def test_helpers - controller = MyHelpers1.new - controller.process(:index) - assert_equal "Hello World : Included", controller.response_body + + def setup + @controller = AbstractHelpers.new end + + def test_helpers_with_block + @controller.process(:with_block) + assert_equal "Hello World", @controller.response_body + end + + def test_helpers_with_module + @controller.process(:with_module) + assert_equal "Module Included", @controller.response_body + end + + def test_helpers_with_symbol + @controller.process(:with_symbol) + assert_equal "I respond to bare_a: true", @controller.response_body + end + + def test_declare_missing_helper + assert_raise(MissingSourceFile) { AbstractHelpers.helper :missing } + end + + def test_helpers_with_module_through_block + @controller = AbstractHelpersBlock.new + @controller.process(:with_module) + assert_equal "Module Included", @controller.response_body + end + end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index 453d31826e..ae2f1bf1f2 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -17,17 +17,6 @@ module AbstractControllerTests "layouts/omg.erb" => "OMGHI2U <%= yield %>", "layouts/with_false_layout.erb" => "False Layout <%= yield %>" )] - - def self.controller_path - @controller_path ||= self.name.sub(/Controller$/, '').underscore - end - - def controller_path() self.class.controller_path end - - def render_to_body(options) - options[:_layout] = _default_layout({}) - super - end end class Blank < Base @@ -44,6 +33,22 @@ module AbstractControllerTests def index render :_template => ActionView::TextTemplate.new("Hello string!") end + + def overwrite_default + render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => :default + end + + def overwrite_false + render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => false + end + + def overwrite_string + render :_template => ActionView::TextTemplate.new("Hello string!"), :layout => "omg" + end + + def overwrite_skip + render :text => "Hello text!" + end end class WithStringChild < WithString @@ -153,7 +158,31 @@ module AbstractControllerTests controller.process(:index) assert_equal "With String Hello string!", controller.response_body end - + + test "when layout is overwriten by :default in render, render default layout" do + controller = WithString.new + controller.process(:overwrite_default) + assert_equal "With String Hello string!", controller.response_body + end + + test "when layout is overwriten by string in render, render new layout" do + controller = WithString.new + controller.process(:overwrite_string) + assert_equal "OMGHI2U Hello string!", controller.response_body + end + + test "when layout is overwriten by false in render, render no layout" do + controller = WithString.new + controller.process(:overwrite_false) + assert_equal "Hello string!", controller.response_body + end + + test "when text is rendered, render no layout" do + controller = WithString.new + controller.process(:overwrite_skip) + assert_equal "Hello text!", controller.response_body + end + test "when layout is specified as a string, but the layout is missing, raise an exception" do assert_raises(ActionView::MissingTemplate) { WithMissingLayout.new.process(:index) } end @@ -183,7 +212,7 @@ module AbstractControllerTests end test "when the layout is specified as a symbol and the method doesn't exist, raise an exception" do - assert_raises(NoMethodError, /:nilz/) { WithSymbolAndNoMethod.new.process(:index) } + assert_raises(NoMethodError) { WithSymbolAndNoMethod.new.process(:index) } end test "when the layout is specified as a symbol and the method returns something besides a string/false/nil, raise an exception" do diff --git a/actionpack/test/abstract/localized_cache_test.rb b/actionpack/test/abstract/localized_cache_test.rb new file mode 100644 index 0000000000..6f9bb693f7 --- /dev/null +++ b/actionpack/test/abstract/localized_cache_test.rb @@ -0,0 +1,57 @@ +require 'abstract_unit' + +module AbstractController + module Testing + + class CachedController < AbstractController::Base + include AbstractController::RenderingController + include AbstractController::LocalizedCache + + self.view_paths = [ActionView::FixtureResolver.new( + "default.erb" => "With Default", + "template.erb" => "With Template", + "some/file.erb" => "With File", + "template_name.erb" => "With Template Name" + )] + end + + class TestLocalizedCache < ActiveSupport::TestCase + + def setup + @controller = CachedController.new + CachedController.clear_template_caches! + end + + def test_templates_are_cached + @controller.render :template => "default.erb" + assert_equal "With Default", @controller.response_body + + cached = @controller.class.template_cache + assert_equal 1, cached.size + assert_kind_of ActionView::Template, cached.values.first["default.erb"] + end + + def test_cache_is_used + CachedController.new.render :template => "default.erb" + + @controller.expects(:find_template).never + @controller.render :template => "default.erb" + + assert_equal 1, @controller.class.template_cache.size + end + + def test_cache_changes_with_locale + CachedController.new.render :template => "default.erb" + + I18n.locale = :es + @controller.render :template => "default.erb" + + assert_equal 2, @controller.class.template_cache.size + ensure + I18n.locale = :en + end + + end + + end +end diff --git a/actionpack/test/abstract/render_test.rb b/actionpack/test/abstract/render_test.rb new file mode 100644 index 0000000000..45a4763fe4 --- /dev/null +++ b/actionpack/test/abstract/render_test.rb @@ -0,0 +1,88 @@ +require 'abstract_unit' + +module AbstractController + module Testing + + class ControllerRenderer < AbstractController::Base + include AbstractController::RenderingController + + self.view_paths = [ActionView::FixtureResolver.new( + "default.erb" => "With Default", + "template.erb" => "With Template", + "some/file.erb" => "With File", + "template_name.erb" => "With Template Name" + )] + + def template + render :template => "template" + end + + def file + render :file => "some/file" + end + + def inline + render :inline => "With <%= :Inline %>" + end + + def text + render :text => "With Text" + end + + def default + render + end + + def template_name + render :_template_name => :template_name + end + + def object + render :_template => ActionView::TextTemplate.new("With Object") + end + end + + class TestRenderer < ActiveSupport::TestCase + + def setup + @controller = ControllerRenderer.new + end + + def test_render_template + @controller.process(:template) + assert_equal "With Template", @controller.response_body + end + + def test_render_file + @controller.process(:file) + assert_equal "With File", @controller.response_body + end + + def test_render_inline + @controller.process(:inline) + assert_equal "With Inline", @controller.response_body + end + + def test_render_text + @controller.process(:text) + assert_equal "With Text", @controller.response_body + end + + def test_render_default + @controller.process(:default) + assert_equal "With Default", @controller.response_body + end + + def test_render_template_name + @controller.process(:template_name) + assert_equal "With Template Name", @controller.response_body + end + + def test_render_object + @controller.process(:object) + assert_equal "With Object", @controller.response_body + end + + end + end +end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 4820f00aa1..775cfc82bf 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -1,20 +1,18 @@ -$:.unshift(File.dirname(__FILE__) + '/../lib') -$:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib') -$:.unshift(File.dirname(__FILE__) + '/../../activemodel/lib') +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift "#{root}/activesupport/lib" + $:.unshift "#{root}/activemodel/lib" +end + +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) $:.unshift(File.dirname(__FILE__) + '/lib') $:.unshift(File.dirname(__FILE__) + '/fixtures/helpers') $:.unshift(File.dirname(__FILE__) + '/fixtures/alternate_helpers') -bundler = File.join(File.dirname(__FILE__), '..', 'vendor', 'gems', 'environment') -require bundler if File.exist?("#{bundler}.rb") - -begin - %w( rack rack/test sqlite3 ).each { |lib| require lib } -rescue LoadError => e - abort e.message -end - ENV['TMPDIR'] = File.join(File.dirname(__FILE__), 'tmp') require 'test/unit' @@ -103,6 +101,26 @@ class ActionController::IntegrationTest < ActiveSupport::TestCase self.app = build_app + class StubDispatcher + def self.new(*args) + lambda { |env| + params = env['action_dispatch.request.path_parameters'] + controller, action = params[:controller], params[:action] + [200, {'Content-Type' => 'text/html'}, ["#{controller}##{action}"]] + } + end + end + + def self.stub_controllers + old_dispatcher = ActionDispatch::Routing::RouteSet::Dispatcher + ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } + ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, StubDispatcher } + yield ActionDispatch::Routing::RouteSet.new + ensure + ActionDispatch::Routing::RouteSet.module_eval { remove_const :Dispatcher } + ActionDispatch::Routing::RouteSet.module_eval { const_set :Dispatcher, old_dispatcher } + end + def with_routing(&block) real_routes = ActionController::Routing::Routes ActionController::Routing.module_eval { remove_const :Routes } @@ -173,8 +191,8 @@ class ::ApplicationController < ActionController::Base end module ActionController - class << Routing - def possible_controllers + module Routing + def self.possible_controllers @@possible_controllers ||= [] end end diff --git a/actionpack/test/activerecord/polymorphic_routes_test.rb b/actionpack/test/activerecord/polymorphic_routes_test.rb index 37f1f6dff8..ad744421db 100644 --- a/actionpack/test/activerecord/polymorphic_routes_test.rb +++ b/actionpack/test/activerecord/polymorphic_routes_test.rb @@ -98,14 +98,6 @@ class PolymorphicRoutesTest < ActionController::TestCase end end - def test_formatted_url_helper_is_deprecated - with_test_routes do - assert_deprecated do - formatted_polymorphic_url([@project, :pdf]) - end - end - end - def test_format_option with_test_routes do @project.save @@ -251,6 +243,12 @@ class PolymorphicRoutesTest < ActionController::TestCase end end + def test_with_array_containing_symbols + with_test_routes do + assert_equal "http://example.com/series/new", polymorphic_url([:new, :series]) + end + end + def test_with_hash with_test_routes do @project.save diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index b97ceb4594..b57550a69a 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -10,12 +10,12 @@ module Submodule def public_action render :nothing => true end - + hide_action :hidden_action def hidden_action raise "Noooo!" end - + def another_hidden_action end hide_action :another_hidden_action @@ -30,25 +30,25 @@ class NonEmptyController < ActionController::Base def public_action render :nothing => true end - + hide_action :hidden_action def hidden_action end end class MethodMissingController < ActionController::Base - + hide_action :shouldnt_be_called def shouldnt_be_called raise "NO WAY!" end - + protected - + def method_missing(selector) render :text => selector.to_s end - + end class DefaultUrlOptionsController < ActionController::Base @@ -79,7 +79,7 @@ class ControllerInstanceTests < Test::Unit::TestCase @empty = EmptyController.new @contained = Submodule::ContainedEmptyController.new @empty_controllers = [@empty, @contained, Submodule::SubclassedController.new] - + @non_empty_controllers = [NonEmptyController.new, Submodule::ContainedNonEmptyController.new] end @@ -135,24 +135,24 @@ class PerformActionTest < ActionController::TestCase rescue_action_in_public! end - + def test_get_on_priv_should_show_selector use_controller MethodMissingController get :shouldnt_be_called assert_response :success assert_equal 'shouldnt_be_called', @response.body end - + def test_method_missing_is_not_an_action_name use_controller MethodMissingController assert ! @controller.__send__(:action_method?, 'method_missing') - + get :method_missing assert_response :success assert_equal 'method_missing', @response.body end - + def test_get_on_hidden_should_fail use_controller NonEmptyController assert_raise(ActionController::UnknownAction) { get :hidden_action } diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 69b0eb5e3e..3ce90b6ccf 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -149,7 +149,9 @@ end class ActionCachingTestController < ActionController::Base rescue_from(Exception) { head 500 } - rescue_from(ActiveRecord::RecordNotFound) { head :not_found } + if defined? ActiveRecord + rescue_from(ActiveRecord::RecordNotFound) { head :not_found } + end caches_action :index, :redirected, :forbidden, :if => Proc.new { |c| !c.request.format.json? }, :expires_in => 1.hour caches_action :show, :cache_path => 'http://test.host/custom/show' @@ -226,12 +228,16 @@ class ActionCachingMockController @mock_url_for end + def params + request.parameters + end + def request mocked_path = @mock_path Object.new.instance_eval(<<-EVAL) def path; '#{@mock_path}' end def format; 'all' end - def cache_format; nil end + def parameters; {:format => nil}; end self EVAL end @@ -464,7 +470,7 @@ class ActionCacheTest < ActionController::TestCase @mock_controller.mock_url_for = 'http://example.org/' @mock_controller.mock_path = '/' - assert_equal 'example.org/index', @path_class.path_for(@mock_controller, {}) + assert_equal 'example.org/index', @path_class.new(@mock_controller, {}).path end def test_file_extensions @@ -474,11 +480,13 @@ class ActionCacheTest < ActionController::TestCase assert_response :success end - def test_record_not_found_returns_404_for_multiple_requests - get :record_not_found - assert_response 404 - get :record_not_found - assert_response 404 + if defined? ActiveRecord + def test_record_not_found_returns_404_for_multiple_requests + get :record_not_found + assert_response 404 + get :record_not_found + assert_response 404 + end end def test_four_oh_four_returns_404_for_multiple_requests @@ -624,20 +632,13 @@ class FragmentCachingTest < ActionController::TestCase def test_fragment_for_logging fragment_computed = false - - listener = [] - ActiveSupport::Orchestra.register listener + ActiveSupport::Notifications.queue.expects(:publish).times(2) buffer = 'generated till now -> ' @controller.fragment_for(buffer, 'expensive') { fragment_computed = true } - assert_equal 1, listener.count { |e| e.name == :fragment_exist? } - assert_equal 1, listener.count { |e| e.name == :write_fragment } - assert fragment_computed assert_equal 'generated till now -> ', buffer - ensure - ActiveSupport::Orchestra.unregister listener end end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 7199da3441..53d4364576 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -106,7 +106,7 @@ class CookieTest < ActionController::TestCase def test_cookiejar_accessor @request.cookies["user_name"] = "david" @controller.request = @request - jar = ActionController::CookieJar.new(@controller) + jar = ActionController::CookieJar.build(@controller.request, @controller.response) assert_equal "david", jar["user_name"] assert_equal nil, jar["something_else"] end @@ -114,19 +114,25 @@ class CookieTest < ActionController::TestCase def test_cookiejar_accessor_with_array_value @request.cookies["pages"] = %w{1 2 3} @controller.request = @request - jar = ActionController::CookieJar.new(@controller) + jar = ActionController::CookieJar.build(@controller.request, @controller.response) assert_equal %w{1 2 3}, jar["pages"] end + def test_cookiejar_delete_removes_item_and_returns_its_value + @request.cookies["user_name"] = "david" + @controller.response = @response + jar = ActionController::CookieJar.build(@controller.request, @controller.response) + assert_equal "david", jar.delete("user_name") + end + def test_delete_cookie_with_path get :delete_cookie_with_path assert_cookie_header "user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT" end def test_cookies_persist_throughout_request - get :authenticate - cookies = @controller.send(:cookies) - assert_equal 'david', cookies['user_name'] + response = get :authenticate + assert response.headers["Set-Cookie"] =~ /user_name=david/ end private diff --git a/actionpack/test/controller/filter_params_test.rb b/actionpack/test/controller/filter_params_test.rb index 19232c6bc9..43bef34885 100644 --- a/actionpack/test/controller/filter_params_test.rb +++ b/actionpack/test/controller/filter_params_test.rb @@ -19,23 +19,23 @@ class FilterParamTest < ActionController::TestCase def method_missing(method, *args) @logged ||= [] - @logged << args.first + @logged << args.first unless block_given? + @logged << yield if block_given? end end setup :set_logger + def test_filter_parameters_must_have_one_word + assert_raises RuntimeError do + FilterParamController.filter_parameter_logging + end + 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'},[]], - [{'foo'=>1},{'foo'=>1},[]], + test_hashes = [ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'], [{'foo'=>'bar'},{'foo'=>'[FILTERED]'},%w'foo'], [{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'], diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 2da97a9d86..8445428e8f 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'active_support/core_ext/symbol' class ActionController::Base class << self diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb index 23149fee27..b9be163904 100644 --- a/actionpack/test/controller/helper_test.rb +++ b/actionpack/test/controller/helper_test.rb @@ -3,12 +3,6 @@ require 'active_support/core_ext/kernel/reporting' ActionController::Base.helpers_dir = File.dirname(__FILE__) + '/../fixtures/helpers' -class TestController < ActionController::Base - attr_accessor :delegate_attr - def delegate_method() end - def rescue_action(e) raise end -end - module Fun class GamesController < ActionController::Base def render_hello_world @@ -38,6 +32,12 @@ module LocalAbcHelper end class HelperTest < Test::Unit::TestCase + class TestController < ActionController::Base + attr_accessor :delegate_attr + def delegate_method() end + def rescue_action(e) raise end + end + def setup # Increment symbol counter. @symbol = (@@counter ||= 'A0').succ!.dup @@ -57,40 +57,6 @@ class HelperTest < Test::Unit::TestCase assert_equal [], missing_methods end - def test_declare_helper - require 'abc_helper' - self.test_helper = AbcHelper - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { @controller_class.helper :abc } - assert_equal [], missing_methods - end - - def test_declare_missing_helper - assert_equal expected_helper_methods, missing_methods - assert_raise(MissingSourceFile) { @controller_class.helper :missing } - end - - def test_declare_missing_file_from_helper - require 'broken_helper' - rescue LoadError => e - assert_nil(/\bbroken_helper\b/.match(e.to_s)[1]) - end - - def test_helper_block - assert_nothing_raised { - @controller_class.helper { def block_helper_method; end } - } - assert master_helper_methods.include?('block_helper_method') - end - - def test_helper_block_include - assert_equal expected_helper_methods, missing_methods - assert_nothing_raised { - @controller_class.helper { include HelperTest::TestHelper } - } - assert [], missing_methods - end - def test_helper_method assert_nothing_raised { @controller_class.helper_method :delegate_method } assert master_helper_methods.include?('delegate_method') diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 508364d0b5..624b14e69b 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -199,6 +199,24 @@ class IntegrationTestTest < Test::Unit::TestCase assert_equal ::ActionController::Integration::Session, session2.class assert_not_equal session1, session2 end + + # RSpec mixes Matchers (which has a #method_missing) into + # IntegrationTest's superclass. Make sure IntegrationTest does not + # try to delegate these methods to the session object. + def test_does_not_prevent_method_missing_passing_up_to_ancestors + mixin = Module.new do + def method_missing(name, *args) + name.to_s == 'foo' ? 'pass' : super + end + end + @test.class.superclass.__send__(:include, mixin) + begin + assert_equal 'pass', @test.foo + ensure + # leave other tests as unaffected as possible + mixin.__send__(:remove_method, :method_missing) + end + end end # Tests that integration tests don't call Controller test methods for processing. @@ -372,7 +390,7 @@ class IntegrationProcessTest < ActionController::IntegrationTest def with_test_route_set with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "integration_process_test/integration" + match ':action', :to => ::IntegrationProcessTest::IntegrationController end yield end diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index a79648396c..fee9cf46f9 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -501,6 +501,12 @@ class RespondWithController < ActionController::Base respond_with(Customer.new("david", 13), :responder => responder) end + def using_resource_with_action + respond_with(Customer.new("david", 13), :action => :foo) do |format| + format.html { raise ActionView::MissingTemplate.new([], "method") } + end + end + protected def _render_js(js, options) @@ -715,6 +721,20 @@ class RespondWithControllerTest < ActionController::TestCase assert_match /<name>jamis<\/name>/, @response.body end + def test_using_resource_with_action + @controller.instance_eval do + def render(params={}) + self.response_body = "#{params[:action]} - #{formats}" + end + end + + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + + post :using_resource_with_action + assert_equal "foo - #{[:html].to_s}", @controller.response_body + end + def test_clear_respond_to @controller = InheritedRespondWithController.new @request.accept = "text/html" @@ -760,6 +780,14 @@ class RespondWithControllerTest < ActionController::TestCase assert_equal "Resource name is david", @response.body end + def test_using_resource_with_set_responder + RespondWithController.responder = proc { |c, r, o| c.render :text => "Resource name is #{r.first.name}" } + get :using_resource + assert_equal "Resource name is david", @response.body + ensure + RespondWithController.responder = ActionController::Responder + end + def test_not_acceptable @request.accept = "application/xml" get :using_defaults diff --git a/actionpack/test/controller/new_base/content_negotiation_test.rb b/actionpack/test/controller/new_base/content_negotiation_test.rb index 7b38a82f51..b98a22dfcc 100644 --- a/actionpack/test/controller/new_base/content_negotiation_test.rb +++ b/actionpack/test/controller/new_base/content_negotiation_test.rb @@ -5,7 +5,7 @@ module ContentNegotiation # This has no layout and it works class BasicController < ActionController::Base self.view_paths = [ActionView::FixtureResolver.new( - "content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats %>!" + "content_negotiation/basic/hello.html.erb" => "Hello world <%= request.formats.first.to_s %>!" )] end diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb index e1d46b906e..ab61fd98ee 100644 --- a/actionpack/test/controller/new_base/metal_test.rb +++ b/actionpack/test/controller/new_base/metal_test.rb @@ -20,8 +20,8 @@ module MetalTest class TestMiddleware < ActiveSupport::TestCase def setup @app = Rack::Builder.new do - use MetalMiddleware - run Endpoint.new + use MetalTest::MetalMiddleware + run MetalTest::Endpoint.new end.to_app end diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb index ecd29c4530..239f68659c 100644 --- a/actionpack/test/controller/new_base/render_action_test.rb +++ b/actionpack/test/controller/new_base/render_action_test.rb @@ -86,7 +86,7 @@ module RenderAction describe "Both <controller_path>.html.erb and application.html.erb are missing" test "rendering with layout => true" do - assert_raise(ArgumentError, /no default layout for RenderAction::BasicController in/) do + assert_raise(ArgumentError) do get "/render_action/basic/hello_world_with_layout", {}, "action_dispatch.show_exceptions" => false end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 2db524ca4b..b32325fa20 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -73,6 +73,11 @@ class TestController < ActionController::Base render :action => 'hello_world' end + def conditional_hello_with_expires_now + expires_now + render :action => 'hello_world' + end + def conditional_hello_with_bangs render :action => 'hello_world' end @@ -855,7 +860,7 @@ class RenderTest < ActionController::TestCase # :ported: def test_access_to_controller_name_in_view get :accessing_controller_name_in_template - assert_equal "test", @response.body # name is explicitly set to 'test' inside the controller. + assert_equal "test", @response.body # name is explicitly set in the controller. end # :ported: @@ -1321,6 +1326,11 @@ class ExpiresInRenderTest < ActionController::TestCase get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"] end + + def test_expires_now + get :conditional_hello_with_expires_now + assert_equal "no-cache", @response.headers["Cache-Control"] + end end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 689359166f..37367eaafc 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -327,7 +327,7 @@ class RescueTest < ActionController::IntegrationTest test 'rescue routing exceptions' do @app = ActionDispatch::Rescue.new(ActionController::Routing::Routes) do - rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, "Gotcha!"] } + rescue_from ActionController::RoutingError, lambda { |env| [200, {"Content-Type" => "text/html"}, ["Gotcha!"]] } end get '/b00m' @@ -343,9 +343,9 @@ class RescueTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect 'foo', :controller => "rescue_test/test", :action => 'foo' - map.connect 'invalid', :controller => "rescue_test/test", :action => 'invalid' - map.connect 'b00m', :controller => "rescue_test/test", :action => 'b00m' + match 'foo', :to => ::RescueTest::TestController.action(:foo) + match 'invalid', :to => ::RescueTest::TestController.action(:invalid) + match 'b00m', :to => ::RescueTest::TestController.action(:b00m) end yield end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 5b47de19ae..04e9acf855 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -41,7 +41,7 @@ class ResourcesTest < ActionController::TestCase end def test_should_arrange_actions - resource = ActionController::Resources::Resource.new(:messages, + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :collection => { :rss => :get, :reorder => :post, :csv => :post }, :member => { :rss => :get, :atom => :get, :upload => :post, :fix => :post }, :new => { :preview => :get, :draft => :get }) @@ -54,18 +54,18 @@ class ResourcesTest < ActionController::TestCase end def test_should_resource_controller_name_equal_resource_name_by_default - resource = ActionController::Resources::Resource.new(:messages, {}) + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, {}) assert_equal 'messages', resource.controller end def test_should_resource_controller_name_equal_controller_option - resource = ActionController::Resources::Resource.new(:messages, :controller => 'posts') + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :controller => 'posts') assert_equal 'posts', resource.controller end def test_should_all_singleton_paths_be_the_same [ :path, :nesting_path_prefix, :member_path ].each do |method| - resource = ActionController::Resources::SingletonResource.new(:messages, :path_prefix => 'admin') + resource = ActionDispatch::Routing::DeprecatedMapper::SingletonResource.new(:messages, :path_prefix => 'admin') assert_equal 'admin/messages', resource.send(method) end end @@ -121,7 +121,7 @@ class ResourcesTest < ActionController::TestCase end def test_override_paths_for_default_restful_actions - resource = ActionController::Resources::Resource.new(:messages, + resource = ActionDispatch::Routing::DeprecatedMapper::Resource.new(:messages, :path_names => {:new => 'nuevo', :edit => 'editar'}) assert_equal resource.new_path, "#{resource.path}/nuevo" end @@ -135,7 +135,7 @@ class ResourcesTest < ActionController::TestCase def test_with_custom_conditions with_restful_routing :messages, :conditions => { :subdomain => 'app' } do - assert ActionController::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app') + assert ActionDispatch::Routing::Routes.recognize_path("/messages", :method => :get, :subdomain => 'app') end end @@ -281,7 +281,7 @@ class ResourcesTest < ActionController::TestCase def test_with_member_action_and_requirement expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'} - + with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get) end @@ -701,8 +701,8 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_head_method_for_member_routes with_routing do |set| - set.draw do |map| - assert_raise(ArgumentError) do + assert_raise(ArgumentError) do + set.draw do |map| map.resources :messages, :member => {:something => :head} end end @@ -711,8 +711,8 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_http_methods_for_member_routes with_routing do |set| - set.draw do |map| - assert_raise(ArgumentError) do + assert_raise(ArgumentError) do + set.draw do |map| map.resources :messages, :member => {:something => :invalid} end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index edf243337f..3971aacadb 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -11,24 +11,15 @@ end ROUTING = ActionController::Routing -class ROUTING::RouteBuilder - attr_reader :warn_output - - def warn(msg) - (@warn_output ||= []) << msg - end -end - # See RFC 3986, section 3.3 for allowed path characters. class UriReservedCharactersRoutingTest < Test::Unit::TestCase def setup - ActionController::Routing.use_controllers! ['controller'] @set = ActionController::Routing::RouteSet.new @set.draw do |map| map.connect ':controller/:action/:variable/*additional' end - safe, unsafe = %w(: @ & = + $ , ;), %w(^ / ? # [ ]) + safe, unsafe = %w(: @ & = + $ , ;), %w(^ ? # [ ]) hex = unsafe.map { |char| '%' + char.unpack('H2').first.upcase } @segment = "#{safe.join}#{unsafe.join}".freeze @@ -36,8 +27,9 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end def test_route_generation_escapes_unsafe_path_characters - assert_equal "/contr#{@segment}oller/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", - @set.generate(:controller => "contr#{@segment}oller", + @set.generate(:controller => "content", :action => "act#{@segment}ion", :variable => "variable", :additional => "foo") + assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", + @set.generate(:controller => "content", :action => "act#{@segment}ion", :variable => "var#{@segment}iable", :additional => ["add#{@segment}itional-1", "add#{@segment}itional-2"]) @@ -52,7 +44,7 @@ class UriReservedCharactersRoutingTest < Test::Unit::TestCase end def test_route_generation_allows_passing_non_string_values_to_generated_helper - assert_equal "/controller/action/variable/1/2", @set.generate(:controller => "controller", + assert_equal "/content/action/variable/1/2", @set.generate(:controller => "content", :action => "action", :variable => "variable", :additional => [1, 2]) @@ -118,8 +110,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase ActionController::Base.optimise_named_routes = true @rs = ::ActionController::Routing::RouteSet.new - - ActionController::Routing.use_controllers! %w(content admin/user admin/news_feed) end def teardown @@ -240,18 +230,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase x.send(:home_url)) end - def test_basic_named_route_with_relative_url_root - rs.draw do |map| - map.home '', :controller => 'content', :action => 'list' - end - x = setup_for_named_route - ActionController::Base.relative_url_root = "/foo" - assert_equal("http://test.host/foo/", - x.send(:home_url)) - assert_equal "/foo/", x.send(:home_path) - ActionController::Base.relative_url_root = nil - end - def test_named_route_with_option rs.draw do |map| map.page 'page/:title', :controller => 'content', :action => 'show_page' @@ -307,19 +285,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase x.send(:users_url)) end - def test_optimised_named_route_call_never_uses_url_for - rs.draw do |map| - map.users 'admin/user', :controller => '/admin/user', :action => 'index' - map.user 'admin/user/:id', :controller=>'/admin/user', :action=>'show' - end - x = setup_for_named_route - x.expects(:url_for).never - x.send(:users_url) - x.send(:users_path) - x.send(:user_url, 2, :foo=>"bar") - x.send(:user_path, 3, :bar=>"foo") - end - def test_optimised_named_route_with_host rs.draw do |map| map.pages 'pages', :controller => 'content', :action => 'show_page', :host => 'foo.com' @@ -391,10 +356,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase results = rs.recognize_path "/file/hello%20world/how%20are%20you%3F" assert results, "Recognition should have succeeded" assert_equal ['hello world', 'how are you?'], results[:path] - - results = rs.recognize_path "/file" - assert results, "Recognition should have succeeded" - assert_equal [], results[:path] end def test_paths_slashes_unescaped_with_ordered_parameters @@ -404,7 +365,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase # No / to %2F in URI, only for query params. x = setup_for_named_route - assert_equal("/file/hello/world", x.send(:path_path, 'hello/world')) + assert_equal("/file/hello/world", x.send(:path_path, ['hello', 'world'])) end def test_non_controllers_cannot_be_matched @@ -432,35 +393,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase rs.draw do |map| map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} end - exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") } - assert_match /^post_url failed to generate/, exception.message - from_match = exception.message.match(/from \{[^\}]+\}/).to_s - assert_match /:bad_param=>"foo"/, from_match - assert_match /:action=>"show"/, from_match - assert_match /:controller=>"post"/, from_match - - expected_match = exception.message.match(/expected: \{[^\}]+\}/).to_s - assert_no_match /:bad_param=>"foo"/, expected_match - assert_match /:action=>"show"/, expected_match - assert_match /:controller=>"post"/, expected_match - - diff_match = exception.message.match(/diff: \{[^\}]+\}/).to_s - assert_match /:bad_param=>"foo"/, diff_match - assert_no_match /:action=>"show"/, diff_match - assert_no_match /:controller=>"post"/, diff_match - end - - # this specifies the case where your formerly would get a very confusing error message with an empty diff - def test_should_have_better_error_message_when_options_diff_is_empty - rs.draw do |map| - map.content '/content/:query', :controller => 'content', :action => 'show' - end - - exception = assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'content', :action => 'show', :use_route => "content") } - assert_match %r[:action=>"show"], exception.message - assert_match %r[:controller=>"content"], exception.message - assert_match %r[you may have ambiguous routes, or you may need to supply additional parameters for this route], exception.message - assert_match %r[content_url has the following required parameters: \["content", :query\] - are they all satisfied?], exception.message + assert_raise(ActionController::RoutingError) { rs.generate(:controller => 'post', :action => 'show', :bad_param => "foo", :use_route => "post") } end def test_dynamic_path_allowed @@ -517,6 +450,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal({:controller => "content", :action => 'show_page', :id => 'foo'}, rs.recognize_path("/page/foo")) token = "\321\202\320\265\320\272\321\201\321\202" # 'text' in russian + token.force_encoding(Encoding::BINARY) if token.respond_to?(:force_encoding) escaped_token = CGI::escape(token) assert_equal '/page/' + escaped_token, rs.generate(:controller => 'content', :action => 'show_page', :id => token) @@ -528,17 +462,6 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal '/content', rs.generate({:controller => 'content'}, {:controller => 'content', :action => 'show'}) end - def test_recognition_with_uppercase_controller_name - @rs.draw {|m| m.connect ':controller/:action/:id' } - assert_equal({:controller => "content", :action => 'index'}, rs.recognize_path("/Content")) - assert_equal({:controller => "content", :action => 'list'}, rs.recognize_path("/ConTent/list")) - assert_equal({:controller => "content", :action => 'show', :id => '10'}, rs.recognize_path("/CONTENT/show/10")) - - # these used to work, before the routes rewrite, but support for this was pulled in the new version... - #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/NewsFeed")) - #assert_equal({'controller' => "admin/news_feed", 'action' => 'index'}, rs.recognize_path("Admin/News_Feed")) - end - def test_requirement_should_prevent_optional_id rs.draw do |map| map.post 'post/:id', :controller=> 'post', :action=> 'show', :requirements => {:id => /\d+/} @@ -785,12 +708,9 @@ class RouteSetTest < ActiveSupport::TestCase def default_route_set @default_route_set ||= begin - set = nil - ActionController::Routing.with_controllers(['accounts']) do - set = ROUTING::RouteSet.new - set.draw do |map| - map.connect '/:controller/:action/:id/' - end + set = ROUTING::RouteSet.new + set.draw do |map| + map.connect '/:controller/:action/:id/' end set end @@ -978,47 +898,38 @@ class RouteSetTest < ActiveSupport::TestCase end def test_draw_default_route - ActionController::Routing.with_controllers(['users']) do - set.draw do |map| - map.connect '/:controller/:action/:id' - end - - assert_equal 1, set.routes.size - route = set.routes.first + set.draw do |map| + map.connect '/:controller/:action/:id' + end - assert route.segments.last.optional? + assert_equal 1, set.routes.size - assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) - assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) + assert_equal '/users/show/10', set.generate(:controller => 'users', :action => 'show', :id => 10) + assert_equal '/users/index/10', set.generate(:controller => 'users', :id => 10) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) - assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) - end + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10')) + assert_equal({:controller => 'users', :action => 'index', :id => '10'}, set.recognize_path('/users/index/10/')) end def test_draw_default_route_with_default_controller - ActionController::Routing.with_controllers(['users']) do - set.draw do |map| - map.connect '/:controller/:action/:id', :controller => 'users' - end - assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/')) + set.draw do |map| + map.connect '/:controller/:action/:id', :controller => 'users' end + assert_equal({:controller => 'users', :action => 'index'}, set.recognize_path('/')) end def test_route_with_parameter_shell - ActionController::Routing.with_controllers(['users', 'pages']) do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ - map.connect '/:controller/:action/:id' - end + set.draw do |map| + map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/ + map.connect '/:controller/:action/:id' + end - assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) - assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) - assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages')) + assert_equal({:controller => 'pages', :action => 'index'}, set.recognize_path('/pages/index')) + assert_equal({:controller => 'pages', :action => 'list'}, set.recognize_path('/pages/list')) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) - assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) - end + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/pages/show/10')) + assert_equal({:controller => 'pages', :action => 'show', :id => '10'}, set.recognize_path('/page/10')) end def test_route_requirements_with_anchor_chars_are_invalid @@ -1047,14 +958,6 @@ class RouteSetTest < ActiveSupport::TestCase map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ end end - assert_nothing_raised do - set.draw do |map| - map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ - end - assert_raise ActionController::RoutingError do - set.generate :controller => 'pages', :action => 'show', :id => 10 - end - end end def test_route_requirements_with_invalid_http_method_is_invalid @@ -1081,19 +984,6 @@ class RouteSetTest < ActiveSupport::TestCase end end - def test_non_path_route_requirements_match_all - set.draw do |map| - map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ - end - assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') - assert_raise ActionController::RoutingError do - set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') - end - assert_raise ActionController::RoutingError do - set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') - end - end - def test_recognize_with_encoded_id_and_regex set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /[a-zA-Z0-9\+]+/ @@ -1331,16 +1221,16 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal "/foo/bar/baz/7", url end - def test_id_is_not_impossibly_sticky - set.draw do |map| - map.connect 'foo/:number', :controller => "people", :action => "index" - map.connect ':controller/:action/:id' - end - - url = set.generate({:controller => "people", :action => "index", :number => 3}, - {:controller => "people", :action => "index", :id => "21"}) - assert_equal "/foo/3", url - end + # def test_id_is_not_impossibly_sticky + # set.draw do |map| + # map.connect 'foo/:number', :controller => "people", :action => "index" + # map.connect ':controller/:action/:id' + # end + # + # url = set.generate({:controller => "people", :action => "index", :number => 3}, + # {:controller => "people", :action => "index", :id => "21"}) + # assert_equal "/foo/3", url + # end def test_id_is_sticky_when_it_ought_to_be set.draw do |map| @@ -1403,20 +1293,20 @@ class RouteSetTest < ActiveSupport::TestCase set.draw do |map| map.connect ':controller/:action/:id' end - assert_equal '/post', set.generate( - {:controller => 'post', :action => 'index'}, - {:controller => 'post', :action => 'show', :id => '10'} + assert_equal '/books', set.generate( + {:controller => 'books', :action => 'index'}, + {:controller => 'books', :action => 'show', :id => '10'} ) end def test_query_params_will_be_shown_when_recalled set.draw do |map| - map.connect 'show_post/:parameter', :controller => 'post', :action => 'show' + map.connect 'show_weblog/:parameter', :controller => 'weblog', :action => 'show' map.connect ':controller/:action/:id' end - assert_equal '/post/edit?parameter=1', set.generate( + assert_equal '/weblog/edit?parameter=1', set.generate( {:action => 'edit', :parameter => 1}, - {:controller => 'post', :action => 'show', :parameter => 1} + {:controller => 'weblog', :action => 'show', :parameter => 1} ) end @@ -1438,23 +1328,9 @@ class RouteSetTest < ActiveSupport::TestCase def test_expiry_determination_should_consider_values_with_to_param set.draw { |map| map.connect 'projects/:project_id/:controller/:action' } - assert_equal '/projects/1/post/show', set.generate( + assert_equal '/projects/1/weblog/show', set.generate( {:action => 'show', :project_id => 1}, - {:controller => 'post', :action => 'show', :project_id => '1'}) - end - - def test_generate_all - set.draw do |map| - map.connect 'show_post/:id', :controller => 'post', :action => 'show' - map.connect ':controller/:action/:id' - end - all = set.generate( - {:action => 'show', :id => 10, :generate_all => true}, - {:controller => 'post', :action => 'show'} - ) - assert_equal 2, all.length - assert_equal '/show_post/10', all.first - assert_equal '/post/show/10', all.last + {:controller => 'weblog', :action => 'show', :project_id => '1'}) end def test_named_route_in_nested_resource @@ -1631,101 +1507,53 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal({:controller => 'pages', :action => 'show', :name => :as_symbol}, set.recognize_path('/named')) end - - def test_interpolation_chunk_should_respect_raw - ActionController::Routing.with_controllers(['hello']) do - set.draw do |map| - map.connect '/Hello World', :controller => 'hello' - end - - assert_equal '/Hello%20World', set.generate(:controller => 'hello') - assert_equal({:controller => "hello", :action => "index"}, set.recognize_path('/Hello World')) - assert_raise(ActionController::RoutingError) { set.recognize_path('/Hello%20World') } - end - end - - def test_value_should_not_be_double_unescaped - ActionController::Routing.with_controllers(['foo']) do - set.draw do |map| - map.connect '/Карта', :controller => 'foo' - end - - assert_equal '/%D0%9A%D0%B0%D1%80%D1%82%D0%B0', set.generate(:controller => 'foo') - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Карта')) - assert_raise(ActionController::RoutingError) { set.recognize_path('/%D0%9A%D0%B0%D1%80%D1%82%D0%B0') } - end - end - - def test_regexp_chunk_should_escape_specials - ActionController::Routing.with_controllers(['foo', 'bar']) do - set.draw do |map| - map.connect '/Hello*World', :controller => 'foo' - map.connect '/HelloWorld', :controller => 'bar' - end - - assert_equal '/Hello*World', set.generate(:controller => 'foo') - assert_equal '/HelloWorld', set.generate(:controller => 'bar') - - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/Hello*World')) - assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/HelloWorld')) - end - end - def test_regexp_chunk_should_add_question_mark_for_optionals - ActionController::Routing.with_controllers(['foo', 'bar']) do - set.draw do |map| - map.connect '/', :controller => 'foo' - map.connect '/hello', :controller => 'bar' - end + set.draw do |map| + map.connect '/', :controller => 'foo' + map.connect '/hello', :controller => 'bar' + end - assert_equal '/', set.generate(:controller => 'foo') - assert_equal '/hello', set.generate(:controller => 'bar') + assert_equal '/', set.generate(:controller => 'foo') + assert_equal '/hello', set.generate(:controller => 'bar') - assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/')) - assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello')) - end + assert_equal({:controller => "foo", :action => "index"}, set.recognize_path('/')) + assert_equal({:controller => "bar", :action => "index"}, set.recognize_path('/hello')) end def test_assign_route_options_with_anchor_chars - ActionController::Routing.with_controllers(['cars']) do - set.draw do |map| - map.connect '/cars/:action/:person/:car/', :controller => 'cars' - end + set.draw do |map| + map.connect '/cars/:action/:person/:car/', :controller => 'cars' + end - assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2') + assert_equal '/cars/buy/1/2', set.generate(:controller => 'cars', :action => 'buy', :person => '1', :car => '2') - assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2')) - end + assert_equal({:controller => "cars", :action => "buy", :person => "1", :car => "2"}, set.recognize_path('/cars/buy/1/2')) end def test_segmentation_of_dot_path - ActionController::Routing.with_controllers(['books']) do - set.draw do |map| - map.connect '/books/:action.rss', :controller => 'books' - end + set.draw do |map| + map.connect '/books/:action.rss', :controller => 'books' + end - assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list') + assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list') - assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss')) - end + assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list.rss')) end def test_segmentation_of_dynamic_dot_path - ActionController::Routing.with_controllers(['books']) do - set.draw do |map| - map.connect '/books/:action.:format', :controller => 'books' - end + set.draw do |map| + map.connect '/books/:action.:format', :controller => 'books' + end - assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss') - assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml') - assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list') - assert_equal '/books', set.generate(:controller => 'books', :action => 'index') + assert_equal '/books/list.rss', set.generate(:controller => 'books', :action => 'list', :format => 'rss') + assert_equal '/books/list.xml', set.generate(:controller => 'books', :action => 'list', :format => 'xml') + assert_equal '/books/list', set.generate(:controller => 'books', :action => 'list') + assert_equal '/books', set.generate(:controller => 'books', :action => 'index') - assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss')) - assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml')) - assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list')) - assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books')) - end + assert_equal({:controller => "books", :action => "list", :format => "rss"}, set.recognize_path('/books/list.rss')) + assert_equal({:controller => "books", :action => "list", :format => "xml"}, set.recognize_path('/books/list.xml')) + assert_equal({:controller => "books", :action => "list"}, set.recognize_path('/books/list')) + assert_equal({:controller => "books", :action => "index"}, set.recognize_path('/books')) end def test_slashes_are_implied @@ -1780,7 +1608,6 @@ class RouteSetTest < ActiveSupport::TestCase def test_default_route_should_uri_escape_pluses expected = { :controller => 'pages', :action => 'show', :id => 'hello world' } - assert_equal expected, default_route_set.recognize_path('/pages/show/hello world') assert_equal expected, default_route_set.recognize_path('/pages/show/hello%20world') assert_equal '/pages/show/hello%20world', default_route_set.generate(expected, expected) @@ -1790,52 +1617,96 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal '/pages/show/hello+world', default_route_set.generate(expected, expected) end - def test_parameter_shell - page_url = ROUTING::Route.new - page_url.requirements = {:controller => 'pages', :action => 'show', :id => /\d+/} - assert_equal({:controller => 'pages', :action => 'show'}, page_url.parameter_shell) - end - - def test_defaults - route = ROUTING::RouteBuilder.new.build '/users/:id.:format', :controller => "users", :action => "show", :format => "html" - assert_equal( - { :controller => "users", :action => "show", :format => "html" }, - route.defaults) - end - - def test_builder_complains_without_controller - assert_raise(ArgumentError) do - ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index" - end - end - def test_build_empty_query_string - assert_equal '/foo', default_route_set.generate({:controller => 'foo'}) + assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo'}) end def test_build_query_string_with_nil_value - assert_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil}) + assert_uri_equal '/foo', default_route_set.generate({:controller => 'foo', :x => nil}) end def test_simple_build_query_string - assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'}) + assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => '1', :y => '2'}) end def test_convert_ints_build_query_string - assert_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2}) + assert_uri_equal '/foo?x=1&y=2', default_route_set.generate({:controller => 'foo', :x => 1, :y => 2}) end def test_escape_spaces_build_query_string - assert_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'}) + assert_uri_equal '/foo?x=hello+world&y=goodbye+world', default_route_set.generate({:controller => 'foo', :x => 'hello world', :y => 'goodbye world'}) end def test_expand_array_build_query_string - assert_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) + assert_uri_equal '/foo?x%5B%5D=1&x%5B%5D=2', default_route_set.generate({:controller => 'foo', :x => [1, 2]}) end def test_escape_spaces_build_query_string_selected_keys - assert_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'}) + assert_uri_equal '/foo?x=hello+world', default_route_set.generate({:controller => 'foo', :x => 'hello world'}) + end + + def test_generate_with_default_params + set.draw do |map| + map.connect 'dummy/page/:page', :controller => 'dummy' + map.connect 'dummy/dots/page.:page', :controller => 'dummy', :action => 'dots' + map.connect 'ibocorp/:page', :controller => 'ibocorp', + :requirements => { :page => /\d+/ }, + :defaults => { :page => 1 } + + map.connect ':controller/:action/:id' + end + + pending do + assert_equal '/ibocorp', set.generate({:controller => 'ibocorp', :page => 1}) + end + end + + def test_generate_with_optional_params_recalls_last_request + set.draw do |map| + map.connect "blog/", :controller => "blog", :action => "index" + + map.connect "blog/:year/:month/:day", + :controller => "blog", + :action => "show_date", + :requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/ }, + :day => nil, :month => nil + + map.connect "blog/show/:id", :controller => "blog", :action => "show", :id => /\d+/ + map.connect "blog/:controller/:action/:id" + map.connect "*anything", :controller => "blog", :action => "unknown_request" + end + + assert_equal({:controller => "blog", :action => "index"}, set.recognize_path("/blog")) + assert_equal({:controller => "blog", :action => "show", :id => "123"}, set.recognize_path("/blog/show/123")) + assert_equal({:controller => "blog", :action => "show_date", :year => "2004"}, set.recognize_path("/blog/2004")) + assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12"}, set.recognize_path("/blog/2004/12")) + assert_equal({:controller => "blog", :action => "show_date", :year => "2004", :month => "12", :day => "25"}, set.recognize_path("/blog/2004/12/25")) + assert_equal({:controller => "articles", :action => "edit", :id => "123"}, set.recognize_path("/blog/articles/edit/123")) + assert_equal({:controller => "articles", :action => "show_stats"}, set.recognize_path("/blog/articles/show_stats")) + assert_equal({:controller => "blog", :action => "unknown_request", :anything => ["blog", "wibble"]}, set.recognize_path("/blog/wibble")) + assert_equal({:controller => "blog", :action => "unknown_request", :anything => ["junk"]}, set.recognize_path("/junk")) + + last_request = set.recognize_path("/blog/2006/07/28").freeze + assert_equal({:controller => "blog", :action => "show_date", :year => "2006", :month => "07", :day => "28"}, last_request) + assert_equal("/blog/2006/07/25", set.generate({:day => 25}, last_request)) + assert_equal("/blog/2005", set.generate({:year => 2005}, last_request)) + assert_equal("/blog/show/123", set.generate({:action => "show" , :id => 123}, last_request)) + pending do + assert_equal("/blog/2006/07/28", set.generate({:year => 2006}, last_request)) + end + assert_equal("/blog/2006", set.generate({:year => 2006, :month => nil}, last_request)) end + + private + def assert_uri_equal(expected, actual) + assert_equal(sort_query_string_params(expected), sort_query_string_params(actual)) + end + + def sort_query_string_params(uri) + path, qs = uri.split('?') + qs = qs.split('&').sort.join('&') if qs + qs ? "#{path}?#{qs}" : path + end end class RouteLoadingTest < Test::Unit::TestCase @@ -1916,3 +1787,315 @@ class RouteLoadingTest < Test::Unit::TestCase ActionController::Routing::Routes end end + +class RackMountIntegrationTests < ActiveSupport::TestCase + Model = Struct.new(:to_param) + + Mapping = lambda { |map| + map.namespace :admin do |admin| + admin.resources :users + end + + map.namespace 'api' do |api| + api.root :controller => 'users' + end + + map.connect 'blog/:year/:month/:day', + :controller => 'posts', + :action => 'show_date', + :requirements => { :year => /(19|20)\d\d/, :month => /[01]?\d/, :day => /[0-3]?\d/}, + :day => nil, + :month => nil + + map.blog('archive/:year', :controller => 'archive', :action => 'index', + :defaults => { :year => nil }, + :requirements => { :year => /\d{4}/ } + ) + + map.resources :people + map.connect 'legacy/people', :controller => 'people', :action => 'index', :legacy => 'true' + + map.connect 'symbols', :controller => :symbols, :action => :show, :name => :as_symbol + map.connect 'id_default/:id', :controller => 'foo', :action => 'id_default', :id => 1 + map.connect 'get_or_post', :controller => 'foo', :action => 'get_or_post', :conditions => { :method => [:get, :post] } + map.connect 'optional/:optional', :controller => 'posts', :action => 'index' + map.project 'projects/:project_id', :controller => 'project' + map.connect 'clients', :controller => 'projects', :action => 'index' + + map.connect 'ignorecase/geocode/:postalcode', :controller => 'geocode', + :action => 'show', :postalcode => /hx\d\d-\d[a-z]{2}/i + map.geocode 'extended/geocode/:postalcode', :controller => 'geocode', + :action => 'show',:requirements => { + :postalcode => /# Postcode format + \d{5} #Prefix + (-\d{4})? #Suffix + /x + } + + map.connect '', :controller => 'news', :format => nil + map.connect 'news.:format', :controller => 'news' + + map.connect 'comment/:id/:action', :controller => 'comments', :action => 'show' + map.connect 'ws/:controller/:action/:id', :ws => true + map.connect 'account/:action', :controller => :account, :action => :subscription + map.connect 'pages/:page_id/:controller/:action/:id' + map.connect ':controller/ping', :action => 'ping' + map.connect ':controller/:action/:id' + } + + def setup + @routes = ActionController::Routing::RouteSet.new + @routes.draw(&Mapping) + end + + def test_add_route + @routes.clear! + + assert_raise(ActionController::RoutingError) do + @routes.draw do |map| + map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) + map.connect ':controller/:action/:id' + end + end + end + + def test_recognize_path + assert_equal({:controller => 'admin/users', :action => 'index'}, @routes.recognize_path('/admin/users', :method => :get)) + assert_equal({:controller => 'admin/users', :action => 'create'}, @routes.recognize_path('/admin/users', :method => :post)) + assert_equal({:controller => 'admin/users', :action => 'new'}, @routes.recognize_path('/admin/users/new', :method => :get)) + assert_equal({:controller => 'admin/users', :action => 'show', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :get)) + assert_equal({:controller => 'admin/users', :action => 'update', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :put)) + assert_equal({:controller => 'admin/users', :action => 'destroy', :id => '1'}, @routes.recognize_path('/admin/users/1', :method => :delete)) + assert_equal({:controller => 'admin/users', :action => 'edit', :id => '1'}, @routes.recognize_path('/admin/users/1/edit', :method => :get)) + + assert_equal({:controller => 'admin/posts', :action => 'index'}, @routes.recognize_path('/admin/posts', :method => :get)) + assert_equal({:controller => 'admin/posts', :action => 'new'}, @routes.recognize_path('/admin/posts/new', :method => :get)) + + assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api', :method => :get)) + assert_equal({:controller => 'api/users', :action => 'index'}, @routes.recognize_path('/api/', :method => :get)) + + assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009'}, @routes.recognize_path('/blog/2009', :method => :get)) + assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01'}, @routes.recognize_path('/blog/2009/01', :method => :get)) + assert_equal({:controller => 'posts', :action => 'show_date', :year => '2009', :month => '01', :day => '01'}, @routes.recognize_path('/blog/2009/01/01', :method => :get)) + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/blog/123456789', :method => :get) } + + assert_equal({:controller => 'archive', :action => 'index', :year => '2010'}, @routes.recognize_path('/archive/2010')) + assert_equal({:controller => 'archive', :action => 'index'}, @routes.recognize_path('/archive')) + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/archive/january') } + + assert_equal({:controller => 'people', :action => 'index'}, @routes.recognize_path('/people', :method => :get)) + assert_equal({:controller => 'people', :action => 'index', :format => 'xml'}, @routes.recognize_path('/people.xml', :method => :get)) + assert_equal({:controller => 'people', :action => 'create'}, @routes.recognize_path('/people', :method => :post)) + assert_equal({:controller => 'people', :action => 'new'}, @routes.recognize_path('/people/new', :method => :get)) + assert_equal({:controller => 'people', :action => 'show', :id => '1'}, @routes.recognize_path('/people/1', :method => :get)) + assert_equal({:controller => 'people', :action => 'show', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1.xml', :method => :get)) + assert_equal({:controller => 'people', :action => 'update', :id => '1'}, @routes.recognize_path('/people/1', :method => :put)) + assert_equal({:controller => 'people', :action => 'destroy', :id => '1'}, @routes.recognize_path('/people/1', :method => :delete)) + assert_equal({:controller => 'people', :action => 'edit', :id => '1'}, @routes.recognize_path('/people/1/edit', :method => :get)) + assert_equal({:controller => 'people', :action => 'edit', :id => '1', :format => 'xml'}, @routes.recognize_path('/people/1/edit.xml', :method => :get)) + + assert_equal({:controller => 'symbols', :action => 'show', :name => :as_symbol}, @routes.recognize_path('/symbols')) + assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default/1')) + assert_equal({:controller => 'foo', :action => 'id_default', :id => '2'}, @routes.recognize_path('/id_default/2')) + assert_equal({:controller => 'foo', :action => 'id_default', :id => '1'}, @routes.recognize_path('/id_default')) + assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :get)) + assert_equal({:controller => 'foo', :action => 'get_or_post'}, @routes.recognize_path('/get_or_post', :method => :post)) + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :put) } + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/get_or_post', :method => :delete) } + + assert_equal({:controller => 'posts', :action => 'index', :optional => 'bar'}, @routes.recognize_path('/optional/bar')) + assert_raise(ActionController::ActionControllerError) { @routes.recognize_path('/optional') } + + assert_equal({:controller => 'posts', :action => 'show', :id => '1', :ws => true}, @routes.recognize_path('/ws/posts/show/1', :method => :get)) + assert_equal({:controller => 'posts', :action => 'list', :ws => true}, @routes.recognize_path('/ws/posts/list', :method => :get)) + assert_equal({:controller => 'posts', :action => 'index', :ws => true}, @routes.recognize_path('/ws/posts', :method => :get)) + + assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account', :method => :get)) + assert_equal({:controller => 'account', :action => 'subscription'}, @routes.recognize_path('/account/subscription', :method => :get)) + assert_equal({:controller => 'account', :action => 'billing'}, @routes.recognize_path('/account/billing', :method => :get)) + + assert_equal({:page_id => '1', :controller => 'notes', :action => 'index'}, @routes.recognize_path('/pages/1/notes', :method => :get)) + assert_equal({:page_id => '1', :controller => 'notes', :action => 'list'}, @routes.recognize_path('/pages/1/notes/list', :method => :get)) + assert_equal({:page_id => '1', :controller => 'notes', :action => 'show', :id => '2'}, @routes.recognize_path('/pages/1/notes/show/2', :method => :get)) + + assert_equal({:controller => 'posts', :action => 'ping'}, @routes.recognize_path('/posts/ping', :method => :get)) + assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts', :method => :get)) + assert_equal({:controller => 'posts', :action => 'index'}, @routes.recognize_path('/posts/index', :method => :get)) + assert_equal({:controller => 'posts', :action => 'show'}, @routes.recognize_path('/posts/show', :method => :get)) + assert_equal({:controller => 'posts', :action => 'show', :id => '1'}, @routes.recognize_path('/posts/show/1', :method => :get)) + assert_equal({:controller => 'posts', :action => 'create'}, @routes.recognize_path('/posts/create', :method => :post)) + + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1az'}, @routes.recognize_path('/ignorecase/geocode/hx12-1az')) + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => 'hx12-1AZ'}, @routes.recognize_path('/ignorecase/geocode/hx12-1AZ')) + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345-1234'}, @routes.recognize_path('/extended/geocode/12345-1234')) + assert_equal({:controller => 'geocode', :action => 'show', :postalcode => '12345'}, @routes.recognize_path('/extended/geocode/12345')) + + assert_equal({:controller => 'news', :action => 'index', :format => nil}, @routes.recognize_path('/', :method => :get)) + assert_equal({:controller => 'news', :action => 'index', :format => 'rss'}, @routes.recognize_path('/news.rss', :method => :get)) + + assert_raise(ActionController::RoutingError) { @routes.recognize_path('/none', :method => :get) } + end + + def test_generate + assert_equal '/admin/users', @routes.generate(:use_route => 'admin_users') + assert_equal '/admin/users', @routes.generate(:controller => 'admin/users') + assert_equal '/admin/users', @routes.generate(:controller => 'admin/users', :action => 'index') + assert_equal '/admin/users', @routes.generate({:action => 'index'}, {:controller => 'admin/users'}) + assert_equal '/admin/users', @routes.generate({:controller => 'users', :action => 'index'}, {:controller => 'admin/accounts'}) + assert_equal '/people', @routes.generate({:controller => '/people', :action => 'index'}, {:controller => 'admin/accounts'}) + + assert_equal '/admin/posts', @routes.generate({:controller => 'admin/posts'}) + assert_equal '/admin/posts/new', @routes.generate({:controller => 'admin/posts', :action => 'new'}) + + assert_equal '/blog/2009', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009) + assert_equal '/blog/2009/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1) + assert_equal '/blog/2009/1/1', @routes.generate(:controller => 'posts', :action => 'show_date', :year => 2009, :month => 1, :day => 1) + + assert_equal '/archive/2010', @routes.generate(:controller => 'archive', :action => 'index', :year => '2010') + assert_equal '/archive', @routes.generate(:controller => 'archive', :action => 'index') + assert_equal '/archive?year=january', @routes.generate(:controller => 'archive', :action => 'index', :year => 'january') + + assert_equal '/people', @routes.generate(:use_route => 'people') + assert_equal '/people', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index') + assert_equal '/people.xml', @routes.generate(:use_route => 'people', :controller => 'people', :action => 'index', :format => 'xml') + assert_equal '/people', @routes.generate({:use_route => 'people', :controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index'}) + assert_equal '/people', @routes.generate(:controller => 'people') + assert_equal '/people', @routes.generate(:controller => 'people', :action => 'index') + assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people'}) + assert_equal '/people', @routes.generate({:action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people', @routes.generate({}, {:controller => 'people', :action => 'index'}) + assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people/new', @routes.generate(:use_route => 'new_person') + assert_equal '/people/new', @routes.generate(:controller => 'people', :action => 'new') + assert_equal '/people/1', @routes.generate(:use_route => 'person', :id => '1') + assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => '1') + assert_equal '/people/1.xml', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :format => 'xml') + assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => 1) + assert_equal '/people/1', @routes.generate(:controller => 'people', :action => 'show', :id => Model.new('1')) + assert_equal '/people/1', @routes.generate({:action => 'show', :id => '1'}, {:controller => 'people', :action => 'index'}) + assert_equal '/people/1', @routes.generate({:action => 'show', :id => 1}, {:controller => 'people', :action => 'show', :id => '1'}) + # assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'index', :id => '1'}) + assert_equal '/people', @routes.generate({:controller => 'people', :action => 'index'}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people/1', @routes.generate({}, {:controller => 'people', :action => 'show', :id => '1'}) + assert_equal '/people/1', @routes.generate({:controller => 'people', :action => 'show'}, {:controller => 'people', :action => 'index', :id => '1'}) + assert_equal '/people/1/edit', @routes.generate(:controller => 'people', :action => 'edit', :id => '1') + assert_equal '/people/1/edit.xml', @routes.generate(:controller => 'people', :action => 'edit', :id => '1', :format => 'xml') + assert_equal '/people/1/edit', @routes.generate(:use_route => 'edit_person', :id => '1') + assert_equal '/people/1?legacy=true', @routes.generate(:controller => 'people', :action => 'show', :id => '1', :legacy => 'true') + assert_equal '/people?legacy=true', @routes.generate(:controller => 'people', :action => 'index', :legacy => 'true') + + assert_equal '/id_default/2', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '2') + assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => '1') + assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default', :id => 1) + assert_equal '/id_default', @routes.generate(:controller => 'foo', :action => 'id_default') + assert_equal '/optional/bar', @routes.generate(:controller => 'posts', :action => 'index', :optional => 'bar') + assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index') + + assert_equal '/project', @routes.generate({:controller => 'project', :action => 'index'}) + assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index', :project_id => '1'}) + assert_equal '/projects/1', @routes.generate({:controller => 'project', :action => 'index'}, {:project_id => '1'}) + assert_raise(ActionController::RoutingError) { @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}) } + assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index', :project_id => '1'}) + assert_equal '/projects/1', @routes.generate({:use_route => 'project', :controller => 'project', :action => 'index'}, {:project_id => '1'}) + + assert_equal '/clients', @routes.generate(:controller => 'projects', :action => 'index') + assert_equal '/clients?project_id=1', @routes.generate(:controller => 'projects', :action => 'index', :project_id => '1') + assert_equal '/clients', @routes.generate({:controller => 'projects', :action => 'index'}, {:project_id => '1'}) + assert_equal '/clients', @routes.generate({:action => 'index'}, {:controller => 'projects', :action => 'index', :project_id => '1'}) + + assert_equal '/comment/20', @routes.generate({:id => 20}, {:controller => 'comments', :action => 'show'}) + assert_equal '/comment/20', @routes.generate(:controller => 'comments', :id => 20, :action => 'show') + assert_equal '/comments/boo', @routes.generate(:controller => 'comments', :action => 'boo') + + assert_equal '/ws/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1', :ws => true) + assert_equal '/ws/posts', @routes.generate(:controller => 'posts', :action => 'index', :ws => true) + + assert_equal '/account', @routes.generate(:controller => 'account', :action => 'subscription') + assert_equal '/account/billing', @routes.generate(:controller => 'account', :action => 'billing') + + assert_equal '/pages/1/notes/show/1', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'show', :id => '1') + assert_equal '/pages/1/notes/list', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'list') + assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes', :action => 'index') + assert_equal '/pages/1/notes', @routes.generate(:page_id => '1', :controller => 'notes') + assert_equal '/notes', @routes.generate(:page_id => nil, :controller => 'notes') + assert_equal '/notes', @routes.generate(:controller => 'notes') + assert_equal '/notes/print', @routes.generate(:controller => 'notes', :action => 'print') + assert_equal '/notes/print', @routes.generate({}, {:controller => 'notes', :action => 'print'}) + + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1', :foo => 'bar'}) + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes'}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({:action => 'index'}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({}, {:controller => 'notes', :id => '1'}) + assert_equal '/notes/show/1', @routes.generate({}, {:controller => 'notes', :action => 'show', :id => '1'}) + assert_equal '/notes/index/1', @routes.generate({:controller => 'notes', :id => '1'}, {:foo => 'bar'}) + assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'notes', :action => 'show', :id => '1'}) + assert_equal '/notes/list', @routes.generate({:action => 'list'}, {:controller => 'notes', :action => 'show', :id => '1'}) + + assert_equal '/posts/ping', @routes.generate(:controller => 'posts', :action => 'ping') + assert_equal '/posts/show/1', @routes.generate(:controller => 'posts', :action => 'show', :id => '1') + assert_equal '/posts', @routes.generate(:controller => 'posts') + assert_equal '/posts', @routes.generate(:controller => 'posts', :action => 'index') + assert_equal '/posts', @routes.generate({:controller => 'posts'}, {:controller => 'posts', :action => 'index'}) + assert_equal '/posts/create', @routes.generate({:action => 'create'}, {:controller => 'posts'}) + assert_equal '/posts?foo=bar', @routes.generate(:controller => 'posts', :foo => 'bar') + assert_equal '/posts?foo%5B%5D=bar&foo%5B%5D=baz', @routes.generate(:controller => 'posts', :foo => ['bar', 'baz']) + assert_equal '/posts?page=2', @routes.generate(:controller => 'posts', :page => 2) + assert_equal '/posts?q%5Bfoo%5D%5Ba%5D=b', @routes.generate(:controller => 'posts', :q => { :foo => { :a => 'b'}}) + + assert_equal '/', @routes.generate(:controller => 'news', :action => 'index') + assert_equal '/', @routes.generate(:controller => 'news', :action => 'index', :format => nil) + assert_equal '/news.rss', @routes.generate(:controller => 'news', :action => 'index', :format => 'rss') + + + assert_raise(ActionController::RoutingError) { @routes.generate({:action => 'index'}) } + end + + def test_generate_extras + assert_equal ['/people', []], @routes.generate_extras(:controller => 'people') + assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :foo => 'bar') + assert_equal ['/people', []], @routes.generate_extras(:controller => 'people', :action => 'index') + assert_equal ['/people', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'index', :foo => 'bar') + assert_equal ['/people/new', []], @routes.generate_extras(:controller => 'people', :action => 'new') + assert_equal ['/people/new', [:foo]], @routes.generate_extras(:controller => 'people', :action => 'new', :foo => 'bar') + assert_equal ['/people/1', []], @routes.generate_extras(:controller => 'people', :action => 'show', :id => '1') + assert_equal ['/people/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'people', :action => 'show', :id => '1', :foo => '2', :bar => '3')) + assert_equal ['/people', [:person]], @routes.generate_extras(:controller => 'people', :action => 'create', :person => { :first_name => 'Josh', :last_name => 'Peek' }) + assert_equal ['/people', [:people]], @routes.generate_extras(:controller => 'people', :action => 'create', :people => ['Josh', 'Dave']) + + assert_equal ['/posts/show/1', []], @routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1') + assert_equal ['/posts/show/1', [:bar, :foo]], sort_extras!(@routes.generate_extras(:controller => 'posts', :action => 'show', :id => '1', :foo => '2', :bar => '3')) + assert_equal ['/posts', []], @routes.generate_extras(:controller => 'posts', :action => 'index') + assert_equal ['/posts', [:foo]], @routes.generate_extras(:controller => 'posts', :action => 'index', :foo => 'bar') + end + + def test_extras + params = {:controller => 'people'} + assert_equal [], @routes.extra_keys(params) + assert_equal({:controller => 'people'}, params) + + params = {:controller => 'people', :foo => 'bar'} + assert_equal [:foo], @routes.extra_keys(params) + assert_equal({:controller => 'people', :foo => 'bar'}, params) + + params = {:controller => 'people', :action => 'create', :person => { :name => 'Josh'}} + assert_equal [:person], @routes.extra_keys(params) + assert_equal({:controller => 'people', :action => 'create', :person => { :name => 'Josh'}}, params) + end + + private + def sort_extras!(extras) + if extras.length == 2 + extras[1].sort! { |a, b| a.to_s <=> b.to_s } + end + extras + end + + def assert_raise(e) + result = yield + flunk "Did not raise #{e}, but returned #{result.inspect}" + rescue e + assert true + end +end diff --git a/actionpack/test/controller/test_test.rb b/actionpack/test/controller/test_test.rb index 73870a56bb..375878b755 100644 --- a/actionpack/test/controller/test_test.rb +++ b/actionpack/test/controller/test_test.rb @@ -108,6 +108,11 @@ XML head :created, :location => 'created resource' end + def delete_cookie + cookies.delete("foo") + render :nothing => true + end + private def rescue_action(e) raise e @@ -512,6 +517,18 @@ XML assert @request.params[:foo].blank? end + def test_should_have_knowledge_of_client_side_cookie_state_even_if_they_are_not_set + @request.cookies['foo'] = 'bar' + get :no_op + assert_equal 'bar', cookies['foo'] + end + + def test_should_detect_if_cookie_is_deleted + @request.cookies['foo'] = 'bar' + get :delete_cookie + assert_nil cookies['foo'] + end + %w(controller response request).each do |variable| %w(get post put delete head process).each do |method| define_method("test_#{variable}_missing_for_#{method}_raises_error") do diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 4c4bf9ade4..3b14cbb2d8 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -224,9 +224,8 @@ class UrlWriterTests < ActionController::TestCase def test_named_routes with_routing do |set| set.draw do |map| - map.no_args '/this/is/verbose', :controller => 'home', :action => 'index' - map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index' - map.connect ':controller/:action/:id' + match 'this/is/verbose', :to => 'home#index', :as => :no_args + match 'home/sweet/home/:user', :to => 'home#index', :as => :home end # We need to create a new class in order to install the new named route. @@ -264,7 +263,7 @@ class UrlWriterTests < ActionController::TestCase def test_only_path with_routing do |set| set.draw do |map| - map.home '/home/sweet/home/:user', :controller => 'home', :action => 'index' + match 'home/sweet/home/:user', :to => 'home#index', :as => :home map.connect ':controller/:action/:id' end @@ -321,8 +320,8 @@ class UrlWriterTests < ActionController::TestCase params = extract_params(url) assert_equal params[0], { 'query[hobby]' => 'piercing' }.to_query assert_equal params[1], { 'query[person][name]' => 'Bob' }.to_query - assert_equal params[2], { 'query[person][position][]' => 'prof' }.to_query - assert_equal params[3], { 'query[person][position][]' => 'art director' }.to_query + assert_equal params[2], { 'query[person][position][]' => 'art director' }.to_query + assert_equal params[3], { 'query[person][position][]' => 'prof' }.to_query end def test_path_generation_for_symbol_parameter_keys @@ -334,7 +333,6 @@ class UrlWriterTests < ActionController::TestCase set.draw do |map| map.main '', :controller => 'posts', :format => nil map.resources :posts - map.connect ':controller/:action/:id' end # We need to create a new class in order to install the new named route. @@ -359,10 +357,10 @@ class UrlWriterTests < ActionController::TestCase controller = kls.new params = {:id => 1, :format => :xml} assert_deprecated do - assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params)) + assert_equal("/posts/1.xml", controller.send(:formatted_post_path, params)) end assert_deprecated do - assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml)) + assert_equal("/posts/1.xml", controller.send(:formatted_post_path, 1, :xml)) end end end @@ -382,6 +380,6 @@ class UrlWriterTests < ActionController::TestCase private def extract_params(url) - url.split('?', 2).last.split('&') + url.split('?', 2).last.split('&').sort end end diff --git a/actionpack/test/controller/verification_test.rb b/actionpack/test/controller/verification_test.rb index 1a9eb65f29..11d0d10897 100644 --- a/actionpack/test/controller/verification_test.rb +++ b/actionpack/test/controller/verification_test.rb @@ -125,8 +125,8 @@ class VerificationTest < ActionController::TestCase assert_not_deprecated do with_routing do |set| set.draw do |map| - map.foo '/foo', :controller => 'test', :action => 'foo' - map.connect ":controller/:action/:id" + match 'foo', :to => 'test#foo', :as => :foo + match 'verification_test/:action', :to => ::VerificationTest::TestController end get :guarded_one_for_named_route_test, :two => "not one" assert_redirected_to '/foo' diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index db6cf7b330..d3308f73cc 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -57,7 +57,7 @@ class JsonParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "json_params_parsing_test/test" + match ':action', :to => ::JsonParamsParsingTest::TestController end yield end diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index a31e326ddf..071d80c5b0 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -109,12 +109,12 @@ class QueryStringParsingTest < ActionController::IntegrationTest def assert_parses(expected, actual) with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "query_string_parsing_test/test" + match ':action', :to => ::QueryStringParsingTest::TestController end get "/parse", actual assert_response :ok - assert_equal(expected, TestController.last_query_parameters) + assert_equal(expected, ::QueryStringParsingTest::TestController.last_query_parameters) end end end diff --git a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb index 7167cdafac..69dbd7f528 100644 --- a/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/url_encoded_params_parsing_test.rb @@ -130,7 +130,7 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "url_encoded_params_parsing_test/test" + match ':action', :to => ::UrlEncodedParamsParsingTest::TestController end yield end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index 521002b519..96189e4ca2 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -84,7 +84,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest def with_test_routing with_routing do |set| set.draw do |map| - map.connect ':action', :controller => "xml_params_parsing_test/test" + match ':action', :to => ::XmlParamsParsingTest::TestController end yield end @@ -100,4 +100,4 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest def default_headers {'HTTP_X_POST_DATA_FORMAT' => 'xml'} end -end
\ No newline at end of file +end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 239fda98e0..b62df9a6b2 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -432,6 +432,10 @@ class RequestTest < ActiveSupport::TestCase request = stub_request request.expects(:parameters).at_least_once.returns({ :format => :txt }) assert_equal with_set(Mime::TEXT), request.formats + + request = stub_request + request.expects(:parameters).at_least_once.returns({ :format => :unknown }) + assert request.formats.empty? end test "negotiate_mime" do diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb new file mode 100644 index 0000000000..ca07bc7a28 --- /dev/null +++ b/actionpack/test/dispatch/routing_test.rb @@ -0,0 +1,391 @@ +require 'abstract_unit' +require 'controller/fake_controllers' + +class TestRoutingMapper < ActionDispatch::IntegrationTest + SprocketsApp = lambda { |env| + [200, {"Content-Type" => "text/html"}, ["javascripts"]] + } + + class IpRestrictor + def self.matches?(request) + request.ip =~ /192\.168\.1\.1\d\d/ + end + end + + stub_controllers do |routes| + Routes = routes + Routes.draw do |map| + controller :sessions do + get 'login', :to => :new, :as => :login + post 'login', :to => :create + + delete 'logout', :to => :destroy, :as => :logout + end + + match 'account/login', :to => redirect("/login") + + match 'openid/login', :via => [:get, :post], :to => "openid#login" + + controller(:global) do + match 'global/:action' + match 'global/export', :to => :export, :as => :export_request + match 'global/hide_notice', :to => :hide_notice, :as => :hide_notice + match '/export/:id/:file', :to => :export, :as => :export_download, :constraints => { :file => /.*/ } + end + + constraints(:ip => /192\.168\.1\.\d\d\d/) do + get 'admin', :to => "queenbee#index" + end + + constraints ::TestRoutingMapper::IpRestrictor do + get 'admin/accounts', :to => "queenbee#accounts" + end + + resources :projects, :controller => :project do + resources :involvements, :attachments + + resources :participants do + put :update_all, :on => :collection + end + + resources :companies do + resources :people + resource :avatar + end + + resources :images do + post :revise, :on => :member + end + + resources :people do + namespace ":access_token" do + resource :avatar + end + + member do + put :accessible_projects + post :resend, :generate_new_password + end + end + + resources :posts do + get :archive, :toggle_view, :on => :collection + post :preview, :on => :member + + resource :subscription + + resources :comments do + post :preview, :on => :collection + end + end + end + + match 'sprockets.js', :to => ::TestRoutingMapper::SprocketsApp + + match 'people/:id/update', :to => 'people#update', :as => :update_person + match '/projects/:project_id/people/:id/update', :to => 'people#update', :as => :update_project_person + + # misc + match 'articles/:year/:month/:day/:title', :to => "articles#show", :as => :article + + namespace :account do + resource :subscription, :credit, :credit_card + end + + controller :articles do + scope 'articles' do + scope ':title', :title => /[a-z]+/, :as => :with_title do + match ':id', :to => :with_id + end + end + end + + scope ':access_token', :constraints => { :access_token => /\w{5,5}/ } do + resources :rooms + end + end + end + + def app + Routes + end + + def test_logout + with_test_routes do + delete '/logout' + assert_equal 'sessions#destroy', @response.body + + assert_equal '/logout', logout_path + assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true) + end + end + + def test_login + with_test_routes do + get '/login' + assert_equal 'sessions#new', @response.body + assert_equal '/login', login_path + + post '/login' + assert_equal 'sessions#create', @response.body + + assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) + assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) + end + end + + def test_login_redirect + with_test_routes do + get '/account/login' + assert_equal 301, @response.status + assert_equal 'http://www.example.com/login', @response.headers['Location'] + assert_equal 'Moved Permanently', @response.body + end + end + + def test_openid + with_test_routes do + get '/openid/login' + assert_equal 'openid#login', @response.body + + post '/openid/login' + assert_equal 'openid#login', @response.body + end + end + + # TODO: rackmount is broken + # def test_admin + # with_test_routes do + # get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} + # assert_equal 'queenbee#index', @response.body + # + # assert_raise(ActionController::RoutingError) { get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + # + # get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} + # assert_equal 'queenbee#accounts', @response.body + # + # assert_raise(ActionController::RoutingError) { get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} } + # end + # end + + def test_global + with_test_routes do + get '/global/dashboard' + assert_equal 'global#dashboard', @response.body + + get '/global/export' + assert_equal 'global#export', @response.body + + get '/global/hide_notice' + assert_equal 'global#hide_notice', @response.body + + get '/export/123/foo.txt' + assert_equal 'global#export', @response.body + + assert_equal '/global/export', export_request_path + assert_equal '/global/hide_notice', hide_notice_path + assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt') + end + end + + def test_projects + with_test_routes do + get '/projects' + assert_equal 'projects#index', @response.body + assert_equal '/projects', projects_path + + get '/projects/new' + assert_equal 'projects#new', @response.body + assert_equal '/projects/new', new_project_path + + get '/projects/1' + assert_equal 'projects#show', @response.body + assert_equal '/projects/1', project_path(:id => '1') + + get '/projects/1/edit' + assert_equal 'projects#edit', @response.body + assert_equal '/projects/1/edit', edit_project_path(:id => '1') + end + end + + def test_projects_involvements + with_test_routes do + get '/projects/1/involvements' + assert_equal 'involvements#index', @response.body + + get '/projects/1/involvements/1' + assert_equal 'involvements#show', @response.body + end + end + + def test_projects_attachments + with_test_routes do + get '/projects/1/attachments' + assert_equal 'attachments#index', @response.body + end + end + + def test_projects_participants + with_test_routes do + get '/projects/1/participants' + assert_equal 'participants#index', @response.body + + put '/projects/1/participants/update_all' + assert_equal 'participants#update_all', @response.body + end + end + + def test_projects_companies + with_test_routes do + get '/projects/1/companies' + assert_equal 'companies#index', @response.body + + get '/projects/1/companies/1/people' + assert_equal 'people#index', @response.body + + get '/projects/1/companies/1/avatar' + assert_equal 'avatars#show', @response.body + end + end + + def test_project_images + with_test_routes do + get '/projects/1/images' + assert_equal 'images#index', @response.body + + post '/projects/1/images/1/revise' + assert_equal 'images#revise', @response.body + end + end + + def test_projects_people + with_test_routes do + get '/projects/1/people' + assert_equal 'people#index', @response.body + + get '/projects/1/people/1' + assert_equal 'people#show', @response.body + + get '/projects/1/people/1/7a2dec8/avatar' + assert_equal 'avatars#show', @response.body + + put '/projects/1/people/1/accessible_projects' + assert_equal 'people#accessible_projects', @response.body + + post '/projects/1/people/1/resend' + assert_equal 'people#resend', @response.body + + post '/projects/1/people/1/generate_new_password' + assert_equal 'people#generate_new_password', @response.body + end + end + + def test_projects_posts + with_test_routes do + get '/projects/1/posts' + assert_equal 'posts#index', @response.body + + get '/projects/1/posts/archive' + assert_equal 'posts#archive', @response.body + + get '/projects/1/posts/toggle_view' + assert_equal 'posts#toggle_view', @response.body + + post '/projects/1/posts/1/preview' + assert_equal 'posts#preview', @response.body + + get '/projects/1/posts/1/subscription' + assert_equal 'subscriptions#show', @response.body + + get '/projects/1/posts/1/comments' + assert_equal 'comments#index', @response.body + + post '/projects/1/posts/1/comments/preview' + assert_equal 'comments#preview', @response.body + end + end + + def test_sprockets + with_test_routes do + get '/sprockets.js' + assert_equal 'javascripts', @response.body + end + end + + def test_update_person_route + with_test_routes do + get '/people/1/update' + assert_equal 'people#update', @response.body + + assert_equal '/people/1/update', update_person_path(:id => 1) + end + end + + def test_update_project_person + with_test_routes do + get '/projects/1/people/2/update' + assert_equal 'people#update', @response.body + + assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2) + end + end + + def test_articles_perma + with_test_routes do + get '/articles/2009/08/18/rails-3' + assert_equal 'articles#show', @response.body + + assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3') + end + end + + def test_account_namespace + with_test_routes do + get '/account/subscription' + assert_equal 'subscriptions#show', @response.body + + get '/account/credit' + assert_equal 'credits#show', @response.body + + get '/account/credit_card' + assert_equal 'credit_cards#show', @response.body + end + end + + def test_articles_with_id + with_test_routes do + get '/articles/rails/1' + assert_equal 'articles#with_id', @response.body + + assert_raise(ActionController::RoutingError) { get '/articles/123/1' } + + assert_equal '/articles/rails/1', with_title_path(:title => 'rails', :id => 1) + end + end + + def test_access_token_rooms + with_test_routes do + get '/12345/rooms' + assert_equal 'rooms#index', @response.body + + get '/12345/rooms/1' + assert_equal 'rooms#show', @response.body + + get '/12345/rooms/1/edit' + assert_equal 'rooms#edit', @response.body + end + end + + private + def with_test_routes + real_routes, temp_routes = ActionController::Routing::Routes, Routes + + ActionController::Routing.module_eval { remove_const :Routes } + ActionController::Routing.module_eval { const_set :Routes, temp_routes } + + yield + ensure + ActionController::Routing.module_eval { remove_const :Routes } + ActionController::Routing.const_set(:Routes, real_routes) + end +end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index ab5fabde65..ab7b9bc31b 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -219,7 +219,7 @@ class CookieStoreTest < ActionController::IntegrationTest def with_test_route_set(options = {}) with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "cookie_store_test/test" + match ':action', :to => ::CookieStoreTest::TestController end options = {:key => SessionKey, :secret => SessionSecret}.merge(options) @app = ActionDispatch::Session::CookieStore.new(set, options) diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index c7435bd06b..5a1dcb4dab 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -112,7 +112,7 @@ class MemCacheStoreTest < ActionController::IntegrationTest def with_test_route_set with_routing do |set| set.draw do |map| - map.connect "/:action", :controller => "mem_cache_store_test/test" + match ':action', :to => ::MemCacheStoreTest::TestController end @app = ActionDispatch::Session::MemCacheStore.new(set, :key => '_session_id') yield diff --git a/actionpack/test/dispatch/session/test_session_test.rb b/actionpack/test/dispatch/session/test_session_test.rb index 0ff93f1c5d..c8dc4ab461 100644 --- a/actionpack/test/dispatch/session/test_session_test.rb +++ b/actionpack/test/dispatch/session/test_session_test.rb @@ -26,11 +26,11 @@ class ActionController::TestSessionTest < ActiveSupport::TestCase assert_equal('value', session[:key]) end - def test_calling_delete_removes_item + def test_calling_delete_removes_item_and_returns_its_value session = ActionController::TestSession.new session[:key] = 'value' assert_equal('value', session[:key]) - session.delete(:key) + assert_equal('value', session.delete(:key)) assert_nil(session[:key]) end diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index b8e340e055..5da02b2ea6 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -5,7 +5,7 @@ class TestRequestTest < ActiveSupport::TestCase env = ActionDispatch::TestRequest.new.env assert_equal "GET", env.delete("REQUEST_METHOD") - assert_equal nil, env.delete("HTTPS") + assert_equal "off", env.delete("HTTPS") assert_equal "http", env.delete("rack.url_scheme") assert_equal "example.org", env.delete("SERVER_NAME") assert_equal "80", env.delete("SERVER_PORT") @@ -18,7 +18,7 @@ class TestRequestTest < ActiveSupport::TestCase assert_equal "0.0.0.0", env.delete("REMOTE_ADDR") assert_equal "Rails Testing", env.delete("HTTP_USER_AGENT") - assert_equal [0, 1], env.delete("rack.version") + assert_equal [1, 0], env.delete("rack.version") assert_equal "", env.delete("rack.input").string assert_kind_of StringIO, env.delete("rack.errors") assert_equal true, env.delete("rack.multithread") diff --git a/actionpack/test/fixtures/layouts/block_with_layout.erb b/actionpack/test/fixtures/layouts/block_with_layout.erb index 6a8b41914b..f25b41271d 100644 --- a/actionpack/test/fixtures/layouts/block_with_layout.erb +++ b/actionpack/test/fixtures/layouts/block_with_layout.erb @@ -1,3 +1,3 @@ -<% render(:layout => "layout_for_partial", :locals => { :name => "Anthony" }) do %>Inside from first block in layout<% end %> +<% render(:layout => "layout_for_partial", :locals => { :name => "Anthony" }) do %>Inside from first block in layout<% "Return value should be discarded" %><% end %> <%= yield %> <% render(:layout => "layout_for_partial", :locals => { :name => "Ramm" }) do %>Inside from second block in layout<% end %> diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb index 9ec7f330b8..250327e6dc 100644 --- a/actionpack/test/lib/controller/fake_controllers.rb +++ b/actionpack/test/lib/controller/fake_controllers.rb @@ -5,8 +5,10 @@ class NotAController; end module Admin class << self; alias_method :const_available?, :const_defined?; end - class UserController < ActionController::Base; end class NewsFeedController < ActionController::Base; end + class PostsController < ActionController::Base; end + class StuffController < ActionController::Base; end + class UserController < ActionController::Base; end end module Api @@ -24,8 +26,11 @@ class ElsewhereController < ActionController::Base; end class FooController < ActionController::Base; end class HiController < ActionController::Base; end class ImageController < ActionController::Base; end +class NotesController < ActionController::Base; end class PeopleController < ActionController::Base; end +class PostsController < ActionController::Base; end class SessionsController < ActionController::Base; end +class StuffController < ActionController::Base; end class SubpathBooksController < ActionController::Base; end class WeblogController < ActionController::Base; end diff --git a/actionpack/test/template/active_model_helper_i18n_test.rb b/actionpack/test/template/active_model_helper_i18n_test.rb new file mode 100644 index 0000000000..2465444fc5 --- /dev/null +++ b/actionpack/test/template/active_model_helper_i18n_test.rb @@ -0,0 +1,42 @@ +require 'abstract_unit' + +class ActiveModelHelperI18nTest < Test::Unit::TestCase + include ActionView::Context + include ActionView::Helpers::ActiveModelHelper + + attr_reader :request + + def setup + @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages']) + @object.stubs :to_model => @object + @object.stubs :class => stub(:model_name => stub(:human => "")) + + @object_name = 'book_seller' + @object_name_without_underscore = 'book seller' + + stubs(:content_tag).returns 'content_tag' + + I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" + I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + end + + def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').never + error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') + end + + def test_error_messages_for_given_no_header_option_it_translates_header_message + I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activemodel, :errors, :template], :count => 1, :model => '').returns 'header message' + error_messages_for(:object => @object, :locale => 'en') + end + + def test_error_messages_for_given_a_message_option_it_does_not_translate_message + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).never + error_messages_for(:object => @object, :message => 'message', :locale => 'en') + end + + def test_error_messages_for_given_no_message_option_it_translates_message + I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activemodel, :errors, :template]).returns 'There were problems with the following fields:' + error_messages_for(:object => @object, :locale => 'en') + end +end diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb index c149070f2a..3e01ae78c3 100644 --- a/actionpack/test/template/active_record_helper_test.rb +++ b/actionpack/test/template/active_model_helper_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' -class ActiveRecordHelperTest < ActionView::TestCase +class ActiveModelHelperTest < ActionView::TestCase tests ActionView::Helpers::ActiveModelHelper silence_warnings do diff --git a/actionpack/test/template/active_record_helper_i18n_test.rb b/actionpack/test/template/active_record_helper_i18n_test.rb deleted file mode 100644 index 047f81be29..0000000000 --- a/actionpack/test/template/active_record_helper_i18n_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'abstract_unit' - -class ActiveRecordHelperI18nTest < Test::Unit::TestCase - include ActionView::Context - include ActionView::Helpers::ActiveModelHelper - - attr_reader :request - - def setup - @object = stub :errors => stub(:count => 1, :full_messages => ['full_messages']) - @object.stubs :to_model => @object - @object.stubs :class => stub(:model_name => stub(:human => "")) - - @object_name = 'book_seller' - @object_name_without_underscore = 'book seller' - - stubs(:content_tag).returns 'content_tag' - - I18n.stubs(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns "1 error prohibited this from being saved" - I18n.stubs(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' - end - - def test_error_messages_for_given_a_header_option_it_does_not_translate_header_message - I18n.expects(:translate).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').never - error_messages_for(:object => @object, :header_message => 'header message', :locale => 'en') - end - - def test_error_messages_for_given_no_header_option_it_translates_header_message - I18n.expects(:t).with(:'header', :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => '').returns 'header message' - I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :locale => 'en') - end - - def test_error_messages_for_given_a_message_option_it_does_not_translate_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).never - I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :message => 'message', :locale => 'en') - end - - def test_error_messages_for_given_no_message_option_it_translates_message - I18n.expects(:t).with(:'body', :locale => 'en', :scope => [:activerecord, :errors, :template]).returns 'There were problems with the following fields:' - I18n.expects(:t).with('', :default => '', :count => 1, :scope => [:activerecord, :models]).once.returns '' - error_messages_for(:object => @object, :locale => 'en') - end - - def test_error_messages_for_given_object_name_it_translates_object_name - I18n.expects(:t).with(:header, :locale => 'en', :scope => [:activerecord, :errors, :template], :count => 1, :model => @object_name_without_underscore).returns "1 error prohibited this #{@object_name_without_underscore} from being saved" - I18n.expects(:t).with(@object_name, :default => @object_name_without_underscore, :count => 1, :scope => [:activerecord, :models]).once.returns @object_name_without_underscore - error_messages_for(:object => @object, :locale => 'en', :object_name => @object_name) - end -end diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index d94135b04b..57802ebf42 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -3,6 +3,13 @@ require 'abstract_unit' class AssetTagHelperTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG.merge( + :assets_dir => File.dirname(__FILE__) + "/../fixtures/public", + :javascripts_dir => File.dirname(__FILE__) + "/../fixtures/public/javascripts", + :stylesheets_dir => File.dirname(__FILE__) + "/../fixtures/public/stylesheets") + + include ActiveSupport::Configurable + def setup super silence_warnings do @@ -872,6 +879,9 @@ end class AssetTagHelperNonVhostTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + include ActiveSupport::Configurable + def setup super ActionController::Base.relative_url_root = "/collaboration/hieraki" diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 04c635e770..44734abb18 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -613,6 +613,26 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + + '</form>' + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_one_to_one_association_with_explicit_hidden_field_placement + @post.author = Author.new(321) + + form_for(:post, @post) do |f| + concat f.text_field(:title) + f.fields_for(:author) do |af| + concat af.hidden_field(:id) + concat af.text_field(:name) + end + end + + expected = '<form action="http://www.example.com" method="post">' + + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + '</form>' @@ -634,6 +654,30 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + + '</form>' + + assert_dom_equal expected, output_buffer + end + + def test_nested_fields_for_with_existing_records_on_a_nested_attributes_collection_association_with_explicit_hidden_field_placement + @post.comments = Array.new(2) { |id| Comment.new(id + 1) } + + form_for(:post, @post) do |f| + concat f.text_field(:title) + @post.comments.each do |comment| + f.fields_for(:comments, comment) do |cf| + concat cf.hidden_field(:id) + concat cf.text_field(:name) + end + end + end + + expected = '<form action="http://www.example.com" method="post">' + + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + @@ -678,8 +722,8 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + '</form>' @@ -713,10 +757,10 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + '</form>' assert_dom_equal expected, output_buffer @@ -736,8 +780,8 @@ class FormHelperTest < ActionView::TestCase expected = '<form action="http://www.example.com" method="post">' + '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + '</form>' @@ -755,8 +799,8 @@ class FormHelperTest < ActionView::TestCase end expected = '<form action="http://www.example.com" method="post">' + - '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' + '</form>' assert_dom_equal expected, output_buffer @@ -790,18 +834,18 @@ class FormHelperTest < ActionView::TestCase end expected = '<form action="http://www.example.com" method="post">' + - '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' + - '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + + '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' + - '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + + '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + + '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' + + '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + + '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + '</form>' assert_dom_equal expected, output_buffer diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index d64b9492e2..47462b1237 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -3,6 +3,9 @@ require 'abstract_unit' class FormTagHelperTest < ActionView::TestCase tests ActionView::Helpers::FormTagHelper + include ActiveSupport::Configurable + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + def setup super @controller = Class.new do diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 85a97d570c..0a2b82bd89 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -88,7 +88,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("111.00", number_with_precision(111, :precision => 2)) assert_equal("111.235", number_with_precision("111.2346")) assert_equal("31.83", number_with_precision("31.825", :precision => 2)) - assert_equal("3268", number_with_precision((32.675 * 100.00), :precision => 0)) + assert_equal("3268", number_with_precision((32.6751 * 100.00), :precision => 0)) assert_equal("112", number_with_precision(111.50, :precision => 0)) assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) diff --git a/actionpack/test/view/safe_buffer_test.rb b/actionpack/test/template/safe_buffer_test.rb index 2236709627..6a18201d16 100644 --- a/actionpack/test/view/safe_buffer_test.rb +++ b/actionpack/test/template/safe_buffer_test.rb @@ -26,7 +26,7 @@ class SafeBufferTest < ActionView::TestCase end test "Should not mess with a previously escape test" do - @buffer << CGI.escapeHTML("<script>") + @buffer << ERB::Util.html_escape("<script>") assert_equal "<script>", @buffer end diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index ca72c13ffa..05a409d05a 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -24,7 +24,7 @@ module ActionView test_case.class_eval do test "helpers defined on ActionView::TestCase are available" do assert test_case.ancestors.include?(ASharedTestHelper) - assert 'Holla!', from_shared_helper + assert_equal 'Holla!', from_shared_helper end end end @@ -38,10 +38,15 @@ module ActionView assert_equal 'Eloy', render('developers/developer', :developer => stub(:name => 'Eloy')) end + test "can render a layout with block" do + assert_equal "Before (ChrisCruft)\n!\nAfter", + render(:layout => "test/layout_for_partial", :locals => {:name => "ChrisCruft"}) {"!"} + end + helper AnotherTestHelper test "additional helper classes can be specified as in a controller" do assert test_case.ancestors.include?(AnotherTestHelper) - assert 'Howdy!', from_another_helper + assert_equal 'Howdy!', from_another_helper end end @@ -58,14 +63,14 @@ module ActionView helper AnotherTestHelper test "additional helper classes can be specified as in a controller" do assert test_case.ancestors.include?(AnotherTestHelper) - assert 'Howdy!', from_another_helper + assert_equal 'Howdy!', from_another_helper test_case.helper_class.module_eval do def render_from_helper from_another_helper end end - assert 'Howdy!', render(:partial => 'test/from_helper') + assert_equal 'Howdy!', render(:partial => 'test/from_helper') end end diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb index 05a14f3554..68e790cf46 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionpack/test/template/test_test.rb @@ -48,8 +48,7 @@ class PeopleHelperTest < ActionView::TestCase def with_test_route_set with_routing do |set| set.draw do |map| - map.people 'people', :controller => 'people', :action => 'index' - map.connect ':controller/:action/:id' + match 'people', :to => 'people#index', :as => :people end yield end diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 7f6ebc56b7..bf0b4ad3a7 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -5,6 +5,9 @@ require 'controller/fake_controllers' RequestMock = Struct.new("Request", :request_uri, :protocol, :host_with_port, :env) class UrlHelperTest < ActionView::TestCase + include ActiveSupport::Configurable + DEFAULT_CONFIG = ActionView::DEFAULT_CONFIG + def setup super @controller = Class.new do @@ -19,11 +22,16 @@ class UrlHelperTest < ActionView::TestCase def test_url_for_escapes_urls @controller.url = "http://www.example.com?a=b&c=d" - assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd') + assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd') assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => true) assert_equal "http://www.example.com?a=b&c=d", url_for(:a => 'b', :c => 'd', :escape => false) end + def test_url_for_escaping_is_safety_aware + assert url_for(:a => 'b', :c => 'd', :escape => true).html_safe?, "escaped urls should be html_safe?" + assert !url_for(:a => 'b', :c => 'd', :escape => false).html_safe?, "non-escaped urls shouldn't be safe" + end + def test_url_for_escapes_url_once @controller.url = "http://www.example.com?a=b&c=d" assert_equal "http://www.example.com?a=b&c=d", url_for("http://www.example.com?a=b&c=d") @@ -39,6 +47,16 @@ class UrlHelperTest < ActionView::TestCase assert_equal 'javascript:history.back()', url_for(:back) end + def test_url_for_from_hash_doesnt_escape_ampersand + @controller = TestController.new + @view = ActionView::Base.new + @view.controller = @controller + + path = @view.url_for(:controller => :cheeses, :foo => :bar, :baz => :quux) + + assert_equal '/cheeses?baz=quux&foo=bar', sort_query_string_params(path) + end + # todo: missing test cases def test_button_to_with_straight_url assert_dom_equal "<form method=\"post\" action=\"http://www.example.com\" class=\"button-to\"><div><input type=\"submit\" value=\"Hello\" /></div></form>", button_to("Hello", "http://www.example.com") @@ -266,21 +284,21 @@ class UrlHelperTest < ActionView::TestCase assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end - + def test_current_page_ignoring_params @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog" }) assert current_page?("http://www.example.com/weblog/show") end - + def test_current_page_with_params_that_match @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert current_page?({ :action => "show", :controller => "weblog", :order => "desc", :page => "1" }) assert current_page?("http://www.example.com/weblog/show?order=desc&page=1") end - + def test_link_unless_current @controller.request = RequestMock.new("http://www.example.com/weblog/show") @controller.url = "http://www.example.com/weblog/show" @@ -295,7 +313,7 @@ class UrlHelperTest < ActionView::TestCase @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=1" assert_equal "Showing", link_to_unless_current("Showing", { :action => "show", :controller => "weblog", :order=>'desc', :page=>'1' }) - assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") + assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") assert_equal "Showing", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=1") @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc") @@ -305,7 +323,7 @@ class UrlHelperTest < ActionView::TestCase @controller.request = RequestMock.new("http://www.example.com/weblog/show?order=desc&page=1") @controller.url = "http://www.example.com/weblog/show?order=desc&page=2" - assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) + assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", { :action => "show", :controller => "weblog" }) assert_equal "<a href=\"http://www.example.com/weblog/show?order=desc&page=2\">Showing</a>", link_to_unless_current("Showing", "http://www.example.com/weblog/show?order=desc&page=2") @@ -360,10 +378,17 @@ class UrlHelperTest < ActionView::TestCase assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%4d%79%20%65%6d%61%69%6c%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", "My email", :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") assert_dom_equal "<script type=\"text/javascript\">eval(decodeURIComponent('%64%6f%63%75%6d%65%6e%74%2e%77%72%69%74%65%28%27%3c%61%20%68%72%65%66%3d%22%6d%61%69%6c%74%6f%3a%6d%65%40%64%6f%6d%61%69%6e%2e%63%6f%6d%22%3e%6d%65%28%61%74%29%64%6f%6d%61%69%6e%28%64%6f%74%29%63%6f%6d%3c%2f%61%3e%27%29%3b'))</script>", mail_to("me@domain.com", nil, :encode => "javascript", :replace_at => "(at)", :replace_dot => "(dot)") end - + def protect_against_forgery? false end + + private + def sort_query_string_params(uri) + path, qs = uri.split('?') + qs = qs.split('&').sort.join('&') if qs + qs ? "#{path}?#{qs}" : path + end end class UrlHelperController < ActionController::Base diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb index 21d62f6aa7..5670d93613 100644 --- a/actionpack/test/ts_isolated.rb +++ b/actionpack/test/ts_isolated.rb @@ -10,8 +10,8 @@ class TestIsolated < Test::Unit::TestCase Dir["#{File.dirname(__FILE__)}/{abstract,controller,dispatch,template}/**/*_test.rb"].each do |file| define_method("test #{file}") do command = "#{ruby} -Ilib:test #{file}" - silence_stderr { `#{command}` } - assert_equal 0, $?.to_i, command + result = silence_stderr { `#{command}` } + assert_block("#{command}\n#{result}") { $?.to_i.zero? } end end end diff --git a/activemodel/Rakefile b/activemodel/Rakefile index fd69a557aa..1f4a8466c9 100755 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -1,4 +1,5 @@ -require File.join(File.dirname(__FILE__), 'lib', 'active_model', 'version') +dir = File.dirname(__FILE__) +require "#{dir}/lib/active_model/version" PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' PKG_NAME = 'activemodel' @@ -11,18 +12,20 @@ require 'rake/testtask' task :default => :test -Rake::TestTask.new do |t| - t.libs << "test" - t.test_files = Dir.glob("test/cases/**/*_test.rb").sort +Rake::TestTask.new do |t| + t.libs << "#{dir}/test" + t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort t.verbose = true t.warning = true end -task :isolated_test do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - Dir.glob("test/**/*_test.rb").all? do |file| - system(ruby, '-w', '-Ilib:test', file) - end or raise "Failures" +namespace :test do + task :isolated do + ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) + Dir.glob("#{dir}/test/**/*_test.rb").all? do |file| + system(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) + end or raise "Failures" + end end @@ -30,23 +33,21 @@ require 'rake/rdoctask' # Generate the RDoc documentation Rake::RDocTask.new do |rdoc| - rdoc.rdoc_dir = 'doc' + rdoc.rdoc_dir = "#{dir}/doc" rdoc.title = "Active Model" rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' rdoc.options << '--charset' << 'utf-8' rdoc.template = ENV['template'] ? "#{ENV['template']}.rb" : '../doc/template/horo' - rdoc.rdoc_files.include('README', 'CHANGES') - rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.rdoc_files.include("#{dir}/README", "#{dir}/CHANGES") + rdoc.rdoc_files.include("#{dir}/lib/**/*.rb") end require 'rake/packagetask' require 'rake/gempackagetask' -spec = eval(File.read('activemodel.gemspec')) +spec = eval(File.read("#{dir}/activemodel.gemspec")) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 67f529262d..505e16c195 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -39,8 +39,10 @@ module ActiveModel autoload :Serialization, 'active_model/serialization' autoload :StateMachine, 'active_model/state_machine' autoload :TestCase, 'active_model/test_case' + autoload :Translation, 'active_model/translation' autoload :Validations, 'active_model/validations' autoload :ValidationsRepairHelper, 'active_model/validations_repair_helper' + autoload :Validator, 'active_model/validator' autoload :VERSION, 'active_model/version' module Serializers diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 7a48960f89..e8bb62953d 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -93,7 +93,7 @@ module ActiveModel # company = Company.create(:address => '123 First St.') # company.errors.full_messages # => # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] - def full_messages(options = {}) + def full_messages full_messages = [] each do |attribute, messages| @@ -103,8 +103,10 @@ module ActiveModel if attribute == :base messages.each {|m| full_messages << m } else - attr_name = attribute.to_s.humanize - prefix = attr_name + I18n.t('activemodel.errors.format.separator', :default => ' ') + attr_name = @base.class.human_attribute_name(attribute) + options = { :default => ' ', :scope => @base.class.i18n_scope } + prefix = attr_name + I18n.t(:"errors.format.separator", options) + messages.each do |m| full_messages << "#{prefix}#{m}" end @@ -135,10 +137,7 @@ module ActiveModel def generate_message(attribute, message = :invalid, options = {}) message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) - klass_ancestors = [@base.class] - klass_ancestors += @base.class.ancestors.reject {|x| x.is_a?(Module)} - - defaults = klass_ancestors.map do |klass| + defaults = @base.class.lookup_ancestors.map do |klass| [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", :"models.#{klass.name.underscore}.#{message}" ] end @@ -150,10 +149,10 @@ module ActiveModel value = @base.send(:read_attribute_for_validation, attribute) options = { :default => defaults, - :model => @base.class.name.humanize, - :attribute => attribute.to_s.humanize, + :model => @base.class.model_name.human, + :attribute => @base.class.human_attribute_name(attribute), :value => value, - :scope => [:activemodel, :errors] + :scope => [@base.class.i18n_scope, :errors] }.merge(options) I18n.translate(key, options) diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index b8c2a367b4..675d62b9a6 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -5,12 +5,13 @@ module ActiveModel attr_reader :singular, :plural, :element, :collection, :partial_path, :human alias_method :cache_key, :collection - def initialize(name) - super + def initialize(klass, name) + super(name) + @klass = klass @singular = ActiveSupport::Inflector.underscore(self).tr('/', '_').freeze @plural = ActiveSupport::Inflector.pluralize(@singular).freeze @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze - @human = @element.gsub(/_/, " ") + @human = ActiveSupport::Inflector.humanize(@element).freeze @collection = ActiveSupport::Inflector.tableize(self).freeze @partial_path = "#{@collection}/#{@element}".freeze end @@ -20,7 +21,7 @@ module ActiveModel # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information. def model_name - @_model_name ||= ActiveModel::Name.new(name) + @_model_name ||= ActiveModel::Name.new(self, name) end end end diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb new file mode 100644 index 0000000000..42ca463f82 --- /dev/null +++ b/activemodel/lib/active_model/translation.rb @@ -0,0 +1,64 @@ +require 'active_support/core_ext/hash/reverse_merge' + +module ActiveModel + module Translation + include ActiveModel::Naming + + # Returns the i18n_scope for the class. Overwrite if you want custom lookup. + def i18n_scope + :activemodel + end + + # When localizing a string, goes through the lookup returned by this method. + # Used in ActiveModel::Name#human, ActiveModel::Errors#full_messages and + # ActiveModel::Translation#human_attribute_name. + def lookup_ancestors + self.ancestors.select { |x| x.respond_to?(:model_name) } + end + + # Transforms attributes names into a more human format, such as "First name" instead of "first_name". + # + # Example: + # + # Person.human_attribute_name("first_name") # => "First name" + # + # Specify +options+ with additional translating options. + def human_attribute_name(attribute, options = {}) + defaults = lookup_ancestors.map do |klass| + :"#{klass.model_name.underscore}.#{attribute}" + end + + defaults << options.delete(:default) if options[:default] + defaults << attribute.to_s.humanize + + options.reverse_merge! :scope => [self.i18n_scope, :attributes], :count => 1, :default => defaults + I18n.translate(defaults.shift, options) + end + + # Model.human_name is deprecated. Use Model.model_name.human instead. + def human_name(*args) + ActiveSupport::Deprecation.warn("human_name has been deprecated, please use model_name.human instead", caller[0,1]) + model_name.human(*args) + end + end + + class Name < String + # Transform the model name into a more humane format, using I18n. By default, + # it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post"). + # Specify +options+ with additional translating options. + def human(options={}) + return @human unless @klass.respond_to?(:lookup_ancestors) && + @klass.respond_to?(:i18n_scope) + + defaults = @klass.lookup_ancestors.map do |klass| + klass.model_name.underscore.to_sym + end + + defaults << options.delete(:default) if options[:default] + defaults << @human + + options.reverse_merge! :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults + I18n.translate(defaults.shift, options) + end + end +end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index edeb508a08..064ec98f3a 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,14 +1,13 @@ require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/hash/keys' -require 'active_support/concern' -require 'active_support/callbacks' module ActiveModel module Validations extend ActiveSupport::Concern - include ActiveSupport::NewCallbacks + include ActiveSupport::Callbacks included do + extend ActiveModel::Translation define_callbacks :validate, :scope => :name end @@ -99,7 +98,7 @@ module ActiveModel def invalid? !valid? end - + protected # Hook method defining how an attribute value should be retieved. By default this is assumed # to be an instance named after the attribute. Override this method in subclasses should you @@ -110,9 +109,9 @@ module ActiveModel # def initialize(data = {}) # @data = data # end - # + # # private - # + # # def read_attribute_for_validation(key) # @data[key] # end diff --git a/activerecord/lib/active_record/validator.rb b/activemodel/lib/active_model/validator.rb index 83a33f4dcd..09de72b757 100644 --- a/activerecord/lib/active_record/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,12 +1,12 @@ -module ActiveRecord #:nodoc: +module ActiveModel #:nodoc: - # A simple base class that can be used along with ActiveRecord::Base.validates_with + # A simple base class that can be used along with ActiveModel::Base.validates_with # - # class Person < ActiveRecord::Base + # class Person < ActiveModel::Base # validates_with MyValidator # end # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def validate # if some_complex_logic # record.errors[:base] = "This record is invalid" @@ -19,14 +19,14 @@ module ActiveRecord #:nodoc: # end # end # - # Any class that inherits from ActiveRecord::Validator will have access to <tt>record</tt>, + # Any class that inherits from ActiveModel::Validator will have access to <tt>record</tt>, # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>. # - # class Person < ActiveRecord::Base + # class Person < ActiveModel::Base # validates_with MyValidator # end # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def validate # record # => The person instance being validated # options # => Any non-standard options passed to validates_with @@ -36,7 +36,7 @@ module ActiveRecord #:nodoc: # To cause a validation error, you must add to the <tt>record<tt>'s errors directly # from within the validators message # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def validate # record.errors[:base] << "This is some custom error message" # record.errors[:first_name] << "This is some complex validation" @@ -46,7 +46,7 @@ module ActiveRecord #:nodoc: # # To add behavior to the initialize method, use the following signature: # - # class MyValidator < ActiveRecord::Validator + # class MyValidator < ActiveModel::Validator # def initialize(record, options) # super # @my_custom_field = options[:field_name] || :first_name diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 3c0bd15236..c1a3f6a4a7 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,8 +1,14 @@ -$:.unshift(File.dirname(__FILE__) + '/../../lib') -$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib') +root = File.expand_path('../../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") +end -require 'config' +lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) +require 'config' require 'active_model' # Show backtraces for deprecated behavior for quicker cleanup. diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 4d97af3d13..fe1ea36450 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' class NamingTest < ActiveModel::TestCase def setup - @model_name = ActiveModel::Name.new('Post::TrackBack') + @model_name = ActiveModel::Name.new(self, 'Post::TrackBack') end def test_singular diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb new file mode 100644 index 0000000000..d171784963 --- /dev/null +++ b/activemodel/test/cases/translation_test.rb @@ -0,0 +1,51 @@ +require 'cases/helper' + +class SuperUser + extend ActiveModel::Translation +end + +class User < SuperUser +end + +class ActiveModelI18nTests < ActiveModel::TestCase + + def setup + I18n.backend = I18n::Backend::Simple.new + end + + def test_translated_model_attributes + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } } + assert_equal 'super_user name attribute', SuperUser.human_attribute_name('name') + end + + def test_translated_model_attributes_with_symbols + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } } + assert_equal 'super_user name attribute', SuperUser.human_attribute_name(:name) + end + + def test_translated_model_attributes_with_ancestor + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:user => {:name => 'user name attribute'} } } + assert_equal 'user name attribute', User.human_attribute_name('name') + end + + def test_translated_model_attributes_with_ancestors_fallback + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:super_user => {:name => 'super_user name attribute'} } } + assert_equal 'super_user name attribute', User.human_attribute_name('name') + end + + def test_translated_model_names + I18n.backend.store_translations 'en', :activemodel => {:models => {:super_user => 'super_user model'} } + assert_equal 'super_user model', SuperUser.model_name.human + end + + def test_translated_model_names_with_sti + I18n.backend.store_translations 'en', :activemodel => {:models => {:user => 'user model'} } + assert_equal 'user model', User.model_name.human + end + + def test_translated_model_names_with_ancestors_fallback + I18n.backend.store_translations 'en', :activemodel => {:models => {:super_user => 'super_user model'} } + assert_equal 'super_user model', User.model_name.human + end +end + diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 443a81c6ac..54b2405c92 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -63,7 +63,6 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') end - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index fc4f1926b0..68b1b27f75 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -56,6 +56,12 @@ class I18nValidationTest < ActiveModel::TestCase @person.errors.add_on_blank :title, 'custom' end + def test_errors_full_messages_translates_human_attribute_name_for_model_attributes + @person.errors.add('name', 'empty') + I18n.expects(:translate).with(:"person.name", :default => ['Name'], :scope => [:activemodel, :attributes], :count => 1).returns('Name') + @person.errors.full_messages + end + # ActiveRecord::Validations # validates_confirmation_of w/ mocha def test_validates_confirmation_of_generates_message @@ -494,6 +500,8 @@ class I18nValidationTest < ActiveModel::TestCase assert_equal ['global message'], @person.errors[:title] end + # test with validates_with + def test_validations_with_message_symbol_must_translate I18n.backend.store_translations 'en', :activemodel => {:errors => {:messages => {:custom_error => "I am a custom error"}}} Person.validates_presence_of :title, :message => :custom_error diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index c290b49a28..fae87a6188 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -13,24 +13,24 @@ class ValidatesWithTest < ActiveRecord::TestCase ERROR_MESSAGE = "Validation error from validator" OTHER_ERROR_MESSAGE = "Validation error from other validator" - class ValidatorThatAddsErrors < ActiveRecord::Validator + class ValidatorThatAddsErrors < ActiveModel::Validator def validate() record.errors[:base] << ERROR_MESSAGE end end - class OtherValidatorThatAddsErrors < ActiveRecord::Validator + class OtherValidatorThatAddsErrors < ActiveModel::Validator def validate() record.errors[:base] << OTHER_ERROR_MESSAGE end end - class ValidatorThatDoesNotAddErrors < ActiveRecord::Validator + class ValidatorThatDoesNotAddErrors < ActiveModel::Validator def validate() end end - class ValidatorThatValidatesOptions < ActiveRecord::Validator + class ValidatorThatValidatesOptions < ActiveModel::Validator def validate() if options[:field] == :first_name record.errors[:base] << ERROR_MESSAGE diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 1d8062e042..2511f13fed 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -49,11 +49,13 @@ task :test do run_without_aborting(*tasks) end -task :isolated_test do - tasks = defined?(JRUBY_VERSION) ? - %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) : - %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql) - run_without_aborting(*tasks) +namespace :test do + task :isolated do + tasks = defined?(JRUBY_VERSION) ? + %w(isolated_test_jdbcmysql isolated_test_jdbcsqlite3 isolated_test_jdbcpostgresql) : + %w(isolated_test_mysql isolated_test_sqlite3 isolated_test_postgresql) + run_without_aborting(*tasks) + end end %w( mysql postgresql sqlite3 firebird db2 oracle sybase openbase frontbase jdbcmysql jdbcpostgresql jdbcsqlite3 jdbcderby jdbch2 jdbchsqldb ).each do |adapter| @@ -195,8 +197,6 @@ spec = eval(File.read('activerecord.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 7b6791adc8..c84a3ac5a9 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -9,6 +9,7 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', '= 3.0.pre') s.add_dependency('activemodel', '= 3.0.pre') + s.add_dependency('arel', '= 0.2.pre') s.require_path = 'lib' s.autorequire = 'active_record' diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 2f1e7573d8..8195e78826 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -23,14 +23,13 @@ activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" $:.unshift(activesupport_path) if File.directory?(activesupport_path) -require 'active_support' -begin - require 'active_model' -rescue LoadError - $:.unshift "#{File.dirname(__FILE__)}/../../activemodel/lib" - require 'active_model' -end +activemodel_path = "#{File.dirname(__FILE__)}/../../activemodel/lib" +$:.unshift(activemodel_path) if File.directory?(activemodel_path) + +require 'active_support' +require 'active_model' +require 'arel' module ActiveRecord # TODO: Review explicit loads to see if they will automatically be handled by the initializer. @@ -47,7 +46,9 @@ module ActiveRecord autoload :AssociationPreload, 'active_record/association_preload' autoload :Associations, 'active_record/associations' autoload :AttributeMethods, 'active_record/attribute_methods' + autoload :Attributes, 'active_record/attributes' autoload :AutosaveAssociation, 'active_record/autosave_association' + autoload :Relation, 'active_record/relation' autoload :Base, 'active_record/base' autoload :Batches, 'active_record/batches' autoload :Calculations, 'active_record/calculations' @@ -69,7 +70,7 @@ module ActiveRecord autoload :TestCase, 'active_record/test_case' autoload :Timestamp, 'active_record/timestamp' autoload :Transactions, 'active_record/transactions' - autoload :Validator, 'active_record/validator' + autoload :Types, 'active_record/types' autoload :Validations, 'active_record/validations' module AttributeMethods @@ -82,6 +83,20 @@ module ActiveRecord autoload :Write, 'active_record/attribute_methods/write' end + module Attributes + autoload :Aliasing, 'active_record/attributes/aliasing' + autoload :Store, 'active_record/attributes/store' + autoload :Typecasting, 'active_record/attributes/typecasting' + end + + module Type + autoload :Number, 'active_record/types/number' + autoload :Object, 'active_record/types/object' + autoload :Serialize, 'active_record/types/serialize' + autoload :TimeWithZone, 'active_record/types/time_with_zone' + autoload :Unknown, 'active_record/types/unknown' + end + module Locking autoload :Optimistic, 'active_record/locking/optimistic' autoload :Pessimistic, 'active_record/locking/pessimistic' @@ -92,4 +107,5 @@ module ActiveRecord end end +Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base) I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml' diff --git a/activerecord/lib/active_record/association_preload.rb b/activerecord/lib/active_record/association_preload.rb index e41fda7a4b..9f7b2a60b2 100644 --- a/activerecord/lib/active_record/association_preload.rb +++ b/activerecord/lib/active_record/association_preload.rb @@ -1,3 +1,6 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/enumerable' + module ActiveRecord # See ActiveRecord::AssociationPreload::ClassMethods for documentation. module AssociationPreload #:nodoc: @@ -82,7 +85,7 @@ module ActiveRecord # only one level deep in the +associations+ argument, i.e. it's not passed # to the child associations when +associations+ is a Hash. def preload_associations(records, associations, preload_options={}) - records = [records].flatten.compact.uniq + records = Array.wrap(records).compact.uniq return if records.empty? case associations when Array then associations.each {|association| preload_associations(records, association, preload_options)} @@ -92,7 +95,7 @@ module ActiveRecord raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol) preload_associations(records, parent, preload_options) reflection = reflections[parent] - parents = records.map {|record| record.send(reflection.name)}.flatten.compact + parents = records.sum { |record| Array.wrap(record.send(reflection.name)) } unless parents.empty? parents.first.class.preload_associations(parents, child) end @@ -123,7 +126,8 @@ module ActiveRecord parent_records.each do |parent_record| association_proxy = parent_record.send(reflection_name) association_proxy.loaded - association_proxy.target.push(*[associated_record].flatten) + association_proxy.target.push *Array.wrap(associated_record) + association_proxy.__send__(:set_inverse_instance, associated_record, parent_record) end end @@ -254,6 +258,7 @@ module ActiveRecord through_reflection = reflections[through_association] through_primary_key = through_reflection.primary_key_name + through_records = [] if reflection.options[:source_type] interface = reflection.source_reflection.options[:foreign_type] preload_options = {:conditions => ["#{connection.quote_column_name interface} = ?", reflection.options[:source_type]]} @@ -262,23 +267,22 @@ module ActiveRecord records.first.class.preload_associations(records, through_association, preload_options) # Dont cache the association - we would only be caching a subset - through_records = [] records.each do |record| proxy = record.send(through_association) if proxy.respond_to?(:target) - through_records << proxy.target + through_records.concat Array.wrap(proxy.target) proxy.reset else # this is a has_one :through reflection through_records << proxy if proxy end end - through_records.flatten! else records.first.class.preload_associations(records, through_association) - through_records = records.map {|record| record.send(through_association)}.flatten + records.each do |record| + through_records.concat Array.wrap(record.send(through_association)) + end end - through_records.compact! through_records end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5cce1c8736..0539aa4db2 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -438,7 +438,7 @@ module ActiveRecord # @group.users.collect { |u| u.avatar }.flatten # select all avatars for all users in the group # @group.avatars # selects all avatars by going through the User join model. # - # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are + # An important caveat with going through +has_one+ or +has_many+ associations on the join model is that these associations are # *read-only*. For example, the following would not work following the previous example: # # @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around. @@ -544,14 +544,14 @@ module ActiveRecord # # Since only one table is loaded at a time, conditions or orders cannot reference tables other than the main one. If this is the case # Active Record falls back to the previously used LEFT OUTER JOIN based strategy. For example - # + # # Post.find(:all, :include => [ :author, :comments ], :conditions => ['comments.approved = ?', true]) # # This will result in a single SQL query with joins along the lines of: <tt>LEFT OUTER JOIN comments ON comments.post_id = posts.id</tt> and # <tt>LEFT OUTER JOIN authors ON authors.id = posts.author_id</tt>. Note that using conditions like this can have unintended consequences. # In the above example posts with no approved comments are not returned at all, because the conditions apply to the SQL statement as a whole # and not just to the association. You must disambiguate column references for this fallback to happen, for example - # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not. + # <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not. # # If you do want eager load only some members of an association it is usually more natural to <tt>:include</tt> an association # which has conditions defined on it: @@ -588,7 +588,7 @@ module ActiveRecord # This will execute one query to load the addresses and load the addressables with one query per addressable type. # For example if all the addressables are either of class Person or Company then a total of 3 queries will be executed. The list of # addressable types to load is determined on the back of the addresses loaded. This is not supported if Active Record has to fallback - # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent + # to the previous implementation of eager loading and will raise ActiveRecord::EagerLoadPolymorphicError. The reason is that the parent # model's type is a column value so its corresponding table name cannot be put in the +FROM+/+JOIN+ clauses of that query. # # == Table Aliasing @@ -697,7 +697,7 @@ module ActiveRecord # d.level = 10 # d.level == t.dungeon.level # => false # - # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to the same object data from the database, but are + # The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to the same object data from the database, but are # actually different in-memory copies of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell # +ActiveRecord+ about inverse relationships and it will optimise object loading. For example, if we changed our model definitions to: # @@ -864,8 +864,8 @@ module ActiveRecord # [:autosave] # If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default. # [:inverse_of] - # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt> - # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options. + # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt> + # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail. # # Option examples: @@ -963,7 +963,7 @@ module ActiveRecord # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. # [:through] # Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt> - # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a + # are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a # <tt>has_one</tt> or <tt>belongs_to</tt> association on the join model. # [:source] # Specifies the source association name used by <tt>has_one :through</tt> queries. Only use it if the name cannot be @@ -979,8 +979,8 @@ module ActiveRecord # [:autosave] # If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default. # [:inverse_of] - # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt> - # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options. + # Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt> + # association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail. # # Option examples: @@ -1084,8 +1084,8 @@ module ActiveRecord # If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or # destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute. # [:inverse_of] - # Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt> - # association. Does not work in combination with the <tt>:polymorphic</tt> options. + # Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt> + # association. Does not work in combination with the <tt>:polymorphic</tt> options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional assocations for more detail. # # Option examples: @@ -1227,8 +1227,8 @@ module ActiveRecord # the association will use "project_id" as the default <tt>:association_foreign_key</tt>. # [:conditions] # Specify the conditions that the associated object must meet in order to be included as a +WHERE+ - # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used. - # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt> + # SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used. + # <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt> # or <tt>@blog.posts.build</tt>. # [:order] # Specify the order in which the associated objects are returned as an <tt>ORDER BY</tt> SQL fragment, @@ -1441,12 +1441,12 @@ module ActiveRecord "#{reflection.class_name}.send(:attr_readonly,\"#{cache_column}\".intern) if defined?(#{reflection.class_name}) && #{reflection.class_name}.respond_to?(:attr_readonly)" ) end - + def add_touch_callbacks(reflection, touch_attribute) method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}".to_sym define_method(method_name) do association = send(reflection.name) - + if touch_attribute == true association.touch unless association.nil? else @@ -1457,9 +1457,9 @@ module ActiveRecord after_destroy(method_name) end - def find_with_associations(options = {}) + def find_with_associations(options = {}, join_dependency = nil) catch :invalid_query do - join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) + join_dependency ||= JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins]) rows = select_all_rows(options, join_dependency) return join_dependency.instantiate(rows) end @@ -1479,7 +1479,7 @@ module ActiveRecord if reflection.options.include?(:dependent) # Add polymorphic type if the :as option is present dependent_conditions = [] - dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}" + dependent_conditions << "#{reflection.primary_key_name} = \#{record.#{reflection.name}.send(:owner_quoted_id)}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] dependent_conditions << sanitize_sql(reflection.options[:conditions], reflection.quoted_table_name) if reflection.options[:conditions] dependent_conditions << extra_conditions if extra_conditions @@ -1687,19 +1687,6 @@ module ActiveRecord reflection end - def reflect_on_included_associations(associations) - [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) } - end - - def guard_against_unlimitable_reflections(reflections, options) - if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections) - raise( - ConfigurationError, - "You can not use offset and limit together with has_many or has_and_belongs_to_many associations" - ) - end - end - def select_all_rows(options, join_dependency) connection.select_all( construct_finder_sql_with_included_associations(options, join_dependency), @@ -1707,82 +1694,67 @@ module ActiveRecord ) end - def construct_finder_sql_with_included_associations(options, join_dependency) + def construct_finder_arel_with_included_associations(options, join_dependency) scope = scope(:find) - sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} " - sql << join_dependency.join_associations.collect{|join| join.association_join }.join - add_joins!(sql, options[:joins], scope) - add_conditions!(sql, options[:conditions], scope) - add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + relation = arel_table((scope && scope[:from]) || options[:from]) - add_group!(sql, options[:group], options[:having], scope) - add_order!(sql, options[:order], scope) - add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) - add_lock!(sql, options, scope) + for association in join_dependency.join_associations + relation = association.join_relation(relation) + end - return sanitize_sql(sql) + relation = relation.joins(construct_join(options[:joins], scope)). + select(column_aliases(join_dependency)). + group(construct_group(options[:group], options[:having], scope)). + order(construct_order(options[:order], scope)). + conditions(construct_conditions(options[:conditions], scope)) + + relation = relation.conditions(construct_arel_limited_ids_condition(options, join_dependency)) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) + relation = relation.limit(construct_limit(options[:limit], scope)) if using_limitable_reflections?(join_dependency.reflections) + + relation end - def add_limited_ids_condition!(sql, options, join_dependency) - unless (id_list = select_limited_ids_list(options, join_dependency)).empty? - sql << "#{condition_word(sql)} #{connection.quote_table_name table_name}.#{primary_key} IN (#{id_list}) " - else + def construct_finder_sql_with_included_associations(options, join_dependency) + construct_finder_arel_with_included_associations(options, join_dependency).to_sql + end + + def construct_arel_limited_ids_condition(options, join_dependency) + if (ids_array = select_limited_ids_array(options, join_dependency)).empty? throw :invalid_query + else + Arel::Predicates::In.new( + Arel::SqlLiteral.new("#{connection.quote_table_name table_name}.#{primary_key}"), + ids_array + ) end end - def select_limited_ids_list(options, join_dependency) - pk = columns_hash[primary_key] - + def select_limited_ids_array(options, join_dependency) connection.select_all( construct_finder_sql_for_association_limiting(options, join_dependency), "#{name} Load IDs For Limited Eager Loading" - ).collect { |row| connection.quote(row[primary_key], pk) }.join(", ") + ).collect { |row| row[primary_key] } end def construct_finder_sql_for_association_limiting(options, join_dependency) - scope = scope(:find) - - # Only join tables referenced in order or conditions since this is particularly slow on the pre-query. - tables_from_conditions = conditions_tables(options) - tables_from_order = order_tables(options) - all_tables = tables_from_conditions + tables_from_order - distinct_join_associations = all_tables.uniq.map{|table| - join_dependency.joins_for_table_name(table) - }.flatten.compact.uniq - - order = options[:order] - if scoped_order = (scope && scope[:order]) - order = order ? "#{order}, #{scoped_order}" : scoped_order - end + scope = scope(:find) - is_distinct = !options[:joins].blank? || include_eager_conditions?(options, tables_from_conditions) || include_eager_order?(options, tables_from_order) - sql = "SELECT " - if is_distinct - sql << connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", order) - else - sql << primary_key - end - sql << " FROM #{connection.quote_table_name table_name} " + relation = arel_table(options[:from]) - if is_distinct - sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join - add_joins!(sql, options[:joins], scope) + for association in join_dependency.join_associations + relation = association.join_relation(relation) end - add_conditions!(sql, options[:conditions], scope) - add_group!(sql, options[:group], options[:having], scope) - - if order && is_distinct - connection.add_order_by_for_association_limiting!(sql, :order => order) - else - add_order!(sql, options[:order], scope) - end + relation = relation.joins(construct_join(options[:joins], scope)). + conditions(construct_conditions(options[:conditions], scope)). + group(construct_group(options[:group], options[:having], scope)). + order(construct_order(options[:order], scope)). + limit(construct_limit(options[:limit], scope)). + offset(construct_limit(options[:offset], scope)). + select(connection.distinct("#{connection.quote_table_name table_name}.#{primary_key}", construct_order(options[:order], scope(:find)).join(","))) - add_limit!(sql, options, scope) - - return sanitize_sql(sql) + relation.to_sql end def tables_in_string(string) @@ -1836,7 +1808,7 @@ module ActiveRecord if array_of_strings?(merged_joins) tables_in_string(merged_joins.join(' ')) else - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_joins, nil) join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact end else @@ -1886,10 +1858,6 @@ module ActiveRecord end end - def condition_word(sql) - sql =~ /where/i ? " AND " : "WHERE " - end - def create_extension_modules(association_id, block_extension, extensions) if block_extension extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension" @@ -1953,37 +1921,22 @@ module ActiveRecord reflection = base.reflections[name] is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro) - parent_records = records.map do |record| - descendant = record.send(reflection.name) - next unless descendant - descendant.target.uniq! if is_collection - descendant - end.flatten.compact + parent_records = [] + records.each do |record| + if descendant = record.send(reflection.name) + if is_collection + parent_records.concat descendant.target.uniq + else + parent_records << descendant + end + end + end remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty? end end end - def join_for_table_name(table_name) - join = (@joins.select{|j|j.aliased_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first) rescue nil - return join unless join.nil? - @joins.select{|j|j.is_a?(JoinAssociation) && j.aliased_join_table_name == table_name.gsub(/^\"(.*)\"$/){$1} }.first rescue nil - end - - def joins_for_table_name(table_name) - join = join_for_table_name(table_name) - result = nil - if join && join.is_a?(JoinAssociation) - result = [join] - if join.parent && join.parent.is_a?(JoinAssociation) - result = joins_for_table_name(join.parent.aliased_table_name) + - result - end - end - result - end - protected def build(associations, parent = nil) parent ||= @joins.last @@ -2007,7 +1960,6 @@ module ActiveRecord end end - # overridden in InnerJoinDependency subclass def build_join_association(reflection, parent) JoinAssociation.new(reflection, self, parent) end @@ -2132,6 +2084,7 @@ module ActiveRecord @aliased_prefix = "t#{ join_dependency.joins.size }" @parent_table_name = parent.active_record.table_name @aliased_table_name = aliased_table_name_for(table_name) + @join = nil if reflection.macro == :has_and_belongs_to_many @aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join") @@ -2143,42 +2096,39 @@ module ActiveRecord end def association_join + return @join if @join connection = reflection.active_record.connection - join = case reflection.macro + @join = case reflection.macro when :has_and_belongs_to_many - " #{join_type} %s ON %s.%s = %s.%s " % [ - table_alias_for(options[:join_table], aliased_join_table_name), + ["%s.%s = %s.%s " % [ connection.quote_table_name(aliased_join_table_name), options[:foreign_key] || reflection.active_record.to_s.foreign_key, connection.quote_table_name(parent.aliased_table_name), - reflection.active_record.primary_key] + - " #{join_type} %s ON %s.%s = %s.%s " % [ - table_name_and_alias, + reflection.active_record.primary_key], + "%s.%s = %s.%s " % [ connection.quote_table_name(aliased_table_name), klass.primary_key, connection.quote_table_name(aliased_join_table_name), options[:association_foreign_key] || klass.to_s.foreign_key ] + ] when :has_many, :has_one - case - when reflection.options[:through] - through_conditions = through_reflection.options[:conditions] ? "AND #{interpolate_sql(sanitize_sql(through_reflection.options[:conditions]))}" : '' - - jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil - first_key = second_key = as_extra = nil - - if through_reflection.options[:as] # has_many :through against a polymorphic join - jt_foreign_key = through_reflection.options[:as].to_s + '_id' - jt_as_extra = " AND %s.%s = %s" % [ - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(through_reflection.options[:as].to_s + '_type'), - klass.quote_value(parent.active_record.base_class.name) - ] - else - jt_foreign_key = through_reflection.primary_key_name - end + if reflection.options[:through] + jt_foreign_key = jt_as_extra = jt_source_extra = jt_sti_extra = nil + first_key = second_key = as_extra = nil - case source_reflection.macro + if through_reflection.options[:as] # has_many :through against a polymorphic join + jt_foreign_key = through_reflection.options[:as].to_s + '_id' + jt_as_extra = " AND %s.%s = %s" % [ + connection.quote_table_name(aliased_join_table_name), + connection.quote_column_name(through_reflection.options[:as].to_s + '_type'), + klass.quote_value(parent.active_record.base_class.name) + ] + else + jt_foreign_key = through_reflection.primary_key_name + end + + case source_reflection.macro when :has_many if source_reflection.options[:as] first_key = "#{source_reflection.options[:as]}_id" @@ -2211,65 +2161,77 @@ module ActiveRecord else second_key = source_reflection.primary_key_name end - end - - " #{join_type} %s ON (%s.%s = %s.%s%s%s%s) " % [ - table_alias_for(through_reflection.klass.table_name, aliased_join_table_name), - connection.quote_table_name(parent.aliased_table_name), - connection.quote_column_name(parent.primary_key), - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(jt_foreign_key), - jt_as_extra, jt_source_extra, jt_sti_extra - ] + - " #{join_type} %s ON (%s.%s = %s.%s%s) " % [ - table_name_and_alias, - connection.quote_table_name(aliased_table_name), - connection.quote_column_name(first_key), - connection.quote_table_name(aliased_join_table_name), - connection.quote_column_name(second_key), - as_extra - ] + end + + ["(%s.%s = %s.%s%s%s%s) " % [ + connection.quote_table_name(parent.aliased_table_name), + connection.quote_column_name(parent.primary_key), + connection.quote_table_name(aliased_join_table_name), + connection.quote_column_name(jt_foreign_key), + jt_as_extra, jt_source_extra, jt_sti_extra], + "(%s.%s = %s.%s%s) " % [ + connection.quote_table_name(aliased_table_name), + connection.quote_column_name(first_key), + connection.quote_table_name(aliased_join_table_name), + connection.quote_column_name(second_key), + as_extra] + ] - when reflection.options[:as] && [:has_many, :has_one].include?(reflection.macro) - " #{join_type} %s ON %s.%s = %s.%s AND %s.%s = %s" % [ - table_name_and_alias, - connection.quote_table_name(aliased_table_name), - "#{reflection.options[:as]}_id", - connection.quote_table_name(parent.aliased_table_name), - parent.primary_key, - connection.quote_table_name(aliased_table_name), - "#{reflection.options[:as]}_type", - klass.quote_value(parent.active_record.base_class.name) - ] - else - foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key - " #{join_type} %s ON %s.%s = %s.%s " % [ - table_name_and_alias, - aliased_table_name, - foreign_key, - parent.aliased_table_name, - reflection.options[:primary_key] || parent.primary_key - ] + elsif reflection.options[:as] + "%s.%s = %s.%s AND %s.%s = %s" % [ + connection.quote_table_name(aliased_table_name), + "#{reflection.options[:as]}_id", + connection.quote_table_name(parent.aliased_table_name), + parent.primary_key, + connection.quote_table_name(aliased_table_name), + "#{reflection.options[:as]}_type", + klass.quote_value(parent.active_record.base_class.name) + ] + else + foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key + "%s.%s = %s.%s " % [ + aliased_table_name, + foreign_key, + parent.aliased_table_name, + reflection.options[:primary_key] || parent.primary_key + ] end when :belongs_to - " #{join_type} %s ON %s.%s = %s.%s " % [ - table_name_and_alias, - connection.quote_table_name(aliased_table_name), - reflection.klass.primary_key, - connection.quote_table_name(parent.aliased_table_name), - options[:foreign_key] || reflection.primary_key_name - ] - else - "" - end || '' - join << %(AND %s) % [ + "%s.%s = %s.%s " % [ + connection.quote_table_name(aliased_table_name), + reflection.klass.primary_key, + connection.quote_table_name(parent.aliased_table_name), + options[:foreign_key] || reflection.primary_key_name + ] + end + @join << %(AND %s) % [ klass.send(:type_condition, aliased_table_name)] unless klass.descends_from_active_record? [through_reflection, reflection].each do |ref| - join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions] + @join << "AND #{interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))} " if ref && ref.options[:conditions] end - join + @join + end + + def relation + if reflection.macro == :has_and_belongs_to_many + [Arel::Table.new(table_alias_for(options[:join_table], aliased_join_table_name)), Arel::Table.new(table_name_and_alias)] + elsif reflection.options[:through] + [Arel::Table.new(table_alias_for(through_reflection.klass.table_name, aliased_join_table_name)), Arel::Table.new(table_name_and_alias)] + else + Arel::Table.new(table_name_and_alias) + end + end + + def join_relation(joining_relation, join = nil) + if (relations = relation).is_a?(Array) + joining_relation. + joins(relations.first, Arel::OuterJoin).on(association_join.first). + joins(relations.last, Arel::OuterJoin).on(association_join.last) + else + joining_relation.joins(relations, Arel::OuterJoin).on(association_join) + end end protected @@ -2297,7 +2259,7 @@ module ActiveRecord end def table_alias_for(table_name, table_alias) - "#{reflection.active_record.connection.quote_table_name(table_name)} #{table_alias if table_name != table_alias}".strip + "#{table_name} #{table_alias if table_name != table_alias}".strip end def table_name_and_alias @@ -2307,28 +2269,8 @@ module ActiveRecord def interpolate_sql(sql) instance_eval("%@#{sql.gsub('@', '\@')}@") end - - private - def join_type - "LEFT OUTER JOIN" - end - end - end - - class InnerJoinDependency < JoinDependency # :nodoc: - protected - def build_join_association(reflection, parent) - InnerJoinAssociation.new(reflection, self, parent) - end - - class InnerJoinAssociation < JoinAssociation - private - def join_type - "INNER JOIN" - end end end - end end end diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 1b7bf42b91..25e329c0c1 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -476,7 +476,7 @@ module ActiveRecord def callback(method, record) callbacks_for(method).each do |callback| - ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record) + ActiveSupport::DeprecatedCallbacks::Callback.new(method, callback, record).call(@owner, record) end end diff --git a/activerecord/lib/active_record/associations/association_proxy.rb b/activerecord/lib/active_record/associations/association_proxy.rb index e36b04ea95..7d8f4670fa 100644 --- a/activerecord/lib/active_record/associations/association_proxy.rb +++ b/activerecord/lib/active_record/associations/association_proxy.rb @@ -156,15 +156,6 @@ module ActiveRecord @reflection.options[:dependent] end - # Returns a string with the IDs of +records+ joined with a comma, quoted - # if needed. The result is ready to be inserted into a SQL IN clause. - # - # quoted_record_ids(records) # => "23,56,58,67" - # - def quoted_record_ids(records) - records.map { |record| record.quoted_id }.join(',') - end - def interpolate_sql(sql, record = nil) @owner.send(:interpolate_sql, sql, record) end @@ -265,10 +256,16 @@ module ActiveRecord end end - # Array#flatten has problems with recursive arrays. Going one level - # deeper solves the majority of the problems. - def flatten_deeper(array) - array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + if RUBY_VERSION < '1.9.2' + # Array#flatten has problems with recursive arrays before Ruby 1.9.2. + # Going one level deeper solves the majority of the problems. + def flatten_deeper(array) + array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + end + else + def flatten_deeper(array) + array.flatten + end end # Returns the ID of the owner, quoted if needed. diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 628033c87a..d2f2267e5c 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -36,11 +36,11 @@ module ActiveRecord loaded record end - + def updated? @updated end - + private def find_target find_method = if @reflection.options[:primary_key] diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index 417e2fdc0f..c646fe488b 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -34,7 +34,7 @@ module ActiveRecord options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select]) options[:select] ||= (@reflection.options[:select] || '*') end - + def count_records load_target.size end @@ -56,26 +56,23 @@ module ActiveRecord if @reflection.options[:insert_sql] @owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record)) else + relation = arel_table(@reflection.options[:join_table]) attributes = columns.inject({}) do |attrs, column| case column.name.to_s when @reflection.primary_key_name.to_s - attrs[column.name] = owner_quoted_id + attrs[relation[column.name]] = owner_quoted_id when @reflection.association_foreign_key.to_s - attrs[column.name] = record.quoted_id + attrs[relation[column.name]] = record.quoted_id else if record.has_attribute?(column.name) value = @owner.send(:quote_value, record[column.name], column) - attrs[column.name] = value unless value.nil? + attrs[relation[column.name]] = value unless value.nil? end end attrs end - sql = - "INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " + - "VALUES (#{attributes.values.join(', ')})" - - @owner.connection.insert(sql) + relation.insert(attributes) end return true @@ -85,9 +82,10 @@ module ActiveRecord if sql = @reflection.options[:delete_sql] records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) } else - ids = quoted_record_ids(records) - sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})" - @owner.connection.delete(sql) + relation = arel_table(@reflection.options[:join_table]) + relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id). + and(Arel::Predicates::In.new(relation[@reflection.association_foreign_key], records.map(&:id))) + ).delete end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 73d3c23cd3..cd31b0e211 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -40,11 +40,11 @@ module ActiveRecord # we are certain the current target is an empty array. This is a # documented side-effect of the method that may avoid an extra SELECT. @target ||= [] and loaded if count == 0 - + if @reflection.options[:limit] count = [ @reflection.options[:limit], count ].min end - + return count end @@ -69,11 +69,11 @@ module ActiveRecord when :delete_all @reflection.klass.delete(records.map { |record| record.id }) else - ids = quoted_record_ids(records) - @reflection.klass.update_all( - "#{@reflection.primary_key_name} = NULL", - "#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})" - ) + relation = arel_table(@reflection.table_name) + relation.conditions(relation[@reflection.primary_key_name].eq(@owner.id). + and(Arel::Predicates::In.new(relation[@reflection.klass.primary_key], records.map(&:id))) + ).update(relation[@reflection.primary_key_name] => nil) + @owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter? end end @@ -88,11 +88,11 @@ module ActiveRecord @finder_sql = interpolate_sql(@reflection.options[:finder_sql]) when @reflection.options[:as] - @finder_sql = + @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " + "#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}" @finder_sql << " AND (#{conditions})" if conditions - + else @finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}" @finder_sql << " AND (#{conditions})" if conditions diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 829f0ac0c5..214ce5959a 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -56,7 +56,7 @@ module ActiveRecord options[:joins] = construct_joins(options[:joins]) options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include] end - + def insert_record(record, force = true, validate = true) if record.new_record? if force diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index 16b6123439..1924156e2a 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -42,7 +42,7 @@ module ActiveRecord end def construct_from - @reflection.quoted_table_name + @reflection.table_name end def construct_select(custom_select = nil) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ab7ad34b9e..3a9a67e3a2 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -31,11 +31,10 @@ module ActiveRecord self.class.define_attribute_methods method_name = method_id.to_s guard_private_attribute_method!(method_name, args) - if self.class.generated_attribute_methods.instance_methods.include?(method_name) - return self.send(method_id, *args, &block) - end + send(method_id, *args, &block) + else + super end - super end def respond_to?(*args) diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index a4e144f233..74921241f7 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -8,25 +8,18 @@ module ActiveRecord end def read_attribute_before_type_cast(attr_name) - @attributes[attr_name] + _attributes.without_typecast[attr_name] end # Returns a hash of attributes before typecasting and deserialization. def attributes_before_type_cast - self.attribute_names.inject({}) do |attrs, name| - attrs[name] = read_attribute_before_type_cast(name) - attrs - end + _attributes.without_typecast end private # Handle *_before_type_cast for method_missing. def attribute_before_type_cast(attribute_name) - if attribute_name == 'id' - read_attribute_before_type_cast(self.class.primary_key) - else - read_attribute_before_type_cast(attribute_name) - end + read_attribute_before_type_cast(attribute_name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 4df0f1af69..4a3ab9ea82 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/tap' - module ActiveRecord module AttributeMethods module Dirty diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index a949d80120..0154ee35f8 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -8,23 +8,7 @@ module ActiveRecord end def query_attribute(attr_name) - unless value = read_attribute(attr_name) - false - else - column = self.class.columns_hash[attr_name] - if column.nil? - if Numeric === value || value !~ /[^0-9]/ - !value.to_i.zero? - else - return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) - !value.blank? - end - elsif column.number? - !value.zero? - else - !value.blank? - end - end + _attributes.has?(attr_name) end private @@ -35,3 +19,5 @@ module ActiveRecord end end end + + diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 3da3d9d8cc..97caec7744 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -37,11 +37,7 @@ module ActiveRecord protected def define_method_attribute(attr_name) - if self.serialized_attributes[attr_name] - define_read_method_for_serialized_attribute(attr_name) - else - define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) - end + define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) if attr_name == primary_key && attr_name != "id" define_read_method(:id, attr_name, columns_hash[attr_name]) @@ -49,18 +45,12 @@ module ActiveRecord end private - # Define read method for serialized attribute. - def define_read_method_for_serialized_attribute(attr_name) - generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) - end # Define an attribute reader method. Cope with nil column. def define_read_method(symbol, attr_name, column) - cast_code = column.type_cast_code('v') if column - access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" - + access_code = "_attributes['#{attr_name}']" unless attr_name.to_s == self.primary_key.to_s - access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") + access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless _attributes.key?('#{attr_name}'); ") end if cache_attribute?(attr_name) @@ -73,38 +63,7 @@ module ActiveRecord # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example, # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' - if !(value = @attributes[attr_name]).nil? - if column = column_for_attribute(attr_name) - if unserializable_attribute?(attr_name, column) - unserialize_attribute(attr_name) - else - column.type_cast(value) - end - else - value - end - else - nil - end - end - - # Returns true if the attribute is of a text column and marked for serialization. - def unserializable_attribute?(attr_name, column) - column.text? && self.class.serialized_attributes[attr_name] - end - - # Returns the unserialized object of the attribute. - def unserialize_attribute(attr_name) - unserialized_object = object_from_yaml(@attributes[attr_name]) - - if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil? - @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object - else - raise SerializationTypeMismatch, - "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}" - end + _attributes[attr_name] end private diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index a8e3e28a7a..4ac0c7f608 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -12,48 +12,20 @@ module ActiveRecord end module ClassMethods + + def cache_attribute?(attr_name) + time_zone_aware?(attr_name) || super + end + protected - # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone. - def define_method_attribute(attr_name) - if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) - method_body = <<-EOV - def #{attr_name}(reload = false) - cached = @attributes_cache['#{attr_name}'] - return cached if cached && !reload - time = read_attribute('#{attr_name}') - @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time - end - EOV - generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__) - else - super - end - end - # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone. - def define_method_attribute=(attr_name) - if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) - method_body = <<-EOV - def #{attr_name}=(time) - unless time.acts_like?(:time) - time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time - end - time = time.in_time_zone rescue nil if time - write_attribute(:#{attr_name}, time) - end - EOV - generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__) - else - super - end + def time_zone_aware?(attr_name) + column = columns_hash[attr_name] + time_zone_aware_attributes && + !skip_time_zone_conversion_for_attributes.include?(attr_name.to_sym) && + [:datetime, :timestamp].include?(column.type) end - private - def create_time_zone_conversion_attribute?(name, column) - time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type) - end end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index e31acac050..37eadbe0a9 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,14 +17,9 @@ module ActiveRecord # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float # columns are turned into +nil+. def write_attribute(attr_name, value) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' + attr_name = _attributes.unalias(attr_name) @attributes_cache.delete(attr_name) - if (column = column_for_attribute(attr_name)) && column.number? - @attributes[attr_name] = convert_number_column_value(value) - else - @attributes[attr_name] = value - end + _attributes[attr_name] = value end private diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb new file mode 100644 index 0000000000..e4d9e89821 --- /dev/null +++ b/activerecord/lib/active_record/attributes.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module Attributes + + # Returns true if the given attribute is in the attributes hash + def has_attribute?(attr_name) + _attributes.key?(attr_name) + end + + # Returns an array of names for the attributes available on this object sorted alphabetically. + def attribute_names + _attributes.keys.sort! + end + + # Returns a hash of all the attributes with their names as keys and the values of the attributes as values. + def attributes + attributes = _attributes.dup + attributes.typecast! unless _attributes.frozen? + attributes.to_h + end + + protected + + # Not to be confused with the public #attributes method, which returns a typecasted Hash. + def _attributes + @attributes + end + + def initialize_attribute_store(merge_attributes = nil) + @attributes = ActiveRecord::Attributes::Store.new + @attributes.merge!(merge_attributes) if merge_attributes + @attributes.types.merge!(self.class.attribute_types) + @attributes.aliases.merge!('id' => self.class.primary_key) unless 'id' == self.class.primary_key + @attributes + end + + end +end diff --git a/activerecord/lib/active_record/attributes/aliasing.rb b/activerecord/lib/active_record/attributes/aliasing.rb new file mode 100644 index 0000000000..db77739d1f --- /dev/null +++ b/activerecord/lib/active_record/attributes/aliasing.rb @@ -0,0 +1,42 @@ +module ActiveRecord + module Attributes + module Aliasing + # Allows access to keys using aliased names. + # + # Example: + # class Attributes < Hash + # include Aliasing + # end + # + # attributes = Attributes.new + # attributes.aliases['id'] = 'fancy_primary_key' + # attributes['fancy_primary_key'] = 2020 + # + # attributes['id'] + # => 2020 + # + # Additionally, symbols are always aliases of strings: + # attributes[:fancy_primary_key] + # => 2020 + # + def [](key) + super(unalias(key)) + end + + def []=(key, value) + super(unalias(key), value) + end + + def aliases + @aliases ||= {} + end + + def unalias(key) + key = key.to_s + aliases[key] || key + end + + end + end +end + diff --git a/activerecord/lib/active_record/attributes/store.rb b/activerecord/lib/active_record/attributes/store.rb new file mode 100644 index 0000000000..61109f4acc --- /dev/null +++ b/activerecord/lib/active_record/attributes/store.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module Attributes + class Store < Hash + include ActiveRecord::Attributes::Typecasting + include ActiveRecord::Attributes::Aliasing + + # Attributes not mapped to a column are handled using Type::Unknown, + # which enables boolean typecasting for unmapped keys. + def types + @types ||= Hash.new(Type::Unknown.new) + end + + end + end +end diff --git a/activerecord/lib/active_record/attributes/typecasting.rb b/activerecord/lib/active_record/attributes/typecasting.rb new file mode 100644 index 0000000000..56c32f9895 --- /dev/null +++ b/activerecord/lib/active_record/attributes/typecasting.rb @@ -0,0 +1,117 @@ +module ActiveRecord + module Attributes + module Typecasting + # Typecasts values during access based on their key mapping to a Type. + # + # Example: + # class Attributes < Hash + # include Typecasting + # end + # + # attributes = Attributes.new + # attributes.types['comments_count'] = Type::Integer + # attributes['comments_count'] = '5' + # + # attributes['comments_count'] + # => 5 + # + # To support keys not mapped to a typecaster, add a default to types. + # attributes.types.default = Type::Unknown + # attributes['age'] = '25' + # attributes['age'] + # => '25' + # + # A valid type supports #cast, #precast, #boolean, and #appendable? methods. + # + def [](key) + value = super(key) + typecast_read(key, value) + end + + def []=(key, value) + super(key, typecast_write(key, value)) + end + + def to_h + hash = {} + hash.merge!(self) + hash + end + + def dup # :nodoc: + copy = super + copy.types = types.dup + copy + end + + # Provides a duplicate with typecasting disabled. + # + # Example: + # attributes = Attributes.new + # attributes.types['comments_count'] = Type::Integer + # attributes['comments_count'] = '5' + # + # attributes.without_typecast['comments_count'] + # => '5' + # + def without_typecast + dup.without_typecast! + end + + def without_typecast! + types.clear + self + end + + def typecast! + keys.each { |key| self[key] = self[key] } + self + end + + # Check if key has a value that typecasts to true. + # + # attributes = Attributes.new + # attributes.types['comments_count'] = Type::Integer + # + # attributes['comments_count'] = 0 + # attributes.has?('comments_count') + # => false + # + # attributes['comments_count'] = 1 + # attributes.has?('comments_count') + # => true + # + def has?(key) + value = self[key] + boolean_typecast(key, value) + end + + def types + @types ||= {} + end + + protected + + def types=(other_types) + @types = other_types + end + + def boolean_typecast(key, value) + value ? types[key].boolean(value) : false + end + + def typecast_read(key, value) + type = types[key] + value = type.cast(value) + self[key] = value if type.appendable? && !frozen? + + value + end + + def typecast_write(key, value) + types[key].precast(value) + end + + end + end +end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 150e3fc263..056f29f029 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1,5 +1,7 @@ +require 'benchmark' require 'yaml' require 'set' +require 'active_support/benchmarkable' require 'active_support/dependencies' require 'active_support/time' require 'active_support/core_ext/class/attribute_accessors' @@ -10,7 +12,6 @@ require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/behavior' -require 'active_support/core_ext/symbol' require 'active_support/core_ext/object/metaclass' module ActiveRecord #:nodoc: @@ -421,7 +422,7 @@ module ActiveRecord #:nodoc: # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all # instances in the current object space. class Base - ## + ## # :singleton-method: # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+. @@ -455,11 +456,11 @@ module ActiveRecord #:nodoc: # as a Hash. # # For example, the following database.yml... - # + # # development: # adapter: sqlite3 # database: db/development.sqlite3 - # + # # production: # adapter: sqlite3 # database: db/production.sqlite3 @@ -661,10 +662,26 @@ module ActiveRecord #:nodoc: 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) + # Returns an ActiveRecord::Relation object. You can pass in all the same arguments to this method as you can + # to find(:all). def all(*args) - find(:all, *args) + options = args.extract_options! + + if options.empty? && !scoped?(:find) + relation = arel_table + else + relation = construct_finder_arel(options) + include_associations = merge_includes(scope(:find, :include), options[:include]) + + if include_associations.any? + if references_eager_loaded_tables?(options) + relation.eager_load(include_associations) + else + relation.preload(include_associations) + end + end + end + relation end # Executes a custom SQL query against your database and returns all the results. The results will @@ -860,26 +877,23 @@ module ActiveRecord #:nodoc: # # Update all books that match our conditions, but limit it to 5 ordered by date # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5 def update_all(updates, conditions = nil, options = {}) - sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} " - scope = scope(:find) - select_sql = "" - add_conditions!(select_sql, conditions, scope) + relation = arel_table - if options.has_key?(:limit) || (scope && scope[:limit]) + if conditions = construct_conditions(conditions, scope) + relation = relation.conditions(Arel::SqlLiteral.new(conditions)) + end + + relation = if options.has_key?(:limit) || (scope && scope[:limit]) # Only take order from scope if limit is also provided by scope, this # is useful for updating a has_many association with a limit. - add_order!(select_sql, options[:order], scope) - - add_limit!(select_sql, options, scope) - sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key))) + relation.order(construct_order(options[:order], scope)).limit(construct_limit(options[:limit], scope)) else - add_order!(select_sql, options[:order], nil) - sql.concat(select_sql) + relation.order(options[:order]) end - connection.update(sql, "#{name} Update") + relation.update(sanitize_sql_for_assignment(updates)) end # Destroys the records matching +conditions+ by instantiating each @@ -930,9 +944,11 @@ module ActiveRecord #:nodoc: # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead. def delete_all(conditions = nil) - sql = "DELETE FROM #{quoted_table_name} " - add_conditions!(sql, conditions, scope(:find)) - connection.delete(sql, "#{name} Delete all") + if conditions + arel_table.conditions(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete + else + arel_table.delete + end end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. @@ -1060,7 +1076,7 @@ module ActiveRecord #:nodoc: # If the access logic of your application is richer you can use <tt>Hash#except</tt> # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are # passed to Active Record. - # + # # For example, it could be the case that the list of protected attributes # for a given model depends on the role of the user: # @@ -1108,7 +1124,7 @@ module ActiveRecord #:nodoc: # If the access logic of your application is richer you can use <tt>Hash#except</tt> # or <tt>Hash#slice</tt> to sanitize the hash of parameters before they are # passed to Active Record. - # + # # For example, it could be the case that the list of accessible attributes # for a given model depends on the role of the user: # @@ -1135,7 +1151,7 @@ module ActiveRecord #:nodoc: # Returns an array of all the attributes that have been specified as readonly. def readonly_attributes - read_inheritable_attribute(:attr_readonly) + read_inheritable_attribute(:attr_readonly) || [] end # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object, @@ -1362,17 +1378,18 @@ module ActiveRecord #:nodoc: # end def reset_column_information undefine_attribute_methods - @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil + @arel_table = @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information } end - def self_and_descendants_from_active_record#nodoc: + # Set the lookup ancestors for ActiveModel. + def lookup_ancestors #:nodoc: klass = self classes = [klass] - while klass != klass.base_class + while klass != klass.base_class classes << klass = klass.superclass end classes @@ -1383,32 +1400,9 @@ module ActiveRecord #:nodoc: [self] end - # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example: - # Person.human_attribute_name("first_name") # => "First name" - # This used to be deprecated in favor of humanize, but is now preferred, because it automatically uses the I18n - # module now. - # Specify +options+ with additional translating options. - def human_attribute_name(attribute_key_name, options = {}) - defaults = self_and_descendants_from_active_record.map do |klass| - :"#{klass.name.underscore}.#{attribute_key_name}" - end - defaults << options[:default] if options[:default] - defaults.flatten! - defaults << attribute_key_name.to_s.humanize - options[:count] ||= 1 - I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:activerecord, :attributes])) - end - - # Transform the modelname into a more humane format, using I18n. - # By default, it will underscore then humanize the class name (BlogPost.human_name #=> "Blog post"). - # Default scope of the translation is activerecord.models - # Specify +options+ with additional translating options. - def human_name(options = {}) - defaults = self_and_descendants_from_active_record.map do |klass| - :"#{klass.name.underscore}" - end - defaults << self.name.underscore.humanize - I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options)) + # Set the i18n scope to overwrite ActiveModel. + def i18n_scope #:nodoc: + :activerecord end # True if this isn't a concrete subclass needing a STI type condition. @@ -1448,38 +1442,6 @@ module ActiveRecord #:nodoc: connection.quote(object) end - # Log and benchmark multiple statements in a single block. Example: - # - # Project.benchmark("Creating project") do - # project = Project.create("name" => "stuff") - # project.create_manager("name" => "David") - # project.milestones << Milestone.find(:all) - # end - # - # The benchmark is only recorded if the current level of the logger is less than or equal to the <tt>log_level</tt>, - # which makes it easy to include benchmarking statements in production software that will remain inexpensive because - # the benchmark will only be conducted if the log level is low enough. - # - # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false. - def benchmark(title, log_level = Logger::DEBUG, use_silence = true) - if logger && logger.level <= log_level - result = nil - ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } - logger.add(log_level, '%s (%.1fms)' % [title, ms]) - result - else - yield - end - end - - # Silences the logger for the duration of the block. - def silence - old_logger_level, logger.level = logger.level, Logger::ERROR if logger - yield - ensure - logger.level = old_logger_level if logger - end - # Overwrite the default class equality method to provide support for association proxies. def ===(object) object.is_a?(self) @@ -1529,6 +1491,15 @@ module ActiveRecord #:nodoc: "(#{segments.join(') AND (')})" unless segments.empty? end + + def arel_table(table = nil) + table = table_name if table.blank? + if @arel_table.nil? || @arel_table.name != table + @arel_table = Relation.new(self, Arel::Table.new(table)) + end + @arel_table + end + private def find_initial(options) options.update(:limit => 1) @@ -1650,7 +1621,7 @@ module ActiveRecord #:nodoc: def instantiate(record) object = find_sti_class(record[inheritance_column]).allocate - object.instance_variable_set(:'@attributes', record) + object.send(:initialize_attribute_store, record) object.instance_variable_set(:'@attributes_cache', {}) object.send(:_run_find_callbacks) @@ -1693,22 +1664,83 @@ module ActiveRecord #:nodoc: end end - def construct_finder_sql(options) - scope = scope(:find) - sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} " - sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} " + def construct_finder_arel(options = {}, scope = scope(:find)) + # TODO add lock to Arel + relation = arel_table(options[:from]). + joins(construct_join(options[:joins], scope)). + conditions(construct_conditions(options[:conditions], scope)). + select(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))). + group(construct_group(options[:group], options[:having], scope)). + order(construct_order(options[:order], scope)). + limit(construct_limit(options[:limit], scope)). + offset(construct_offset(options[:offset], scope)) + + relation = relation.readonly if options[:readonly] - add_joins!(sql, options[:joins], scope) - add_conditions!(sql, options[:conditions], scope) + relation + end + + def construct_finder_sql(options, scope = scope(:find)) + construct_finder_arel(options, scope).to_sql + end - add_group!(sql, options[:group], options[:having], scope) - add_order!(sql, options[:order], scope) - add_limit!(sql, options, scope) - add_lock!(sql, options, scope) + def construct_join(joins, scope) + merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) + case merged_joins + when Symbol, Hash, Array + if array_of_strings?(merged_joins) + merged_joins.join(' ') + " " + else + build_association_joins(merged_joins) + end + when String + " #{merged_joins} " + else + "" + end + end + def construct_group(group, having, scope) + sql = '' + if group + sql << group.to_s + sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having + elsif scope && (scoped_group = scope[:group]) + sql << scoped_group.to_s + sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having] + end sql end + def construct_order(order, scope) + orders = [] + scoped_order = scope[:order] if scope + if order + orders << order + orders << scoped_order if scoped_order && scoped_order != order + elsif scoped_order + orders << scoped_order + end + orders + end + + def construct_limit(limit, scope) + limit ||= scope[:limit] if scope + limit + end + + def construct_offset(offset, scope) + offset ||= scope[:offset] if scope + offset + end + + def construct_conditions(conditions, scope) + conditions = [conditions] + conditions << scope[:conditions] if scope + conditions << type_condition if finder_needs_type_condition? + merge_conditions(*conditions) + end + # Merges includes so that the result is a valid +include+ def merge_includes(first, second) (safe_to_array(first) + safe_to_array(second)).uniq @@ -1718,10 +1750,7 @@ module ActiveRecord #:nodoc: if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) } joins = joins.collect do |join| join = [join] if join.is_a?(String) - unless array_of_strings?(join) - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil) - join = join_dependency.join_associations.collect { |assoc| assoc.association_join } - end + join = build_association_joins(join) unless array_of_strings?(join) join end joins.flatten.map{|j| j.strip}.uniq @@ -1730,6 +1759,19 @@ module ActiveRecord #:nodoc: end end + def build_association_joins(joins) + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil) + relation = arel_table.relation + join_dependency.join_associations.map { |association| + if (association_relation = association.relation).is_a?(Array) + [Arel::InnerJoin.new(relation, association_relation.first, association.association_join.first).joins(relation), + Arel::InnerJoin.new(relation, association_relation.last, association.association_join.last).joins(relation)].join() + else + Arel::InnerJoin.new(relation, association_relation, association.association_join).joins(relation) + end + }.join(" ") + end + # Object#to_a is deprecated, though it does have the desired behavior def safe_to_array(o) case o @@ -1746,44 +1788,6 @@ module ActiveRecord #:nodoc: o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end - def add_order!(sql, order, scope = :auto) - scope = scope(:find) if :auto == scope - scoped_order = scope[:order] if scope - if order - sql << " ORDER BY #{order}" - if scoped_order && scoped_order != order - sql << ", #{scoped_order}" - end - else - sql << " ORDER BY #{scoped_order}" if scoped_order - end - end - - def add_group!(sql, group, having, scope = :auto) - if group - sql << " GROUP BY #{group}" - sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having - else - scope = scope(:find) if :auto == scope - if scope && (scoped_group = scope[:group]) - sql << " GROUP BY #{scoped_group}" - sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having] - end - end - end - - # The optional scope argument is for the current <tt>:find</tt> scope. - def add_limit!(sql, options, scope = :auto) - scope = scope(:find) if :auto == scope - - if scope - options[:limit] ||= scope[:limit] - options[:offset] ||= scope[:offset] - end - - connection.add_limit_offset!(sql, options) - end - # The optional scope argument is for the current <tt>:find</tt> scope. # The <tt>:lock</tt> option has precedence over a scoped <tt>:lock</tt>. def add_lock!(sql, options, scope = :auto) @@ -1792,38 +1796,10 @@ module ActiveRecord #:nodoc: connection.add_lock!(sql, options) end - # The optional scope argument is for the current <tt>:find</tt> scope. - def add_joins!(sql, joins, scope = :auto) - scope = scope(:find) if :auto == scope - merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) - case merged_joins - when Symbol, Hash, Array - if array_of_strings?(merged_joins) - sql << merged_joins.join(' ') + " " - else - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) - sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " - end - when String - sql << " #{merged_joins} " - end - end - - # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed. - # The optional scope argument is for the current <tt>:find</tt> scope. - def add_conditions!(sql, conditions, scope = :auto) - scope = scope(:find) if :auto == scope - conditions = [conditions] - conditions << scope[:conditions] if scope - conditions << type_condition if finder_needs_type_condition? - merged_conditions = merge_conditions(*conditions) - sql << "WHERE #{merged_conditions} " unless merged_conditions.blank? - end - def type_condition(table_alias=nil) quoted_table_alias = self.connection.quote_table_name(table_alias || table_name) quoted_inheritance_column = connection.quote_column_name(inheritance_column) - type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass| + type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' " ) do |condition, subclass| condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' " end @@ -1964,7 +1940,7 @@ module ActiveRecord #:nodoc: attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments( [:#{attribute_names.join(',:')}], args # [:user_name, :password], args ) # ) - # + # scoped(:conditions => attributes) # scoped(:conditions => attributes) end # end }, __FILE__, __LINE__ @@ -2416,7 +2392,7 @@ module ActiveRecord #:nodoc: # In both instances, valid attribute keys are determined by the column names of the associated table -- # hence you can't have attributes that aren't part of the table columns. def initialize(attributes = nil) - @attributes = attributes_from_column_definition + initialize_attribute_store(attributes_from_column_definition) @attributes_cache = {} @new_record = true ensure_proper_type @@ -2442,7 +2418,7 @@ module ActiveRecord #:nodoc: callback(:after_initialize) if respond_to_without_attributes?(:after_initialize) cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast) cloned_attributes.delete(self.class.primary_key) - @attributes = cloned_attributes + initialize_attribute_store(cloned_attributes) clear_aggregation_cache @attributes_cache = {} @new_record = true @@ -2469,7 +2445,7 @@ module ActiveRecord #:nodoc: # name # end # end - # + # # user = User.find_by_name('Phusion') # user_path(user) # => "/users/Phusion" def to_param @@ -2520,12 +2496,12 @@ module ActiveRecord #:nodoc: # If +perform_validation+ is true validations run. If any of them fail # the action is cancelled and +save+ returns +false+. If the flag is # false validations are bypassed altogether. See - # ActiveRecord::Validations for more information. + # ActiveRecord::Validations for more information. # # There's a series of callbacks associated with +save+. If any of the # <tt>before_*</tt> callbacks return +false+ the action is cancelled and # +save+ returns +false+. See ActiveRecord::Callbacks for further - # details. + # details. def save create_or_update end @@ -2537,12 +2513,12 @@ module ActiveRecord #:nodoc: # # With <tt>save!</tt> validations always run. If any of them fail # ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations - # for more information. + # for more information. # # There's a series of callbacks associated with <tt>save!</tt>. If any of # the <tt>before_*</tt> callbacks return +false+ the action is cancelled # and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See - # ActiveRecord::Callbacks for further details. + # ActiveRecord::Callbacks for further details. def save! create_or_update || raise(RecordNotSaved) end @@ -2567,11 +2543,7 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - connection.delete( - "DELETE FROM #{self.class.quoted_table_name} " + - "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}", - "#{self.class.name} Destroy" - ) + self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).delete end @destroyed = true @@ -2672,7 +2644,7 @@ module ActiveRecord #:nodoc: def reload(options = nil) clear_aggregation_cache clear_association_cache - @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) + _attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes')) @attributes_cache = {} self end @@ -2702,12 +2674,12 @@ module ActiveRecord #:nodoc: # class User < ActiveRecord::Base # attr_protected :is_admin # end - # + # # user = User.new # user.attributes = { :username => 'Phusion', :is_admin => true } # user.username # => "Phusion" # user.is_admin? # => false - # + # # user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false) # user.is_admin? # => true def attributes=(new_attributes, guard_protected_attributes = true) @@ -2769,16 +2741,6 @@ module ActiveRecord #:nodoc: !value.blank? end - # Returns true if the given attribute is in the attributes hash - def has_attribute?(attr_name) - @attributes.has_key?(attr_name.to_s) - end - - # Returns an array of names for the attributes available on this object sorted alphabetically. - def attribute_names - @attributes.keys.sort - end - # Returns the column object for the named attribute. def column_for_attribute(name) self.class.columns_hash[name.to_s] @@ -2866,14 +2828,9 @@ module ActiveRecord #:nodoc: # Updates the associated record with values matching those of the instance attributes. # Returns the number of affected rows. def update(attribute_names = @attributes.keys) - quoted_attributes = attributes_with_quotes(false, false, attribute_names) - return 0 if quoted_attributes.empty? - connection.update( - "UPDATE #{self.class.quoted_table_name} " + - "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " + - "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}", - "#{self.class.name} Update" - ) + attributes_with_values = arel_attributes_values(false, false, attribute_names) + return 0 if attributes_with_values.empty? + self.class.arel_table.conditions(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values) end # Creates a record with values matching those of the instance attributes @@ -2883,18 +2840,15 @@ module ActiveRecord #:nodoc: self.id = connection.next_sequence_value(self.class.sequence_name) end - quoted_attributes = attributes_with_quotes + attributes_values = arel_attributes_values - statement = if quoted_attributes.empty? - connection.empty_insert_statement(self.class.table_name) + new_id = if attributes_values.empty? + self.class.arel_table.insert connection.empty_insert_statement_value else - "INSERT INTO #{self.class.quoted_table_name} " + - "(#{quoted_column_names.join(', ')}) " + - "VALUES(#{quoted_attributes.values.join(', ')})" + self.class.arel_table.insert attributes_values end - self.id = connection.insert(statement, "#{self.class.name} Create", - self.class.primary_key, self.id, self.class.sequence_name) + self.id ||= new_id @new_record = false id @@ -2910,18 +2864,6 @@ module ActiveRecord #:nodoc: end end - def convert_number_column_value(value) - if value == false - 0 - elsif value == true - 1 - elsif value.is_a?(String) && value.blank? - nil - else - value - end - end - def remove_attributes_protected_from_mass_assignment(attributes) safe_attributes = if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil? @@ -2985,6 +2927,26 @@ module ActiveRecord #:nodoc: include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) end + # Returns a copy of the attributes hash where all the values have been safely quoted for use in + # an Arel insert/update method. + def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys) + attrs = {} + attribute_names.each do |name| + if (column = column_for_attribute(name)) && (include_primary_key || !column.primary) + + if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name)) + value = read_attribute(name) + + if value && ((self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time))) || value.is_a?(Hash) || value.is_a?(Array)) + value = value.to_yaml + end + attrs[self.class.arel_table[name]] = value + end + end + end + attrs + end + # Quote strings appropriately for SQL statements. def quote_value(value, column = nil) self.class.connection.quote(value, column) @@ -3020,7 +2982,7 @@ module ActiveRecord #:nodoc: end def instantiate_time_object(name, values) - if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name)) + if self.class.send(:time_zone_aware?, name) Time.zone.local(*values) else Time.time_with_datetime_fallback(@@default_timezone, *values) @@ -3092,13 +3054,6 @@ module ActiveRecord #:nodoc: hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ") end - def quoted_column_names(attributes = attributes_with_quotes) - connection = self.class.connection - attributes.keys.collect do |column_name| - connection.quote_column_name(column_name) - end - end - def self.quoted_table_name self.connection.quote_table_name(self.table_name) end @@ -3114,15 +3069,13 @@ module ActiveRecord #:nodoc: comma_pair_list(quote_columns(quoter, hash)) end - def object_from_yaml(string) - return string unless string.is_a?(String) && string =~ /^---/ - YAML::load(string) rescue string - end end Base.class_eval do extend ActiveModel::Naming extend QueryCache::ClassMethods + extend ActiveSupport::Benchmarkable + include Validations include Locking::Optimistic, Locking::Pessimistic include AttributeMethods @@ -3130,6 +3083,7 @@ module ActiveRecord #:nodoc: include AttributeMethods::PrimaryKey include AttributeMethods::TimeZoneConversion include AttributeMethods::Dirty + include Attributes, Types include Callbacks, ActiveModel::Observing, Timestamp include Associations, AssociationPreload, NamedScope include ActiveModel::Conversion @@ -3139,6 +3093,7 @@ module ActiveRecord #:nodoc: include AutosaveAssociation, NestedAttributes include Aggregations, Transactions, Reflection, Batches, Calculations, Serialization + end end diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 646fed1a0b..40242333e5 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -53,7 +53,7 @@ module ActiveRecord # # Person.average('age') # => 35.8 def average(column_name, options = {}) - calculate(:avg, column_name, options) + calculate(:average, column_name, options) end # Calculates the minimum value on a given column. The value is returned @@ -62,7 +62,7 @@ module ActiveRecord # # Person.minimum('age') # => 7 def minimum(column_name, options = {}) - calculate(:min, column_name, options) + calculate(:minimum, column_name, options) end # Calculates the maximum value on a given column. The value is returned @@ -71,7 +71,7 @@ module ActiveRecord # # Person.maximum('age') # => 93 def maximum(column_name, options = {}) - calculate(:max, column_name, options) + calculate(:maximum, column_name, options) end # Calculates the sum of values on a given column. The value is returned @@ -123,20 +123,97 @@ module ActiveRecord # Person.sum("2 * age") def calculate(operation, column_name, options = {}) validate_calculation_options(operation, options) - column_name = options[:select] if options[:select] - column_name = '*' if column_name == :all - column = column_for column_name + operation = operation.to_s.downcase + + scope = scope(:find) + + merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) + + if operation == "count" + if merged_includes.any? + distinct = true + column_name = options[:select] || primary_key + end + + distinct = nil if column_name.to_s =~ /\s*DISTINCT\s+/i + distinct ||= options[:distinct] + else + distinct = nil + end + catch :invalid_query do + relation = if merged_includes.any? + join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, construct_join(options[:joins], scope)) + construct_finder_arel_with_included_associations(options, join_dependency) + else + relation = arel_table(options[:from]). + joins(construct_join(options[:joins], scope)). + conditions(construct_conditions(options[:conditions], scope)). + order(options[:order]). + limit(options[:limit]). + offset(options[:offset]) + end if options[:group] - return execute_grouped_calculation(operation, column_name, column, options) + return execute_grouped_calculation(operation, column_name, options, relation) else - return execute_simple_calculation(operation, column_name, column, options) + return execute_simple_calculation(operation, column_name, options.merge(:distinct => distinct), relation) end end 0 end - protected + def execute_simple_calculation(operation, column_name, options, relation) #:nodoc: + column = if column_names.include?(column_name.to_s) + Arel::Attribute.new(arel_table(options[:from] || table_name), + options[:select] || column_name) + else + Arel::SqlLiteral.new(options[:select] || + (column_name == :all ? "*" : column_name.to_s)) + end + + relation = relation.select(operation == 'count' ? column.count(options[:distinct]) : column.send(operation)) + + type_cast_calculated_value(connection.select_value(relation.to_sql), column_for(column_name), operation) + end + + def execute_grouped_calculation(operation, column_name, options, relation) #:nodoc: + group_attr = options[:group].to_s + association = reflect_on_association(group_attr.to_sym) + associated = association && association.macro == :belongs_to # only count belongs_to associations + group_field = associated ? association.primary_key_name : group_attr + group_alias = column_alias_for(group_field) + group_column = column_for group_field + + options[:group] = connection.adapter_name == 'FrontBase' ? group_alias : group_field + + aggregate_alias = column_alias_for(operation, column_name) + + options[:select] = (operation == 'count' && column_name == :all) ? + "COUNT(*) AS count_all" : + Arel::Attribute.new(arel_table, column_name).send(operation).as(aggregate_alias).to_sql + + options[:select] << ", #{group_field} AS #{group_alias}" + + relation = relation.select(options[:select]).group(construct_group(options[:group], options[:having], nil)) + + calculated_data = connection.select_all(relation.to_sql) + + if association + key_ids = calculated_data.collect { |row| row[group_alias] } + key_records = association.klass.base_class.find(key_ids) + key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } + end + + calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| + key = type_cast_calculated_value(row[group_alias], group_column) + key = key_records[key] if associated + value = row[aggregate_alias] + all[key] = type_cast_calculated_value(value, column_for(column_name), operation) + all + end + end + + protected def construct_count_options_from_args(*args) options = {} column_name = :all @@ -166,111 +243,6 @@ module ActiveRecord [column_name || :all, options] end - def construct_calculation_sql(operation, column_name, options) #:nodoc: - operation = operation.to_s.downcase - options = options.symbolize_keys - - scope = scope(:find) - merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) - aggregate_alias = column_alias_for(operation, column_name) - column_name = "#{connection.quote_table_name(table_name)}.#{column_name}" if column_names.include?(column_name.to_s) - - if operation == 'count' - if merged_includes.any? - options[:distinct] = true - column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.' - end - - if options[:distinct] - use_workaround = !connection.supports_count_distinct? - end - end - - if options[:distinct] && column_name.to_s !~ /\s*DISTINCT\s+/i - distinct = 'DISTINCT ' - end - sql = "SELECT #{operation}(#{distinct}#{column_name}) AS #{aggregate_alias}" - - # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. - sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround - - sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] - if options[:from] - sql << " FROM #{options[:from]} " - elsif scope && scope[:from] && !use_workaround - sql << " FROM #{scope[:from]} " - else - sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround - sql << " FROM #{connection.quote_table_name(table_name)} " - end - - joins = "" - add_joins!(joins, options[:joins], scope) - - if merged_includes.any? - join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins) - sql << join_dependency.join_associations.collect{|join| join.association_join }.join - end - - sql << joins unless joins.blank? - - add_conditions!(sql, options[:conditions], scope) - add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) - - if options[:group] - group_key = connection.adapter_name == 'FrontBase' ? :group_alias : :group_field - sql << " GROUP BY #{options[group_key]} " - end - - if options[:group] && options[:having] - having = sanitize_sql_for_conditions(options[:having]) - - # FrontBase requires identifiers in the HAVING clause and chokes on function calls - if connection.adapter_name == 'FrontBase' - having.downcase! - having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias) - end - - sql << " HAVING #{having} " - end - - sql << " ORDER BY #{options[:order]} " if options[:order] - add_limit!(sql, options, scope) - sql << ") #{aggregate_alias}_subquery" if use_workaround - sql - end - - def execute_simple_calculation(operation, column_name, column, options) #:nodoc: - value = connection.select_value(construct_calculation_sql(operation, column_name, options)) - type_cast_calculated_value(value, column, operation) - end - - def execute_grouped_calculation(operation, column_name, column, options) #:nodoc: - group_attr = options[:group].to_s - association = reflect_on_association(group_attr.to_sym) - associated = association && association.macro == :belongs_to # only count belongs_to associations - group_field = associated ? association.primary_key_name : group_attr - group_alias = column_alias_for(group_field) - group_column = column_for group_field - sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias)) - calculated_data = connection.select_all(sql) - aggregate_alias = column_alias_for(operation, column_name) - - if association - key_ids = calculated_data.collect { |row| row[group_alias] } - key_records = association.klass.base_class.find(key_ids) - key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } - end - - calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row| - key = type_cast_calculated_value(row[group_alias], group_column) - key = key_records[key] if associated - value = row[aggregate_alias] - all[key] = type_cast_calculated_value(value, column, operation) - all - end - end - private def validate_calculation_options(operation, options = {}) options.assert_valid_keys(CALCULATIONS_OPTIONS) @@ -301,11 +273,10 @@ module ActiveRecord end def type_cast_calculated_value(value, column, operation = nil) - operation = operation.to_s.downcase case operation when 'count' then value.to_i when 'sum' then type_cast_using_column(value || '0', column) - when 'avg' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d + when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d else type_cast_using_column(value, column) end end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 40a25811c4..b25893a1c3 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -20,7 +20,7 @@ module ActiveRecord # * (6) <tt>after_save</tt> # # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the - # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each + # Active Record lifecycle. The sequence for calling <tt>Base#save</tt> for an existing record is similar, except that each # <tt>_on_create</tt> callback is replaced by the corresponding <tt>_on_update</tt> callback. # # Examples: @@ -210,7 +210,7 @@ module ActiveRecord # instead of quietly returning +false+. module Callbacks extend ActiveSupport::Concern - include ActiveSupport::NewCallbacks + include ActiveSupport::Callbacks CALLBACKS = [ :after_initialize, :after_find, :before_validation, :after_validation, diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 12253eac3f..377f2a44c5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -211,9 +211,10 @@ module ActiveRecord # calling +checkout+ on this pool. def checkin(conn) @connection_mutex.synchronize do - conn.run_callbacks :checkin - @checked_out.delete conn - @queue.signal + conn.run_callbacks :checkin do + @checked_out.delete conn + @queue.signal + end end end @@ -255,9 +256,10 @@ module ActiveRecord end def checkout_and_verify(c) - c.verify! - c.run_callbacks :checkout - @checked_out << c + c.run_callbacks :checkout do + c.verify! + @checked_out << c + end c end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 08601da00a..be89873632 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -53,7 +53,7 @@ module ActiveRecord def delete(sql, name = nil) delete_sql(sql, name) end - + # Checks whether there is currently no transaction active. This is done # by querying the database driver, and does not use the transaction # house-keeping information recorded by #increment_open_transactions and @@ -170,7 +170,7 @@ module ActiveRecord end end end - + # Begins the transaction (and turns off auto-committing). def begin_db_transaction() end @@ -181,33 +181,6 @@ module ActiveRecord # done if the transaction block raises an exception or returns false. def rollback_db_transaction() end - # Alias for <tt>add_limit_offset!</tt>. - def add_limit!(sql, options) - add_limit_offset!(sql, options) if options - end - - # Appends +LIMIT+ and +OFFSET+ options to an SQL statement, or some SQL - # fragment that has the same semantics as LIMIT and OFFSET. - # - # +options+ must be a Hash which contains a +:limit+ option (required) - # and an +:offset+ option (optional). - # - # This method *modifies* the +sql+ parameter. - # - # ===== Examples - # add_limit_offset!('SELECT * FROM suppliers', {:limit => 10, :offset => 50}) - # generates - # SELECT * FROM suppliers LIMIT 10 OFFSET 50 - def add_limit_offset!(sql, options) - if limit = options[:limit] - sql << " LIMIT #{sanitize_limit(limit)}" - if offset = options[:offset] - sql << " OFFSET #{offset.to_i}" - end - end - sql - end - # Appends a locking clause to an SQL statement. # This method *modifies* the +sql+ parameter. # # SELECT * FROM suppliers FOR UPDATE @@ -235,8 +208,8 @@ module ActiveRecord execute "INSERT INTO #{quote_table_name(table_name)} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert' end - def empty_insert_statement(table_name) - "INSERT INTO #{quote_table_name(table_name)} VALUES(DEFAULT)" + def empty_insert_statement_value + "VALUES(DEFAULT)" end def case_sensitive_equality_operator diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 20787ec510..e731bc84f0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -411,12 +411,6 @@ module ActiveRecord "DISTINCT #{columns}" end - # ORDER BY clause for the passed order option. - # PostgreSQL overrides this due to its stricter standards compliance. - def add_order_by_for_association_limiting!(sql, options) - sql << " ORDER BY #{options[:order]}" - end - # Adds timestamps (created_at and updated_at) columns to the named table. # ===== Examples # add_timestamps(:suppliers) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 78c7a4b697..8fae26b790 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,4 +1,3 @@ -require 'benchmark' require 'date' require 'bigdecimal' require 'bigdecimal/util' @@ -12,8 +11,6 @@ require 'active_record/connection_adapters/abstract/connection_pool' require 'active_record/connection_adapters/abstract/connection_specification' require 'active_record/connection_adapters/abstract/query_cache' -require 'active_support/core_ext/benchmark' - module ActiveRecord module ConnectionAdapters # :nodoc: # ActiveRecord supports multiple database systems. AbstractAdapter and @@ -33,6 +30,7 @@ module ActiveRecord include Quoting, DatabaseStatements, SchemaStatements include QueryCache include ActiveSupport::Callbacks + define_callbacks :checkout, :checkin @@row_even = true @@ -75,7 +73,7 @@ module ActiveRecord def supports_ddl_transactions? false end - + # Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite # does not. def supports_savepoints? @@ -193,6 +191,7 @@ module ActiveRecord end def log_info(sql, name, ms) + @runtime += ms if @logger && @logger.debug? name = '%s (%.1fms)' % [name || 'SQL', ms] @logger.debug(format_log_entry(name, sql.squeeze(' '))) @@ -200,13 +199,8 @@ module ActiveRecord end protected - def log(sql, name) - event = ActiveSupport::Orchestra.instrument(:sql, :sql => sql, :name => name) do - yield if block_given? - end - @runtime += event.duration - log_info(sql, name, event.duration) - event.result + def log(sql, name, &block) + ActiveSupport::Notifications.instrument(:sql, :sql => sql, :name => name, &block) rescue Exception => e # Log message and raise exception. # Set last_verification to 0, so that connection gets verified diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 1072eb7ac1..ad36ff22e3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -211,7 +211,7 @@ module ActiveRecord def supports_migrations? #:nodoc: true end - + def supports_primary_key? #:nodoc: true end @@ -334,6 +334,7 @@ module ActiveRecord super sql, name id_value || @connection.insert_id end + alias :create :insert_sql def update_sql(sql, name = nil) #:nodoc: super @@ -370,18 +371,6 @@ module ActiveRecord execute("RELEASE SAVEPOINT #{current_savepoint_name}") end - def add_limit_offset!(sql, options) #:nodoc: - if limit = options[:limit] - limit = sanitize_limit(limit) - unless offset = options[:offset] - sql << " LIMIT #{limit}" - else - sql << " LIMIT #{offset.to_i}, #{limit}" - end - end - end - - # SCHEMA STATEMENTS ======================================== def structure_dump #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 84cf1ad9fd..1d52c5ec14 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -510,6 +510,7 @@ module ActiveRecord end end end + alias :create :insert # create a 2D array representing the result set def result_as_array(res) #:nodoc: @@ -928,20 +929,6 @@ module ActiveRecord sql << order_columns * ', ' end - # Returns an ORDER BY clause for the passed order option. - # - # PostgreSQL does not allow arbitrary ordering when using DISTINCT ON, so we work around this - # by wrapping the +sql+ string as a sub-select and ordering in that query. - def add_order_by_for_association_limiting!(sql, options) #:nodoc: - return sql if options[:order].blank? - - order = options[:order].split(',').collect { |s| s.strip }.reject(&:blank?) - order.map! { |s| 'DESC' if s =~ /\bdesc$/i } - order = order.zip((0...order.size).to_a).map { |s,i| "id_list.alias_#{i} #{s}" }.join(', ') - - sql.replace "SELECT * FROM (#{sql}) AS id_list ORDER BY #{order}" - end - protected # Returns the version of the connected PostgreSQL version. def postgresql_version diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 5ed7094169..c9c2892ba4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -11,11 +11,11 @@ module ActiveRecord raise ArgumentError, "No database file specified. Missing argument: database" end - # Allow database path relative to RAILS_ROOT, but only if + # Allow database path relative to Rails.root, but only if # the database path is not the special path that tells # Sqlite to build a database only in memory. - if Object.const_defined?(:RAILS_ROOT) && ':memory:' != config[:database] - config[:database] = File.expand_path(config[:database], RAILS_ROOT) + if Object.const_defined?(:Rails) && ':memory:' != config[:database] + config[:database] = File.expand_path(config[:database], Rails.root) end end end @@ -91,7 +91,7 @@ module ActiveRecord def supports_add_column? sqlite_version >= '3.1.6' end - + def disconnect! super @connection.close rescue nil @@ -163,6 +163,7 @@ module ActiveRecord def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: super || @connection.last_insert_row_id end + alias :create :insert_sql def select_rows(sql, name = nil) execute(sql, name).map do |row| @@ -289,8 +290,8 @@ module ActiveRecord alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s}) end - def empty_insert_statement(table_name) - "INSERT INTO #{table_name} VALUES(NULL)" + def empty_insert_statement_value + "VALUES(NULL)" end protected @@ -337,7 +338,7 @@ module ActiveRecord (options[:rename][column.name] || options[:rename][column.name.to_sym] || column.name) : column.name - + @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, :null => column.null) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index cec5ca3324..c8cd79a2b0 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -89,12 +89,14 @@ module ActiveRecord attribute_names.uniq! begin - affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking") - UPDATE #{self.class.quoted_table_name} - SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))} - WHERE #{self.class.primary_key} = #{quote_value(id)} - AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)} - end_sql + arel_table = self.class.arel_table(self.class.table_name) + + affected_rows = arel_table.where( + arel_table[self.class.primary_key].eq(quoted_id).and( + arel_table[self.class.locking_column].eq(quote_value(previous_value)) + ) + ).update(arel_attributes_values(false, false, attribute_names)) + unless affected_rows == 1 raise ActiveRecord::StaleObjectError, "Attempted to update a stale object" @@ -116,12 +118,13 @@ module ActiveRecord lock_col = self.class.locking_column previous_value = send(lock_col).to_i - affected_rows = connection.delete( - "DELETE FROM #{self.class.quoted_table_name} " + - "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " + - "AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}", - "#{self.class.name} Destroy" - ) + arel_table = self.class.arel_table(self.class.table_name) + + affected_rows = arel_table.where( + arel_table[self.class.primary_key].eq(quoted_id).and( + arel_table[self.class.locking_column].eq(quote_value(previous_value)) + ) + ).delete unless affected_rows == 1 raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object" diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c2bc26f33e..c218c5bfd1 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -106,7 +106,7 @@ module ActiveRecord # # The Rails package has several tools to help create and apply migrations. # - # To generate a new migration, you can use + # To generate a new migration, you can use # script/generate migration MyNewMigration # # where MyNewMigration is the name of your migration. The generator will @@ -124,16 +124,16 @@ module ActiveRecord # def self.up # add_column :tablenames, :fieldname, :string # end - # + # # def self.down # remove_column :tablenames, :fieldname # end # end - # + # # To run migrations against the currently configured database, use # <tt>rake db:migrate</tt>. This will update the database by running all of the # pending migrations, creating the <tt>schema_migrations</tt> table - # (see "About the schema_migrations table" section below) if missing. It will also + # (see "About the schema_migrations table" section below) if missing. It will also # invoke the db:schema:dump task, which will update your db/schema.rb file # to match the structure of your database. # @@ -243,7 +243,7 @@ module ActiveRecord # lower than the current schema version: when migrating up, those # never-applied "interleaved" migrations will be automatically applied, and # when migrating down, never-applied "interleaved" migrations will be skipped. - # + # # == Timestamped Migrations # # By default, Rails generates migrations that look like: @@ -256,7 +256,7 @@ module ActiveRecord # off by setting: # # config.active_record.timestamped_migrations = false - # + # # In environment.rb. # class Migration @@ -340,10 +340,6 @@ module ActiveRecord self.verbose = save end - def connection - ActiveRecord::Base.connection - end - def method_missing(method, *arguments, &block) arg_list = arguments.map(&:inspect) * ', ' @@ -351,7 +347,7 @@ module ActiveRecord unless arguments.empty? || method == :execute arguments[0] = Migrator.proper_table_name(arguments.first) end - connection.send(method, *arguments, &block) + Base.connection.send(method, *arguments, &block) end end end @@ -403,7 +399,7 @@ module ActiveRecord def down(migrations_path, target_version = nil) self.new(:down, migrations_path, target_version).migrate end - + def run(direction, migrations_path, target_version) self.new(direction, migrations_path, target_version).run end @@ -413,7 +409,8 @@ module ActiveRecord end def get_all_versions - Base.connection.select_values("SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).sort + table = Arel::Table.new(schema_migrations_table_name) + Base.connection.select_values(table.project(table['version']).to_sql).map(&:to_i).sort end def current_version @@ -447,17 +444,17 @@ module ActiveRecord def initialize(direction, migrations_path, target_version = nil) raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? Base.connection.initialize_schema_migrations_table - @direction, @migrations_path, @target_version = direction, migrations_path, target_version + @direction, @migrations_path, @target_version = direction, migrations_path, target_version end def current_version migrated.last || 0 end - + def current_migration migrations.detect { |m| m.version == current_version } end - + def run target = migrations.detect { |m| m.version == @target_version } raise UnknownMigrationVersionError.new(@target_version) if target.nil? @@ -474,14 +471,14 @@ module ActiveRecord if target.nil? && !@target_version.nil? && @target_version > 0 raise UnknownMigrationVersionError.new(@target_version) end - + start = up? ? 0 : (migrations.index(current) || 0) finish = migrations.index(target) || migrations.size - 1 runnable = migrations[start..finish] - + # skip the last migration if we're headed down, but not ALL the way down runnable.pop if down? && !target.nil? - + runnable.each do |migration| Base.logger.info "Migrating to #{migration.name} (#{migration.version})" @@ -509,28 +506,28 @@ module ActiveRecord def migrations @migrations ||= begin files = Dir["#{@migrations_path}/[0-9]*_*.rb"] - + migrations = files.inject([]) do |klasses, file| version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first - + raise IllegalMigrationNameError.new(file) unless version version = version.to_i - + if klasses.detect { |m| m.version == version } - raise DuplicateMigrationVersionError.new(version) + raise DuplicateMigrationVersionError.new(version) end if klasses.detect { |m| m.name == name.camelize } - raise DuplicateMigrationNameError.new(name.camelize) + raise DuplicateMigrationNameError.new(name.camelize) end - + migration = MigrationProxy.new migration.name = name.camelize migration.version = version migration.filename = file klasses << migration end - + migrations = migrations.sort_by(&:version) down? ? migrations.reverse : migrations end @@ -547,15 +544,15 @@ module ActiveRecord private def record_version_state_after_migrating(version) - sm_table = self.class.schema_migrations_table_name + table = Arel::Table.new(self.class.schema_migrations_table_name) @migrated_versions ||= [] if down? - @migrated_versions.delete(version.to_i) - Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") + @migrated_versions.delete(version) + table.where(table["version"].eq(version.to_s)).delete else - @migrated_versions.push(version.to_i).sort! - Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") + @migrated_versions.push(version).sort! + table.insert table["version"] => version.to_s end end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index f65ee9465e..f61a4a6c73 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -250,6 +250,8 @@ module ActiveRecord assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes) end }, __FILE__, __LINE__ + + add_autosave_association_callbacks(reflection) else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end diff --git a/activerecord/lib/active_record/notifications.rb b/activerecord/lib/active_record/notifications.rb new file mode 100644 index 0000000000..562a5b91f4 --- /dev/null +++ b/activerecord/lib/active_record/notifications.rb @@ -0,0 +1,5 @@ +require 'active_support/notifications' + +ActiveSupport::Notifications.subscribe("sql") do |name, before, after, result, instrumenter_id, payload| + ActiveRecord::Base.connection.log_info(payload[:sql], name, after - before) +end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb new file mode 100644 index 0000000000..5f0eec754f --- /dev/null +++ b/activerecord/lib/active_record/relation.rb @@ -0,0 +1,127 @@ +module ActiveRecord + class Relation + delegate :to_sql, :to => :relation + delegate :length, :collect, :find, :map, :each, :to => :to_a + attr_reader :relation, :klass + + def initialize(klass, relation) + @klass, @relation = klass, relation + @readonly = false + @associations_to_preload = [] + @eager_load_associations = [] + end + + def preload(association) + @associations_to_preload += association + self + end + + def eager_load(association) + @eager_load_associations += association + self + end + + def readonly + @readonly = true + self + end + + def to_a + records = if @eager_load_associations.any? + catch :invalid_query do + return @klass.send(:find_with_associations, { + :select => @relation.send(:select_clauses).join(', '), + :joins => @relation.joins(relation), + :group => @relation.send(:group_clauses).join(', '), + :order => @relation.send(:order_clauses).join(', '), + :conditions => @relation.send(:where_clauses).join("\n\tAND "), + :limit => @relation.taken, + :offset => @relation.skipped + }, + ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, @eager_load_associations, nil)) + end + [] + else + @klass.find_by_sql(@relation.to_sql) + end + + @klass.send(:preload_associations, records, @associations_to_preload) unless @associations_to_preload.empty? + records.each { |record| record.readonly! } if @readonly + + records + end + + def first + @relation = @relation.take(1) + to_a.first + end + + def select(selects) + selects.blank? ? self : Relation.new(@klass, @relation.project(selects)) + end + + def group(groups) + groups.blank? ? self : Relation.new(@klass, @relation.group(groups)) + end + + def order(orders) + orders.blank? ? self : Relation.new(@klass, @relation.order(orders)) + end + + def limit(limits) + limits.blank? ? self : Relation.new(@klass, @relation.take(limits)) + end + + def offset(offsets) + offsets.blank? ? self : Relation.new(@klass, @relation.skip(offsets)) + end + + def on(join) + join.blank? ? self : Relation.new(@klass, @relation.on(join)) + end + + def joins(join, join_type = nil) + if join.blank? + self + else + join = case join + when String + @relation.join(join) + when Hash, Array, Symbol + if @klass.send(:array_of_strings?, join) + @relation.join(join.join(' ')) + else + @relation.join(@klass.send(:build_association_joins, join)) + end + else + @relation.join(join, join_type) + end + Relation.new(@klass, join) + end + end + + def conditions(conditions) + if conditions.blank? + self + else + conditions = @klass.send(:merge_conditions, conditions) if [String, Hash, Array].include?(conditions.class) + Relation.new(@klass, @relation.where(conditions)) + end + end + + def respond_to?(method) + @relation.respond_to?(method) || Array.method_defined?(method) || super + end + + private + def method_missing(method, *args, &block) + if @relation.respond_to?(method) + @relation.send(method, *args, &block) + elsif Array.method_defined?(method) + to_a.send(method, *args, &block) + else + super + end + end + end +end diff --git a/activerecord/lib/active_record/types.rb b/activerecord/lib/active_record/types.rb new file mode 100644 index 0000000000..74f569352b --- /dev/null +++ b/activerecord/lib/active_record/types.rb @@ -0,0 +1,38 @@ +module ActiveRecord + module Types + extend ActiveSupport::Concern + + module ClassMethods + + def attribute_types + attribute_types = {} + columns.each do |column| + options = {} + options[:time_zone_aware] = time_zone_aware?(column.name) + options[:serialize] = serialized_attributes[column.name] + + attribute_types[column.name] = to_type(column, options) + end + attribute_types + end + + private + + def to_type(column, options = {}) + type_class = if options[:time_zone_aware] + Type::TimeWithZone + elsif options[:serialize] + Type::Serialize + elsif [ :integer, :float, :decimal ].include?(column.type) + Type::Number + else + Type::Object + end + + type_class.new(column, options) + end + + end + + end +end diff --git a/activerecord/lib/active_record/types/number.rb b/activerecord/lib/active_record/types/number.rb new file mode 100644 index 0000000000..cfbe877575 --- /dev/null +++ b/activerecord/lib/active_record/types/number.rb @@ -0,0 +1,30 @@ +module ActiveRecord + module Type + class Number < Object + + def boolean(value) + value = cast(value) + !(value.nil? || value.zero?) + end + + def precast(value) + convert_number_column_value(value) + end + + private + + def convert_number_column_value(value) + if value == false + 0 + elsif value == true + 1 + elsif value.is_a?(String) && value.blank? + nil + else + value + end + end + + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/types/object.rb b/activerecord/lib/active_record/types/object.rb new file mode 100644 index 0000000000..ec3f861abd --- /dev/null +++ b/activerecord/lib/active_record/types/object.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module Type + module Casting + + def cast(value) + typecaster.type_cast(value) + end + + def precast(value) + value + end + + def boolean(value) + cast(value).present? + end + + # Attributes::Typecasting stores appendable? types (e.g. serialized Arrays) when typecasting reads. + def appendable? + false + end + + end + + class Object + include Casting + + attr_reader :name, :options + attr_reader :typecaster + + def initialize(typecaster = nil, options = {}) + @typecaster, @options = typecaster, options + end + + end + + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/types/serialize.rb b/activerecord/lib/active_record/types/serialize.rb new file mode 100644 index 0000000000..7b6af1981f --- /dev/null +++ b/activerecord/lib/active_record/types/serialize.rb @@ -0,0 +1,33 @@ +module ActiveRecord + module Type + class Serialize < Object + + def cast(value) + unserialize(value) + end + + def appendable? + true + end + + protected + + def unserialize(value) + unserialized_object = object_from_yaml(value) + + if unserialized_object.is_a?(@options[:serialize]) || unserialized_object.nil? + unserialized_object + else + raise SerializationTypeMismatch, + "#{name} was supposed to be a #{@options[:serialize]}, but was a #{unserialized_object.class.to_s}" + end + end + + def object_from_yaml(string) + return string unless string.is_a?(String) && string =~ /^---/ + YAML::load(string) rescue string + end + + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/types/time_with_zone.rb b/activerecord/lib/active_record/types/time_with_zone.rb new file mode 100644 index 0000000000..3a8b9292f9 --- /dev/null +++ b/activerecord/lib/active_record/types/time_with_zone.rb @@ -0,0 +1,20 @@ +module ActiveRecord + module Type + class TimeWithZone < Object + + def cast(time) + time = super(time) + time.acts_like?(:time) ? time.in_time_zone : time + end + + def precast(time) + unless time.acts_like?(:time) + time = time.is_a?(String) ? ::Time.zone.parse(time) : time.to_time rescue time + end + time = time.in_time_zone rescue nil if time + super(time) + end + + end + end +end diff --git a/activerecord/lib/active_record/types/unknown.rb b/activerecord/lib/active_record/types/unknown.rb new file mode 100644 index 0000000000..f832c7b304 --- /dev/null +++ b/activerecord/lib/active_record/types/unknown.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module Type + # Useful for handling attributes not mapped to types. Performs some boolean typecasting, + # but otherwise leaves the value untouched. + class Unknown + + def cast(value) + value + end + + def precast(value) + value + end + + # Attempts typecasting to handle numeric, false and blank values. + def boolean(value) + empty = (numeric?(value) && value.to_i.zero?) || false?(value) || value.blank? + !empty + end + + def appendable? + false + end + + protected + + def false?(value) + ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) + end + + def numeric?(value) + Numeric === value || value !~ /[^0-9]/ + end + + end + end +end
\ No newline at end of file diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index ab79b520a2..e8a2a72735 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/integer/even_odd' - module ActiveRecord # Raised by <tt>save!</tt> and <tt>create!</tt> when the record is invalid. Use the # +record+ method to retrieve the record which did not validate. @@ -17,94 +15,10 @@ module ActiveRecord end end - class Errors < ActiveModel::Errors - class << self - def default_error_messages - message = "Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages')." - ActiveSupport::Deprecation.warn(message) - - I18n.translate 'activerecord.errors.messages' - end - end - - # Returns all the full error messages in an array. - # - # class Company < ActiveRecord::Base - # validates_presence_of :name, :address, :email - # validates_length_of :name, :in => 5..30 - # end - # - # company = Company.create(:address => '123 First St.') - # company.errors.full_messages # => - # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"] - def full_messages(options = {}) - full_messages = [] - - each do |attribute, messages| - messages = Array.wrap(messages) - next if messages.empty? - - if attribute == :base - messages.each {|m| full_messages << m } - else - attr_name = @base.class.human_attribute_name(attribute.to_s) - prefix = attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') - messages.each do |m| - full_messages << "#{prefix}#{m}" - end - end - end - - full_messages - end - - # Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>). - # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there, - # it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the - # default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name, - # translated attribute name and the value are available for interpolation. - # - # When using inheritance in your models, it will check all the inherited models too, but only if the model itself - # hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt> - # error +message+ for the <tt>title</tt> +attribute+, it looks for these translations: - # - # <ol> - # <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li> - # <li><tt>activerecord.errors.models.admin.blank</tt></li> - # <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li> - # <li><tt>activerecord.errors.models.user.blank</tt></li> - # <li><tt>activerecord.errors.messages.blank</tt></li> - # <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li> - # </ol> - def generate_message(attribute, message = :invalid, options = {}) - message, options[:default] = options[:default], message if options[:default].is_a?(Symbol) - - defaults = @base.class.self_and_descendants_from_active_record.map do |klass| - [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", - :"models.#{klass.name.underscore}.#{message}" ] - end - - defaults << options.delete(:default) - defaults = defaults.compact.flatten << :"messages.#{message}" - - key = defaults.shift - value = @base.respond_to?(attribute) ? @base.send(attribute) : nil - - options = { :default => defaults, - :model => @base.class.human_name, - :attribute => @base.class.human_attribute_name(attribute.to_s), - :value => value, - :scope => [:activerecord, :errors] - }.merge(options) - - I18n.translate(key, options) - end - end - module Validations extend ActiveSupport::Concern - include ActiveSupport::Callbacks + include ActiveSupport::DeprecatedCallbacks include ActiveModel::Validations included do @@ -165,11 +79,6 @@ module ActiveRecord errors.empty? end - - # Returns the Errors object that holds all information about attribute error messages. - def errors - @errors ||= Errors.new(self) - end end end end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 9463b7b14a..c59be264a4 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -122,18 +122,6 @@ class AdapterTest < ActiveRecord::TestCase end end - def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas - sql_inject = "1 select * from schema" - assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject) - assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - end - - def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas - sql_inject = "1, 7 procedure help()" - assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject) - assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - end - def test_uniqueness_violations_are_translated_to_specific_exception @connection.execute "INSERT INTO subscribers(nick) VALUES('me')" assert_raises(ActiveRecord::RecordNotUnique) do diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 45e74ea024..ed2e2e9f8f 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -104,7 +104,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase authors.first.posts.first.special_comments.first.post.very_special_comment end end - + def test_eager_association_loading_where_first_level_returns_nil authors = Author.find(:all, :include => {:post_about_thinking => :comments}, :order => 'authors.id DESC') assert_equal [authors(:mary), authors(:david)], authors diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index f313a75233..e8db6d5dab 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -72,10 +72,8 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase ShapeExpression, NonPolyOne, NonPolyTwo].each do |c| c.delete_all end - end - def generate_test_object_graphs 1.upto(NUM_SIMPLE_OBJS) do [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!) diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 811ebfbe3f..d5a4d9007b 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -301,13 +301,13 @@ class EagerAssociationTest < ActiveRecord::TestCase subscriber =Subscriber.find(subscribers(:second).id, :include => :subscriptions) assert_equal subscriptions, subscriber.subscriptions.sort_by(&:id) end - + def test_eager_load_has_many_through_with_string_keys books = books(:awdr, :rfr) subscriber = Subscriber.find(subscribers(:second).id, :include => :books) assert_equal books, subscriber.books.sort_by(&:id) end - + def test_eager_load_belongs_to_with_string_keys subscriber = subscribers(:second) subscription = Subscription.find(subscriptions(:webster_awdr).id, :include => :subscriber) @@ -434,7 +434,7 @@ class EagerAssociationTest < ActiveRecord::TestCase author_posts_without_comments = author.posts.select { |post| post.comments.blank? } assert_equal author_posts_without_comments.size, author.posts.count(:all, :include => :comments, :conditions => 'comments.id is null') end - + def test_eager_count_performed_on_a_has_many_through_association_with_multi_table_conditional person = people(:michael) person_posts_without_comments = person.posts.select { |post| post.comments.blank? } @@ -823,7 +823,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal expected, firm.clients_using_primary_key end end - + def test_preload_has_one_using_primary_key expected = Firm.find(:first).account_using_primary_key firm = Firm.find :first, :include => :account_using_primary_key @@ -839,5 +839,5 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal expected, firm.account_using_primary_key end end - + end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 11a159686e..1bce45865f 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -732,7 +732,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal [projects(:active_record), projects(:action_controller)].map(&:id).sort, developer.project_ids.sort end - def test_select_limited_ids_list + def test_select_limited_ids_array # Set timestamps Developer.transaction do Developer.find(:all, :order => 'id').each_with_index do |record, i| @@ -742,9 +742,9 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase join_base = ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase.new(Project) join_dep = ActiveRecord::Associations::ClassMethods::JoinDependency.new(join_base, :developers, nil) - projects = Project.send(:select_limited_ids_list, {:order => 'developers.created_at'}, join_dep) + projects = Project.send(:select_limited_ids_array, {:order => 'developers.created_at'}, join_dep) assert !projects.include?("'"), projects - assert_equal %w(1 2), projects.scan(/\d/).sort + assert_equal ["1", "2"], projects.sort end def test_scoped_find_on_through_association_doesnt_return_read_only_records @@ -770,7 +770,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal developer, project.developers.find(:first) assert_equal project, developer.projects.find(:first) end - + def test_self_referential_habtm_without_foreign_key_set_should_raise_exception assert_raise(ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded) { Member.class_eval do diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index b193f8d8ba..86d14c9c81 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -659,6 +659,18 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal 1, Client.find_all_by_client_of(firm.id).size end + def test_delete_all_association_with_primary_key_deletes_correct_records + firm = Firm.find(:first) + # break the vanilla firm_id foreign key + assert_equal 2, firm.clients.count + firm.clients.first.update_attribute(:firm_id, nil) + assert_equal 1, firm.clients(true).count + assert_equal 1, firm.clients_using_primary_key_with_delete_all.count + old_record = firm.clients_using_primary_key_with_delete_all.first + firm = Firm.find(:first) + firm.destroy + assert Client.find_by_id(old_record.id).nil? + end def test_creation_respects_hash_condition ms_client = companies(:first_firm).clients_like_ms_with_hash_conditions.build diff --git a/activerecord/test/cases/associations/inner_join_association_test.rb b/activerecord/test/cases/associations/inner_join_association_test.rb index 7141531740..5f08c40005 100644 --- a/activerecord/test/cases/associations/inner_join_association_test.rb +++ b/activerecord/test/cases/associations/inner_join_association_test.rb @@ -26,7 +26,7 @@ class InnerJoinAssociationTest < ActiveRecord::TestCase def test_construct_finder_sql_applies_association_conditions sql = Author.send(:construct_finder_sql, :joins => :categories_like_general, :conditions => "TERMINATING_MARKER") - assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?.*TERMINATING_MARKER/, sql + assert_match /INNER JOIN .?categories.? ON.*AND.*.?General.?(.|\n)*TERMINATING_MARKER/, sql end def test_construct_finder_sql_applies_aliases_tables_on_association_conditions diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 056a29438a..e429c1d157 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -65,25 +65,6 @@ class AssociationsTest < ActiveRecord::TestCase assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" end - def test_storing_in_pstore - require "tmpdir" - store_filename = File.join(Dir.tmpdir, "ar-pstore-association-test") - File.delete(store_filename) if File.exist?(store_filename) - require "pstore" - apple = Firm.create("name" => "Apple") - natural = Client.new("name" => "Natural Company") - apple.clients << natural - - db = PStore.new(store_filename) - db.transaction do - db["apple"] = apple - end - - db = PStore.new(store_filename) - db.transaction do - assert_equal "Natural Company", db["apple"].clients.first.name - end - end end class AssociationProxyTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/attributes/aliasing_test.rb b/activerecord/test/cases/attributes/aliasing_test.rb new file mode 100644 index 0000000000..7ee25779f1 --- /dev/null +++ b/activerecord/test/cases/attributes/aliasing_test.rb @@ -0,0 +1,20 @@ +require "cases/helper" + +class AliasingTest < ActiveRecord::TestCase + + class AliasingAttributes < Hash + include ActiveRecord::Attributes::Aliasing + end + + test "attribute access with aliasing" do + attributes = AliasingAttributes.new + attributes[:name] = 'Batman' + attributes.aliases['nickname'] = 'name' + + assert_equal 'Batman', attributes[:name], "Symbols should point to Strings" + assert_equal 'Batman', attributes['name'] + assert_equal 'Batman', attributes['nickname'] + assert_equal 'Batman', attributes[:nickname] + end + +end diff --git a/activerecord/test/cases/attributes/typecasting_test.rb b/activerecord/test/cases/attributes/typecasting_test.rb new file mode 100644 index 0000000000..8a3b551375 --- /dev/null +++ b/activerecord/test/cases/attributes/typecasting_test.rb @@ -0,0 +1,120 @@ +require "cases/helper" + +class TypecastingTest < ActiveRecord::TestCase + + class TypecastingAttributes < Hash + include ActiveRecord::Attributes::Typecasting + end + + module MockType + class Object + + def cast(value) + value + end + + def precast(value) + value + end + + def boolean(value) + !value.blank? + end + + def appendable? + false + end + + end + + class Integer < Object + + def cast(value) + value.to_i + end + + def precast(value) + value ? value : 0 + end + + def boolean(value) + !Float(value).zero? + end + + end + + class Serialize < Object + + def cast(value) + YAML::load(value) rescue value + end + + def precast(value) + value + end + + def appendable? + true + end + + end + end + + def setup + @attributes = TypecastingAttributes.new + @attributes.types.default = MockType::Object.new + @attributes.types['comments_count'] = MockType::Integer.new + end + + test "typecast on read" do + attributes = @attributes.merge('comments_count' => '5') + assert_equal 5, attributes['comments_count'] + end + + test "typecast on write" do + @attributes['comments_count'] = false + + assert_equal 0, @attributes.to_h['comments_count'] + end + + test "serialized objects" do + attributes = @attributes.merge('tags' => [ 'peanut butter' ].to_yaml) + attributes.types['tags'] = MockType::Serialize.new + attributes['tags'] << 'jelly' + + assert_equal [ 'peanut butter', 'jelly' ], attributes['tags'] + end + + test "without typecasting" do + @attributes.merge!('comments_count' => '5') + attributes = @attributes.without_typecast + + assert_equal '5', attributes['comments_count'] + assert_equal 5, @attributes['comments_count'], "Original attributes should typecast" + end + + + test "typecast all attributes" do + attributes = @attributes.merge('title' => 'I love sandwiches', 'comments_count' => '5') + attributes.typecast! + + assert_equal({ 'title' => 'I love sandwiches', 'comments_count' => 5 }, attributes) + end + + test "query for has? value" do + attributes = @attributes.merge('comments_count' => '1') + + assert_equal true, attributes.has?('comments_count') + attributes['comments_count'] = '0' + assert_equal false, attributes.has?('comments_count') + end + + test "attributes to Hash" do + attributes_hash = { 'title' => 'I love sandwiches', 'comments_count' => '5' } + attributes = @attributes.merge(attributes_hash) + + assert_equal Hash, attributes.to_h.class + assert_equal attributes_hash, attributes.to_h + end + +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 1e2d903492..5c2911eca1 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1893,7 +1893,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_all_with_conditions - assert_equal Developer.find(:all, :order => 'id desc'), Developer.all(:order => 'id desc') + assert_equal Developer.find(:all, :order => 'id desc'), Developer.all.order('id desc').to_a end def test_find_ordered_last @@ -2195,9 +2195,9 @@ class BasicsTest < ActiveRecord::TestCase log = StringIO.new ActiveRecord::Base.logger = Logger.new(log) ActiveRecord::Base.logger.level = Logger::WARN - ActiveRecord::Base.benchmark("Debug Topic Count", Logger::DEBUG) { Topic.count } - ActiveRecord::Base.benchmark("Warn Topic Count", Logger::WARN) { Topic.count } - ActiveRecord::Base.benchmark("Error Topic Count", Logger::ERROR) { Topic.count } + ActiveRecord::Base.benchmark("Debug Topic Count", :level => :debug) { Topic.count } + ActiveRecord::Base.benchmark("Warn Topic Count", :level => :warn) { Topic.count } + ActiveRecord::Base.benchmark("Error Topic Count", :level => :error) { Topic.count } assert_no_match /Debug Topic Count/, log.string assert_match /Warn Topic Count/, log.string assert_match /Error Topic Count/, log.string @@ -2209,8 +2209,8 @@ class BasicsTest < ActiveRecord::TestCase original_logger = ActiveRecord::Base.logger log = StringIO.new ActiveRecord::Base.logger = Logger.new(log) - ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, true) { ActiveRecord::Base.logger.debug "Loud" } - ActiveRecord::Base.benchmark("Logging", Logger::DEBUG, false) { ActiveRecord::Base.logger.debug "Quiet" } + ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => true) { ActiveRecord::Base.logger.debug "Loud" } + ActiveRecord::Base.benchmark("Logging", :level => :debug, :silence => false) { ActiveRecord::Base.logger.debug "Quiet" } assert_no_match /Loud/, log.string assert_match /Quiet/, log.string ensure diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index c2e02763f6..004f4d0ea6 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -27,7 +27,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_return_nil_as_average assert_nil NumericData.average(:bank_balance) end - + def test_type_cast_calculated_value_should_convert_db_averages_of_fixnum_class_to_decimal assert_equal 0, NumericData.send(:type_cast_calculated_value, 0, nil, 'avg') assert_equal 53.0, NumericData.send(:type_cast_calculated_value, 53, nil, 'avg') diff --git a/activerecord/test/cases/copy_table_test_sqlite.rb b/activerecord/test/cases/copy_table_test_sqlite.rb index de8af30997..575b4806c1 100644 --- a/activerecord/test/cases/copy_table_test_sqlite.rb +++ b/activerecord/test/cases/copy_table_test_sqlite.rb @@ -1,7 +1,7 @@ require "cases/helper" class CopyTableTest < ActiveRecord::TestCase - fixtures :companies, :comments + fixtures :customers, :companies, :comments def setup @connection = ActiveRecord::Base.connection @@ -27,8 +27,8 @@ class CopyTableTest < ActiveRecord::TestCase test_copy_table('customers', 'customers2', :rename => {'name' => 'person_name'}) do |from, to, options| expected = column_values(from, 'name') - assert expected.any?, 'only nils in resultset; real values are needed' assert_equal expected, column_values(to, 'person_name') + assert expected.any?, "No values in table: #{expected.inspect}" end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7b6bf597a8..3de07797d4 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1073,10 +1073,10 @@ class FinderTest < ActiveRecord::TestCase end def test_finder_with_scoped_from - all_topics = Topic.all + all_topics = Topic.find(:all) Topic.with_scope(:find => { :from => 'fake_topics' }) do - assert_equal all_topics, Topic.all(:from => 'topics') + assert_equal all_topics, Topic.all(:from => 'topics').to_a end end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index aa09c7061f..25613da912 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -1,5 +1,12 @@ -$:.unshift(File.dirname(__FILE__) + '/../../lib') -$:.unshift(File.dirname(__FILE__) + '/../../../activesupport/lib') +root = File.expand_path('../../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") +end + +lib = File.expand_path("#{File.dirname(__FILE__)}/../../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) require 'config' diff --git a/activerecord/test/cases/i18n_test.rb b/activerecord/test/cases/i18n_test.rb index d59c53cec8..ae4dcfb81e 100644 --- a/activerecord/test/cases/i18n_test.rb +++ b/activerecord/test/cases/i18n_test.rb @@ -30,17 +30,17 @@ class ActiveRecordI18nTests < Test::Unit::TestCase def test_translated_model_names I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } - assert_equal 'topic model', Topic.human_name + assert_equal 'topic model', Topic.model_name.human end def test_translated_model_names_with_sti I18n.backend.store_translations 'en', :activerecord => {:models => {:reply => 'reply model'} } - assert_equal 'reply model', Reply.human_name + assert_equal 'reply model', Reply.model_name.human end def test_translated_model_names_with_sti_fallback I18n.backend.store_translations 'en', :activerecord => {:models => {:topic => 'topic model'} } - assert_equal 'topic model', Reply.human_name + assert_equal 'topic model', Reply.model_name.human end end diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 5cd11e9799..73e51fbd91 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -30,7 +30,7 @@ class InheritanceTest < ActiveRecord::TestCase 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 @@ -39,7 +39,7 @@ class InheritanceTest < ActiveRecord::TestCase 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 diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index e177235591..f946e8699e 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -282,11 +282,14 @@ unless current_adapter?(:SybaseAdapter, :OpenBaseAdapter) assert first.end > second.end end - def test_second_lock_waits - assert [0.2, 1, 5].any? { |zzz| - first, second = duel(zzz) { Person.find 1, :lock => true } - second.end > first.end - } + # Hit by ruby deadlock detection since connection checkout is mutexed. + if RUBY_VERSION < '1.9.0' + def test_second_lock_waits + assert [0.2, 1, 5].any? { |zzz| + first, second = duel(zzz) { Person.find 1, :lock => true } + second.end > first.end + } + end end protected diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 35f7bc5443..eb4ce0e774 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -380,7 +380,7 @@ class NestedScopingTest < ActiveRecord::TestCase Developer.with_scope(:find => { :conditions => "salary < 100000" }) do Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do # Oracle adapter does not generated space after asc therefore trailing space removed from regex - assert_sql /ORDER BY id asc/ do + assert_sql /ORDER BY id asc/ do assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) end end @@ -593,12 +593,12 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_default_scope_with_conditions_string - assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort assert_equal nil, DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash - assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort assert_equal 'Jamis', DeveloperCalledJamis.create!.name end diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index bb2aba9d92..13427daf53 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -345,14 +345,14 @@ class NamedScopeTest < ActiveRecord::TestCase def test_chaining_should_use_latest_conditions_when_searching # Normal hash conditions - assert_equal Topic.all(:conditions => {:approved => true}), Topic.rejected.approved.all - assert_equal Topic.all(:conditions => {:approved => false}), Topic.approved.rejected.all + assert_equal Topic.all(:conditions => {:approved => true}).to_a, Topic.rejected.approved.all.to_a + assert_equal Topic.all(:conditions => {:approved => false}).to_a, Topic.approved.rejected.all.to_a # Nested hash conditions with same keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_very_special_comments.all.to_a # Nested hash conditions with different keys - assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.uniq + assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).all.to_a.uniq end def test_named_scopes_batch_finders diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index bb9013c2a1..2529a33dab 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -4,14 +4,14 @@ require "timeout" class PooledConnectionsTest < ActiveRecord::TestCase def setup - super + @per_test_teardown = [] @connection = ActiveRecord::Base.remove_connection end def teardown ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.establish_connection(@connection) - super + @per_test_teardown.each {|td| td.call } end def checkout_connections @@ -105,7 +105,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase Thread.new do ActiveRecord::Base.connection.rollback_db_transaction ActiveRecord::Base.connection_pool.release_connection - end.join rescue nil + end add_record('three') end @@ -113,6 +113,23 @@ class PooledConnectionsTest < ActiveRecord::TestCase assert_equal 3, after_count - before_count end + def test_connection_pool_callbacks + checked_out, checked_in = false, false + ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do + set_callback(:checkout, :after) { checked_out = true } + set_callback(:checkin, :before) { checked_in = true } + end + @per_test_teardown << proc do + ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do + reset_callbacks :checkout + reset_callbacks :checkin + end + end + checkout_checkin_connections 1, 1 + assert checked_out + assert checked_in + end + private def add_record(name) diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index f3ed8ccd8d..acd214eb5a 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -15,8 +15,8 @@ class ReflectionTest < ActiveRecord::TestCase end def test_human_name - assert_equal "Price estimate", PriceEstimate.human_name - assert_equal "Subscriber", Subscriber.human_name + assert_equal "Price estimate", PriceEstimate.model_name.human + assert_equal "Subscriber", Subscriber.model_name.human end def test_column_null_not_null @@ -176,8 +176,8 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 36, Firm.reflect_on_all_associations.size - assert_equal 26, Firm.reflect_on_all_associations(:has_many).size + assert_equal 37, Firm.reflect_on_all_associations.size + assert_equal 27, Firm.reflect_on_all_associations(:has_many).size assert_equal 10, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb new file mode 100644 index 0000000000..1a2c8030fb --- /dev/null +++ b/activerecord/test/cases/relations_test.rb @@ -0,0 +1,150 @@ +require "cases/helper" +require 'models/post' +require 'models/topic' +require 'models/comment' +require 'models/reply' +require 'models/author' +require 'models/comment' +require 'models/entrant' +require 'models/developer' +require 'models/company' + +class RelationTest < ActiveRecord::TestCase + fixtures :authors, :topics, :entrants, :developers, :companies, :developers_projects, :accounts, :categories, :categorizations, :posts, :comments + + def test_finding_with_conditions + assert_equal Author.find(:all, :conditions => "name = 'David'"), Author.all.conditions("name = 'David'").to_a + end + + def test_finding_with_order + topics = Topic.all.order('id') + assert_equal 4, topics.size + assert_equal topics(:first).title, topics.first.title + end + + def test_finding_with_order_and_take + entrants = Entrant.all.order("id ASC").limit(2).to_a + + assert_equal(2, entrants.size) + assert_equal(entrants(:first).name, entrants.first.name) + end + + def test_finding_with_order_limit_and_offset + entrants = Entrant.all.order("id ASC").limit(2).offset(1) + + assert_equal(2, entrants.size) + assert_equal(entrants(:second).name, entrants.first.name) + + entrants = Entrant.all.order("id ASC").limit(2).offset(2) + assert_equal(1, entrants.size) + assert_equal(entrants(:third).name, entrants.first.name) + end + + def test_finding_with_group + developers = Developer.all.group("salary").select("salary").to_a + assert_equal 4, developers.size + assert_equal 4, developers.map(&:salary).uniq.size + end + + def test_finding_with_hash_conditions_on_joined_table + firms = DependentFirm.all.joins(:account).conditions({:name => 'RailsCore', :accounts => { :credit_limit => 55..60 }}).to_a + assert_equal 1, firms.size + assert_equal companies(:rails_core), firms.first + end + + def test_find_all_with_join + developers_on_project_one = Developer.all.joins('LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id').conditions('project_id=1').to_a + + assert_equal 3, developers_on_project_one.length + developer_names = developers_on_project_one.map { |d| d.name } + assert developer_names.include?('David') + assert developer_names.include?('Jamis') + end + + def test_find_on_hash_conditions + assert_equal Topic.find(:all, :conditions => {:approved => false}), Topic.all.conditions({ :approved => false }).to_a + end + + def test_joins_with_string_array + person_with_reader_and_post = Post.all.joins([ + "INNER JOIN categorizations ON categorizations.post_id = posts.id", + "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" + ] + ).to_a + assert_equal 1, person_with_reader_and_post.size + end + + def test_relation_responds_to_delegated_methods + relation = Topic.all + + ["map", "uniq", "sort", "insert", "delete", "update"].each do |method| + assert relation.respond_to?(method), "Topic.all should respond to #{method.inspect}" + end + end + + def test_find_with_readonly_option + Developer.all.each { |d| assert !d.readonly? } + Developer.all.readonly.each { |d| assert d.readonly? } + Developer.all(:readonly => true).each { |d| assert d.readonly? } + end + + def test_eager_association_loading_of_stis_with_multiple_references + authors = Author.all(:include => { :posts => { :special_comments => { :post => [ :special_comments, :very_special_comment ] } } }, :order => 'comments.body, very_special_comments_posts.body', :conditions => 'posts.id = 4').to_a + assert_equal [authors(:david)], authors + assert_no_queries do + authors.first.posts.first.special_comments.first.post.special_comments + authors.first.posts.first.special_comments.first.post.very_special_comment + end + end + + def test_find_with_included_associations + assert_queries(2) do + posts = Post.find(:all, :include => :comments) + posts.first.comments.first + end + assert_queries(2) do + posts = Post.all(:include => :comments).to_a + posts.first.comments.first + end + assert_queries(2) do + posts = Post.find(:all, :include => :author) + posts.first.author + end + assert_queries(2) do + posts = Post.all(:include => :author).to_a + posts.first.author + end + end + + def test_default_scope_with_conditions_string + assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.all.to_a.map(&:id).sort + assert_equal nil, DeveloperCalledDavid.create!.name + end + + def test_default_scope_with_conditions_hash + assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.all.map(&:id).sort + assert_equal 'Jamis', DeveloperCalledJamis.create!.name + end + + def test_loading_with_one_association + posts = Post.all(:include => :comments) + post = posts.find { |p| p.id == 1 } + assert_equal 2, post.comments.size + assert post.comments.include?(comments(:greetings)) + + post = Post.find(:first, :include => :comments, :conditions => "posts.title = 'Welcome to the weblog'") + assert_equal 2, post.comments.size + assert post.comments.include?(comments(:greetings)) + + posts = Post.all(:include => :last_comment) + post = posts.find { |p| p.id == 1 } + assert_equal Post.find(1).last_comment, post.last_comment + end + + def test_loading_with_one_association_with_non_preload + posts = Post.all(:include => :last_comment, :order => 'comments.id DESC') + post = posts.find { |p| p.id == 1 } + assert_equal Post.find(1).last_comment, post.last_comment + end +end + diff --git a/activerecord/test/cases/types/number_test.rb b/activerecord/test/cases/types/number_test.rb new file mode 100644 index 0000000000..ee7216a0f1 --- /dev/null +++ b/activerecord/test/cases/types/number_test.rb @@ -0,0 +1,30 @@ +require "cases/helper" + +class NumberTest < ActiveRecord::TestCase + + def setup + @column = ActiveRecord::ConnectionAdapters::Column.new('comments_count', 0, 'integer') + @number = ActiveRecord::Type::Number.new(@column) + end + + test "typecast" do + assert_equal 1, @number.cast(1) + assert_equal 1, @number.cast('1') + assert_equal 0, @number.cast('') + + assert_equal 0, @number.precast(false) + assert_equal 1, @number.precast(true) + assert_equal nil, @number.precast('') + assert_equal 0, @number.precast(0) + end + + test "cast as boolean" do + assert_equal true, @number.boolean('1') + assert_equal true, @number.boolean(1) + + assert_equal false, @number.boolean(0) + assert_equal false, @number.boolean('0') + assert_equal false, @number.boolean(nil) + end + +end diff --git a/activerecord/test/cases/types/object_test.rb b/activerecord/test/cases/types/object_test.rb new file mode 100644 index 0000000000..f2667a9b00 --- /dev/null +++ b/activerecord/test/cases/types/object_test.rb @@ -0,0 +1,24 @@ +require "cases/helper" + +class ObjectTest < ActiveRecord::TestCase + + def setup + @column = ActiveRecord::ConnectionAdapters::Column.new('name', '', 'date') + @object = ActiveRecord::Type::Object.new(@column) + end + + test "typecast with column" do + date = Date.new(2009, 7, 10) + assert_equal date, @object.cast('10-07-2009') + assert_equal nil, @object.cast('') + + assert_equal date, @object.precast(date) + end + + test "cast as boolean" do + assert_equal false, @object.boolean(nil) + assert_equal false, @object.boolean('false') + assert_equal true, @object.boolean('10-07-2009') + end + +end diff --git a/activerecord/test/cases/types/serialize_test.rb b/activerecord/test/cases/types/serialize_test.rb new file mode 100644 index 0000000000..e9423a5b9d --- /dev/null +++ b/activerecord/test/cases/types/serialize_test.rb @@ -0,0 +1,20 @@ +require "cases/helper" + +class SerializeTest < ActiveRecord::TestCase + + test "typecast" do + serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array) + + assert_equal [], serializer.cast([].to_yaml) + assert_equal ['1'], serializer.cast(['1'].to_yaml) + assert_equal nil, serializer.cast(nil.to_yaml) + end + + test "cast as boolean" do + serializer = ActiveRecord::Type::Serialize.new(column = nil, :serialize => Array) + + assert_equal true, serializer.boolean(['1'].to_yaml) + assert_equal false, serializer.boolean([].to_yaml) + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/types/time_with_zone_test.rb b/activerecord/test/cases/types/time_with_zone_test.rb new file mode 100644 index 0000000000..b3de79a6c8 --- /dev/null +++ b/activerecord/test/cases/types/time_with_zone_test.rb @@ -0,0 +1,42 @@ +require "cases/helper" + +class TimeWithZoneTest < ActiveRecord::TestCase + + def setup + @column = ActiveRecord::ConnectionAdapters::Column.new('created_at', 0, 'datetime') + @time_with_zone = ActiveRecord::Type::TimeWithZone.new(@column) + end + + test "typecast" do + Time.use_zone("Pacific Time (US & Canada)") do + time_string = "2009-10-07 21:29:10" + time = Time.zone.parse(time_string) + + # assert_equal time, @time_with_zone.cast(time_string) + assert_equal nil, @time_with_zone.cast('') + assert_equal nil, @time_with_zone.cast(nil) + + assert_equal time, @time_with_zone.precast(time) + assert_equal time, @time_with_zone.precast(time_string) + assert_equal time, @time_with_zone.precast(time.to_time) + # assert_equal "#{time.to_date.to_s} 00:00:00 -0700", @time_with_zone.precast(time.to_date).to_s + end + end + + test "cast as boolean" do + Time.use_zone('Central Time (US & Canada)') do + time = Time.zone.now + + assert_equal true, @time_with_zone.boolean(time) + assert_equal true, @time_with_zone.boolean(time.to_date) + assert_equal true, @time_with_zone.boolean(time.to_time) + + assert_equal true, @time_with_zone.boolean(time.to_s) + assert_equal true, @time_with_zone.boolean(time.to_date.to_s) + assert_equal true, @time_with_zone.boolean(time.to_time.to_s) + + assert_equal false, @time_with_zone.boolean('') + end + end + +end diff --git a/activerecord/test/cases/types/unknown_test.rb b/activerecord/test/cases/types/unknown_test.rb new file mode 100644 index 0000000000..230d67b2fb --- /dev/null +++ b/activerecord/test/cases/types/unknown_test.rb @@ -0,0 +1,29 @@ +require "cases/helper" + +class UnknownTest < ActiveRecord::TestCase + + test "typecast attributes does't modify values" do + unkown = ActiveRecord::Type::Unknown.new + person = { 'name' => '0' } + + assert_equal person['name'], unkown.cast(person['name']) + assert_equal person['name'], unkown.precast(person['name']) + end + + test "cast as boolean" do + person = { 'id' => 0, 'name' => ' ', 'admin' => 'false', 'votes' => '0' } + unkown = ActiveRecord::Type::Unknown.new + + assert_equal false, unkown.boolean(person['votes']) + assert_equal false, unkown.boolean(person['admin']) + assert_equal false, unkown.boolean(person['name']) + assert_equal false, unkown.boolean(person['id']) + + person = { 'id' => 5, 'name' => 'Eric', 'admin' => 'true', 'votes' => '25' } + assert_equal true, unkown.boolean(person['votes']) + assert_equal true, unkown.boolean(person['admin']) + assert_equal true, unkown.boolean(person['name']) + assert_equal true, unkown.boolean(person['id']) + end + +end
\ No newline at end of file diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb new file mode 100644 index 0000000000..403a9a6e02 --- /dev/null +++ b/activerecord/test/cases/types_test.rb @@ -0,0 +1,32 @@ +require "cases/helper" +require 'models/topic' + +class TypesTest < ActiveRecord::TestCase + + test "attribute types from columns" do + begin + ActiveRecord::Base.time_zone_aware_attributes = true + attribute_type_classes = {} + Topic.attribute_types.each { |key, type| attribute_type_classes[key] = type.class } + + expected = { "id" => ActiveRecord::Type::Number, + "replies_count" => ActiveRecord::Type::Number, + "parent_id" => ActiveRecord::Type::Number, + "content" => ActiveRecord::Type::Serialize, + "written_on" => ActiveRecord::Type::TimeWithZone, + "title" => ActiveRecord::Type::Object, + "author_name" => ActiveRecord::Type::Object, + "approved" => ActiveRecord::Type::Object, + "parent_title" => ActiveRecord::Type::Object, + "bonus_time" => ActiveRecord::Type::Object, + "type" => ActiveRecord::Type::Object, + "last_read" => ActiveRecord::Type::Object, + "author_email_address" => ActiveRecord::Type::Object } + + assert_equal expected, attribute_type_classes + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + end + end + +end diff --git a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb index 3794a0ebb9..3f96d7973b 100644 --- a/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_generate_message_validation_test.rb @@ -17,26 +17,7 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase } end - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) - def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') - end - - def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) - def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') - end - - def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') - end - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) def test_generate_message_invalid_with_default_message assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') end @@ -45,107 +26,6 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') end - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) - def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) - end - - def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') - end - - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) - def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) - end - - def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') - end - - # add_on_empty: generate_message(attr, :empty, :default => custom_message) - def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) - end - - def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') - end - - # add_on_blank: generate_message(attr, :blank, :default => custom_message) - def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) - end - - def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') - end - - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) - end - - def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) - end - - def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) - end - - def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) - end - - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) - def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') - end - - def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) - def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_odd_with_default_message - assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_even_with_default_message - assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) - end - # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) def test_generate_message_taken_with_default_message assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') @@ -155,4 +35,13 @@ class I18nGenerateMessageValidationTest < ActiveRecord::TestCase assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') end + # ActiveRecord#RecordInvalid exception + + test "RecordInvalid exception can be localized" do + topic = Topic.new + topic.errors.add(:title, :invalid) + topic.errors.add(:title, :blank) + assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message + end + end diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 252138c0d6..532de67d99 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -30,20 +30,6 @@ class I18nValidationTest < ActiveRecord::TestCase end end - def test_percent_s_interpolation_syntax_in_error_messages_was_deprecated - assert_not_deprecated do - default = "%s interpolation syntax was deprecated" - assert_equal default, I18n.t(:does_not_exist, :default => default, :value => 'this') - end - end - - def test_percent_d_interpolation_syntax_in_error_messages_was_deprecated - assert_not_deprecated do - default = "%d interpolation syntaxes are deprecated" - assert_equal default, I18n.t(:does_not_exist, :default => default, :count => 2) - end - end - # ActiveRecord::Errors def test_errors_generate_message_translates_custom_model_attribute_key I18n.expects(:translate).with( @@ -162,722 +148,4 @@ class I18nValidationTest < ActiveRecord::TestCase assert_equal ['global message'], replied_topic.errors[:replies] end - def test_errors_add_on_empty_generates_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => nil}) - @topic.errors.add_on_empty :title - end - - def test_errors_add_on_empty_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :empty, {:default => 'custom'}) - @topic.errors.add_on_empty :title, 'custom' - end - - def test_errors_add_on_blank_generates_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.errors.add_on_blank :title - end - - def test_errors_add_on_blank_generates_message_with_custom_default_message - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.errors.add_on_blank :title, 'custom' - end - - def test_errors_full_messages_translates_human_attribute_name_for_model_attributes - @topic.errors.add('title', 'empty') - I18n.expects(:translate).with(:"topic.title", :default => ['Title'], :scope => [:activerecord, :attributes], :count => 1).returns('Title') - @topic.errors.full_messages :locale => 'en' - end - - # ActiveRecord::Validations - # validates_confirmation_of w/ mocha - def test_validates_confirmation_of_generates_message - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => nil}) - @topic.valid? - end - - def test_validates_confirmation_of_generates_message_with_custom_default_message - Topic.validates_confirmation_of :title, :message => 'custom' - @topic.title_confirmation = 'foo' - @topic.errors.expects(:generate_message).with(:title, :confirmation, {:default => 'custom'}) - @topic.valid? - end - - # validates_acceptance_of w/ mocha - - def test_validates_acceptance_of_generates_message - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => nil}) - @topic.valid? - end - - def test_validates_acceptance_of_generates_message_with_custom_default_message - Topic.validates_acceptance_of :title, :message => 'custom', :allow_nil => false - @topic.errors.expects(:generate_message).with(:title, :accepted, {:default => 'custom'}) - @topic.valid? - end - - # validates_presence_of w/ mocha - - def test_validates_presence_of_generates_message - Topic.validates_presence_of :title - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => nil}) - @topic.valid? - end - - def test_validates_presence_of_generates_message_with_custom_default_message - Topic.validates_presence_of :title, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :blank, {:default => 'custom'}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_length_of :within w/ mocha - - def test_validates_length_of_within_generates_message_with_title_too_short - Topic.validates_length_of :title, :within => 3..5 - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_short_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_short => 'custom' - @topic.errors.expects(:generate_message).with(:title, :too_short, {:count => 3, :default => 'custom'}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long - Topic.validates_length_of :title, :within => 3..5 - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_within_generates_message_with_title_too_long_and_custom_default_message - Topic.validates_length_of :title, :within => 3..5, :too_long => 'custom' - @topic.title = 'this title is too long' - @topic.errors.expects(:generate_message).with(:title, :too_long, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_length_of :is w/ mocha - - def test_validates_length_of_is_generates_message - Topic.validates_length_of :title, :is => 5 - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => nil}) - @topic.valid? - end - - def test_validates_length_of_is_generates_message_with_custom_default_message - Topic.validates_length_of :title, :is => 5, :message => 'custom' - @topic.errors.expects(:generate_message).with(:title, :wrong_length, {:count => 5, :default => 'custom'}) - @topic.valid? - end - - # validates_format_of w/ mocha - - def test_validates_format_of_generates_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => nil}) - @topic.valid? - end - - def test_validates_format_of_generates_message_with_custom_default_message - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/, :message => 'custom' - @topic.title = '72x' - @topic.errors.expects(:generate_message).with(:title, :invalid, {:value => '72x', :default => 'custom'}) - @topic.valid? - end - - # validates_inclusion_of w/ mocha - - def test_validates_inclusion_of_generates_message - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => nil}) - @topic.valid? - end - - def test_validates_inclusion_of_generates_message_with_custom_default_message - Topic.validates_inclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'z' - @topic.errors.expects(:generate_message).with(:title, :inclusion, {:value => 'z', :default => 'custom'}) - @topic.valid? - end - - # validates_exclusion_of w/ mocha - - def test_validates_exclusion_of_generates_message - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => nil}) - @topic.valid? - end - - def test_validates_exclusion_of_generates_message_with_custom_default_message - Topic.validates_exclusion_of :title, :in => %w(a b c), :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :exclusion, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of without :only_integer w/ mocha - - def test_validates_numericality_of_generates_message - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of with :only_integer w/ mocha - - def test_validates_numericality_of_only_integer_generates_message - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_only_integer_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :message => 'custom' - @topic.title = 'a' - @topic.errors.expects(:generate_message).with(:title, :not_a_number, {:value => 'a', :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of :odd w/ mocha - - def test_validates_numericality_of_odd_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :odd => true, :message => 'custom' - @topic.title = 0 - @topic.errors.expects(:generate_message).with(:title, :odd, {:value => 0, :default => 'custom'}) - @topic.valid? - end - - # validates_numericality_of :less_than w/ mocha - - def test_validates_numericality_of_less_than_generates_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => nil}) - @topic.valid? - end - - def test_validates_numericality_of_odd_generates_message_with_custom_default_message - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0, :message => 'custom' - @topic.title = 1 - @topic.errors.expects(:generate_message).with(:title, :less_than, {:value => 1, :count => 0, :default => 'custom'}) - @topic.valid? - end - - # validates_confirmation_of w/o mocha - - def test_validates_confirmation_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:confirmation => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} - - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_confirmation_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:confirmation => 'global message'}}} - - Topic.validates_confirmation_of :title - @topic.title_confirmation = 'foo' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_acceptance_of w/o mocha - - def test_validates_acceptance_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:accepted => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_acceptance_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:accepted => 'global message'}}} - - Topic.validates_acceptance_of :title, :allow_nil => false - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_presence_of w/o mocha - - def test_validates_presence_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:blank => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_presence_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:blank => 'global message'}}} - - Topic.validates_presence_of :title - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_length_of :within w/o mocha - - def test_validates_length_of_within_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:too_short => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_length_of_within_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:too_short => 'global message'}}} - - Topic.validates_length_of :title, :within => 3..5 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_length_of :is w/o mocha - - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - def test_validates_length_of_is_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:wrong_length => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_length_of_is_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:wrong_length => 'global message'}}} - - Topic.validates_length_of :title, :is => 5 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - - # validates_format_of w/o mocha - - def test_validates_format_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:invalid => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_format_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:invalid => 'global message'}}} - - Topic.validates_format_of :title, :with => /^[1-9][0-9]*$/ - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_inclusion_of w/o mocha - - def test_validates_inclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:inclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_inclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:inclusion => 'global message'}}} - - Topic.validates_inclusion_of :title, :in => %w(a b c) - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_exclusion_of w/o mocha - - def test_validates_exclusion_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:exclusion => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_exclusion_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:exclusion => 'global message'}}} - - Topic.validates_exclusion_of :title, :in => %w(a b c) - @topic.title = 'a' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of without :only_integer w/o mocha - - def test_validates_numericality_of_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title - @topic.title = 'a' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of with :only_integer w/o mocha - - def test_validates_numericality_of_only_integer_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:not_a_number => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_only_integer_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:not_a_number => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true - @topic.title = 'a' - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of :odd w/o mocha - - def test_validates_numericality_of_odd_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:odd => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_odd_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:odd => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :odd => true - @topic.title = 0 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - # validates_numericality_of :less_than w/o mocha - - def test_validates_numericality_of_less_than_finds_custom_model_key_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:less_than => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal ['custom message'], @topic.errors[:title] - end - - def test_validates_numericality_of_less_than_finds_global_default_translation - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:less_than => 'global message'}}} - - Topic.validates_numericality_of :title, :only_integer => true, :less_than => 0 - @topic.title = 1 - @topic.valid? - assert_equal ['global message'], @topic.errors[:title] - end - - def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', :activerecord => {:errors => {:messages => {:custom_error => "I am a custom error"}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end - - def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end - - def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', :activerecord => {:errors => {:models => {:topic => {:custom_error => "I am a custom error"}}}} - Topic.validates_presence_of :title, :message => :custom_error - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end - - def test_validates_with_message_string - Topic.validates_presence_of :title, :message => "I am a custom error" - @topic.title = nil - @topic.valid? - assert_equal ["I am a custom error"], @topic.errors[:title] - end -end - -class ActiveRecordValidationsGenerateMessageI18nTests < ActiveRecord::TestCase - - def setup - @topic = Topic.new - I18n.backend.store_translations :'en', { - :activerecord => { - :errors => { - :messages => { - :inclusion => "is not included in the list", - :exclusion => "is reserved", - :invalid => "is invalid", - :confirmation => "doesn't match confirmation", - :accepted => "must be accepted", - :empty => "can't be empty", - :blank => "can't be blank", - :too_long => "is too long (maximum is {{count}} characters)", - :too_short => "is too short (minimum is {{count}} characters)", - :wrong_length => "is the wrong length (should be {{count}} characters)", - :taken => "has already been taken", - :not_a_number => "is not a number", - :greater_than => "must be greater than {{count}}", - :greater_than_or_equal_to => "must be greater than or equal to {{count}}", - :equal_to => "must be equal to {{count}}", - :less_than => "must be less than {{count}}", - :less_than_or_equal_to => "must be less than or equal to {{count}}", - :odd => "must be odd", - :even => "must be even" - } - } - } - } - end - - # validates_inclusion_of: generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) - def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @topic.errors.generate_message(:title, :inclusion, :default => nil, :value => 'title') - end - - def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :inclusion, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_exclusion_of: generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) - def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @topic.errors.generate_message(:title, :exclusion, :default => nil, :value => 'title') - end - - def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :exclusion, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_associated: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - # validates_format_of: generate_message(attr_name, :invalid, :default => configuration[:message], :value => value) - def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @topic.errors.generate_message(:title, :invalid, :default => nil, :value => 'title') - end - - def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :invalid, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_confirmation_of: generate_message(attr_name, :confirmation, :default => configuration[:message]) - def test_generate_message_confirmation_with_default_message - assert_equal "doesn't match confirmation", @topic.errors.generate_message(:title, :confirmation, :default => nil) - end - - def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :confirmation, :default => 'custom message') - end - - # validates_acceptance_of: generate_message(attr_name, :accepted, :default => configuration[:message]) - def test_generate_message_accepted_with_default_message - assert_equal "must be accepted", @topic.errors.generate_message(:title, :accepted, :default => nil) - end - - def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :accepted, :default => 'custom message') - end - - # add_on_empty: generate_message(attr, :empty, :default => custom_message) - def test_generate_message_empty_with_default_message - assert_equal "can't be empty", @topic.errors.generate_message(:title, :empty, :default => nil) - end - - def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :empty, :default => 'custom message') - end - - # add_on_blank: generate_message(attr, :blank, :default => custom_message) - def test_generate_message_blank_with_default_message - assert_equal "can't be blank", @topic.errors.generate_message(:title, :blank, :default => nil) - end - - def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @topic.errors.generate_message(:title, :blank, :default => 'custom message') - end - - # validates_length_of: generate_message(attr, :too_long, :default => options[:too_long], :count => option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @topic.errors.generate_message(:title, :too_long, :default => nil, :count => 10) - end - - def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_long, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, :too_short, :default => options[:too_short], :count => option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @topic.errors.generate_message(:title, :too_short, :default => nil, :count => 10) - end - - def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :too_short, :default => 'custom message {{count}}', :count => 10) - end - - # validates_length_of: generate_message(attr, key, :default => custom_message, :count => option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @topic.errors.generate_message(:title, :wrong_length, :default => nil, :count => 10) - end - - def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @topic.errors.generate_message(:title, :wrong_length, :default => 'custom message {{count}}', :count => 10) - end - - # validates_uniqueness_of: generate_message(attr_name, :taken, :default => configuration[:message]) - def test_generate_message_taken_with_default_message - assert_equal "has already been taken", @topic.errors.generate_message(:title, :taken, :default => nil, :value => 'title') - end - - def test_generate_message_taken_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :taken, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, :not_a_number, :value => raw_value, :default => configuration[:message]) - def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @topic.errors.generate_message(:title, :not_a_number, :default => nil, :value => 'title') - end - - def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @topic.errors.generate_message(:title, :not_a_number, :default => 'custom message {{value}}', :value => 'title') - end - - # validates_numericality_of: generate_message(attr_name, option, :value => raw_value, :default => configuration[:message]) - def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @topic.errors.generate_message(:title, :greater_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @topic.errors.generate_message(:title, :greater_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @topic.errors.generate_message(:title, :equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @topic.errors.generate_message(:title, :less_than, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @topic.errors.generate_message(:title, :less_than_or_equal_to, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_odd_with_default_message - assert_equal "must be odd", @topic.errors.generate_message(:title, :odd, :default => nil, :value => 'title', :count => 10) - end - - def test_generate_message_even_with_default_message - assert_equal "must be even", @topic.errors.generate_message(:title, :even, :default => nil, :value => 'title', :count => 10) - end - # ActiveRecord#RecordInvalid exception - - test "RecordInvalid exception can be localized" do - topic = Topic.new - topic.errors.add(:title, :invalid) - topic.errors.add(:title, :blank) - assert_equal "Validation failed: Title is invalid, Title can't be blank", ActiveRecord::RecordInvalid.new(topic).message - end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 5cdb623eef..130231c622 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -148,40 +148,9 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal "100,000", d.salary_before_type_cast end - def test_validates_length_with_globally_modified_error_message - defaults = ActiveSupport::Deprecation.silence { ActiveRecord::Errors.default_error_messages } - original_message = defaults[:too_short] - defaults[:too_short] = 'tu est trops petit hombre {{count}}' - - Topic.validates_length_of :title, :minimum => 10 - t = Topic.create(:title => 'too short') - assert !t.valid? - - assert_equal ['tu est trops petit hombre 10'], t.errors[:title] - - ensure - defaults[:too_short] = original_message - end - def test_validates_acceptance_of_as_database_column Topic.validates_acceptance_of(:author_name) topic = Topic.create("author_name" => "Dan Brown") assert_equal "Dan Brown", topic["author_name"] end - - def test_deprecated_validation_instance_methods - tom = DeprecatedPerson.new - - assert_deprecated do - assert tom.invalid? - assert_equal ["always invalid", "invalid on create"], tom.errors[:name] - end - - tom.save(false) - - assert_deprecated do - assert tom.invalid? - assert_equal ["always invalid", "invalid on update"], tom.errors[:name] - end - end end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 469f5399ae..7e93fda1eb 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -68,6 +68,8 @@ class Firm < Company has_many :readonly_clients, :class_name => 'Client', :readonly => true has_many :clients_using_primary_key, :class_name => 'Client', :primary_key => 'name', :foreign_key => 'firm_name' + has_many :clients_using_primary_key_with_delete_all, :class_name => 'Client', + :primary_key => 'name', :foreign_key => 'firm_name', :dependent => :delete_all has_many :clients_grouped_by_firm_id, :class_name => "Client", :group => "firm_id", :select => "firm_id" has_many :clients_grouped_by_name, :class_name => "Client", :group => "name", :select => "name" diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 05c5b666ae..f2c05dd48f 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -18,7 +18,7 @@ class Pirate < ActiveRecord::Base has_many :treasure_estimates, :through => :treasures, :source => :price_estimates # These both have :autosave enabled because accepts_nested_attributes_for is used on them. - has_one :ship, :validate => true + has_one :ship has_one :non_validated_ship, :class_name => 'Ship' has_many :birds has_many :birds_with_method_callbacks, :class_name => "Bird", diff --git a/activerecord/test/models/ship.rb b/activerecord/test/models/ship.rb index d0df951622..06759d64b8 100644 --- a/activerecord/test/models/ship.rb +++ b/activerecord/test/models/ship.rb @@ -1,7 +1,7 @@ class Ship < ActiveRecord::Base self.record_timestamps = false - belongs_to :pirate, :validate => true + belongs_to :pirate has_many :parts, :class_name => 'ShipPart', :autosave => true accepts_nested_attributes_for :pirate, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } diff --git a/activeresource/Rakefile b/activeresource/Rakefile index c87345e3b5..6566e84d4c 100644 --- a/activeresource/Rakefile +++ b/activeresource/Rakefile @@ -35,15 +35,16 @@ Rake::TestTask.new { |t| t.warning = true } -task :isolated_test do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib" - Dir.glob("test/**/*_test.rb").all? do |file| - system(ruby, '-w', "-Ilib:test:#{activesupport_path}", file) - end or raise "Failures" +namespace :test do + task :isolated do + ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) + activesupport_path = "#{File.dirname(__FILE__)}/../activesupport/lib" + Dir.glob("test/**/*_test.rb").all? do |file| + system(ruby, '-w', "-Ilib:test:#{activesupport_path}", file) + end or raise "Failures" + end end - # Generate the RDoc documentation Rake::RDocTask.new { |rdoc| @@ -62,8 +63,6 @@ spec = eval(File.read('activeresource.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end task :lines do @@ -80,10 +79,10 @@ task :lines do codelines += 1 end puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - + total_lines += lines total_codelines += codelines - + lines, codelines = 0, 0 end @@ -94,14 +93,14 @@ end # Publishing ------------------------------------------------------ desc "Publish the beta gem" -task :pgem => [:package] do +task :pgem => [:package] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ar", "doc").upload end diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index 803c6be53b..0e74592b0c 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/misc' +require 'active_support/core_ext/object/to_query' require 'set' require 'uri' @@ -280,8 +281,8 @@ module ActiveResource @site = nil else @site = create_site_uri_from(site) - @user = URI.decode(@site.user) if @site.user - @password = URI.decode(@site.password) if @site.password + @user = uri_parser.unescape(@site.user) if @site.user + @password = uri_parser.unescape(@site.password) if @site.password end end @@ -333,6 +334,17 @@ module ActiveResource @password = password end + def auth_type + if defined?(@auth_type) + @auth_type + end + end + + def auth_type=(auth_type) + @connection = nil + @auth_type = auth_type + end + # Sets the format that attributes are sent and received in from a mime type reference: # # Person.format = :json @@ -404,6 +416,7 @@ module ActiveResource @connection.proxy = proxy if proxy @connection.user = user if user @connection.password = password if password + @connection.auth_type = auth_type if auth_type @connection.timeout = timeout if timeout @connection.ssl_options = ssl_options if ssl_options @connection @@ -731,12 +744,12 @@ module ActiveResource # Accepts a URI and creates the site URI from that. def create_site_uri_from(site) - site.is_a?(URI) ? site.dup : URI.parse(site) + site.is_a?(URI) ? site.dup : uri_parser.parse(site) end # Accepts a URI and creates the proxy URI from that. def create_proxy_uri_from(proxy) - proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy) + proxy.is_a?(URI) ? proxy.dup : uri_parser.parse(proxy) end # contains a set of the current prefix parameters. @@ -761,6 +774,10 @@ module ActiveResource [ prefix_options, query_options ] end + + def uri_parser + @uri_parser ||= URI.const_defined?(:Parser) ? URI::Parser.new : URI + end end attr_accessor :attributes #:nodoc: @@ -1146,15 +1163,16 @@ module ActiveResource def respond_to?(method, include_priv = false) method_name = method.to_s if attributes.nil? - return super + super elsif attributes.has_key?(method_name) - return true - elsif ['?','='].include?(method_name.last) && attributes.has_key?(method_name.first(-1)) - return true + true + elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`) + true + else + # super must be called at the end of the method, because the inherited respond_to? + # would return true for generated readers, even if the attribute wasn't present + super end - # super must be called at the end of the method, because the inherited respond_to? - # would return true for generated readers, even if the attribute wasn't present - super end protected @@ -1243,13 +1261,15 @@ module ActiveResource def method_missing(method_symbol, *arguments) #:nodoc: method_name = method_symbol.to_s - case method_name.last + if method_name =~ /(=|\?)$/ + case $1 when "=" - attributes[method_name.first(-1)] = arguments.first + attributes[$`] = arguments.first when "?" - attributes[method_name.first(-1)] - else - attributes.has_key?(method_name) ? attributes[method_name] : super + attributes[$`] + end + else + attributes.include?(method_name) ? attributes[method_name] : super end end end diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb index 9d551f04e7..193be89a82 100644 --- a/activeresource/lib/active_resource/connection.rb +++ b/activeresource/lib/active_resource/connection.rb @@ -17,7 +17,7 @@ module ActiveResource :head => 'Accept' } - attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options + attr_reader :site, :user, :password, :auth_type, :timeout, :proxy, :ssl_options attr_accessor :format class << self @@ -31,20 +31,21 @@ module ActiveResource def initialize(site, format = ActiveResource::Formats::XmlFormat) raise ArgumentError, 'Missing site URI' unless site @user = @password = nil + @uri_parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI self.site = site self.format = format end # Set URI for remote service. def site=(site) - @site = site.is_a?(URI) ? site : URI.parse(site) - @user = URI.decode(@site.user) if @site.user - @password = URI.decode(@site.password) if @site.password + @site = site.is_a?(URI) ? site : @uri_parser.parse(site) + @user = @uri_parser.unescape(@site.user) if @site.user + @password = @uri_parser.unescape(@site.password) if @site.password end # Set the proxy for remote service. def proxy=(proxy) - @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy) + @proxy = proxy.is_a?(URI) ? proxy : @uri_parser.parse(proxy) end # Sets the user for remote service. @@ -57,6 +58,11 @@ module ActiveResource @password = password end + # Sets the auth type for remote service. + def auth_type=(auth_type) + @auth_type = legitimize_auth_type(auth_type) + end + # Sets the number of seconds after which HTTP requests to the remote service should time out. def timeout=(timeout) @timeout = timeout @@ -70,31 +76,31 @@ module ActiveResource # Executes a GET request. # Used to get (find) resources. def get(path, headers = {}) - format.decode(request(:get, path, build_request_headers(headers, :get)).body) + with_auth { format.decode(request(:get, path, build_request_headers(headers, :get, self.site.merge(path))).body) } end # Executes a DELETE request (see HTTP protocol documentation if unfamiliar). # Used to delete resources. def delete(path, headers = {}) - request(:delete, path, build_request_headers(headers, :delete)) + with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) } end # Executes a PUT request (see HTTP protocol documentation if unfamiliar). # Used to update resources. def put(path, body = '', headers = {}) - request(:put, path, body.to_s, build_request_headers(headers, :put)) + with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) } end # Executes a POST request. # Used to create new resources. def post(path, body = '', headers = {}) - request(:post, path, body.to_s, build_request_headers(headers, :post)) + with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) } end # Executes a HEAD request. # Used to obtain meta-information about resources, such as whether they exist and their size (via response headers). def head(path, headers = {}) - request(:head, path, build_request_headers(headers, :head)) + with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) } end @@ -198,13 +204,70 @@ module ActiveResource end # Builds headers for request to remote service. - def build_request_headers(headers, http_method=nil) - authorization_header.update(default_header).update(http_format_header(http_method)).update(headers) + def build_request_headers(headers, http_method, uri) + authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers) + end + + def response_auth_header + @response_auth_header ||= "" + end + + def with_auth + retried ||= false + yield + rescue UnauthorizedAccess => e + raise if retried || auth_type != :digest + @response_auth_header = e.response['WWW-Authenticate'] + retried = true + retry + end + + def authorization_header(http_method, uri) + if @user || @password + if auth_type == :digest + { 'Authorization' => digest_auth_header(http_method, uri) } + else + { 'Authorization' => 'Basic ' + ["#{@user}:#{@password}"].pack('m').delete("\r\n") } + end + else + {} + end end - # Sets authorization header - def authorization_header - (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {}) + def digest_auth_header(http_method, uri) + params = extract_params_from_response + + ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}") + ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{uri.path}") + + params.merge!('cnonce' => client_nonce) + request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":")) + "Digest #{auth_attributes_for(uri, request_digest, params)}" + end + + def client_nonce + Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))) + end + + def extract_params_from_response + params = {} + if response_auth_header =~ /^(\w+) (.*)/ + $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } + end + params + end + + def auth_attributes_for(uri, request_digest, params) + [ + %Q(username="#{@user}"), + %Q(realm="#{params['realm']}"), + %Q(qop="#{params['qop']}"), + %Q(uri="#{uri.path}"), + %Q(nonce="#{params['nonce']}"), + %Q(nc="0"), + %Q(cnonce="#{params['cnonce']}"), + %Q(opaque="#{params['opaque']}"), + %Q(response="#{request_digest}")].join(", ") end def http_format_header(http_method) @@ -214,5 +277,11 @@ module ActiveResource def logger #:nodoc: Base.logger end + + def legitimize_auth_type(auth_type) + return :basic if auth_type.nil? + auth_type = auth_type.to_sym + [:basic, :digest].include?(auth_type) ? auth_type : :basic + end end end diff --git a/activeresource/test/abstract_unit.rb b/activeresource/test/abstract_unit.rb index 05efac79cf..10849be20c 100644 --- a/activeresource/test/abstract_unit.rb +++ b/activeresource/test/abstract_unit.rb @@ -1,8 +1,17 @@ +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + $:.unshift("#{root}/activesupport/lib") +end + +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + require 'rubygems' require 'test/unit' +require 'active_support' require 'active_support/test_case' - -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_resource' $:.unshift "#{File.dirname(__FILE__)}/../test" diff --git a/activeresource/test/cases/authorization_test.rb b/activeresource/test/cases/authorization_test.rb index ca25f437e3..1a7c9ec8a4 100644 --- a/activeresource/test/cases/authorization_test.rb +++ b/activeresource/test/cases/authorization_test.rb @@ -8,46 +8,75 @@ class AuthorizationTest < Test::Unit::TestCase @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person') @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person') @authenticated_conn = ActiveResource::Connection.new("http://david:test123@localhost") - @authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + @basic_authorization_request_header = { 'Authorization' => 'Basic ZGF2aWQ6dGVzdDEyMw==' } + + @nonce = "MTI0OTUxMzc4NzpjYWI3NDM3NDNmY2JmODU4ZjQ2ZjcwNGZkMTJiMjE0NA==" ActiveResource::HttpMock.respond_to do |mock| - mock.get "/people/2.xml", @authorization_request_header, @david - mock.put "/people/2.xml", @authorization_request_header, nil, 204 - mock.delete "/people/2.xml", @authorization_request_header, nil, 200 - mock.post "/people/2/addresses.xml", @authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' + mock.get "/people/2.xml", @basic_authorization_request_header, @david + mock.get "/people/1.xml", @basic_authorization_request_header, nil, 401, { 'WWW-Authenticate' => 'i_should_be_ignored' } + mock.put "/people/2.xml", @basic_authorization_request_header, nil, 204 + mock.delete "/people/2.xml", @basic_authorization_request_header, nil, 200 + mock.post "/people/2/addresses.xml", @basic_authorization_request_header, nil, 201, 'Location' => '/people/1/addresses/5' + mock.head "/people/2.xml", @basic_authorization_request_header, nil, 200 + + mock.get "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "a10c9bd131c9d4d7755b8f4706fd04af") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.get "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "912c7a643f18cda562b8d9662c47b6f5") }, @david, 200 + mock.get "/people/1.xml", { 'Authorization' => request_digest_auth_header("/people/1.xml", "d76e675c0ecfa2bb1abe01491b068a06") }, @matz, 200 + + mock.put "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "7de8a265a5be3c4c2d3a246562ecd6bd") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.put "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "3fb3b33d9d0b869cc75815aa11faacd9") }, nil, 204 + + mock.delete "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "07dfc32769a34ea3510d3a77d64ca495") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.delete "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "5d438610de7ec163b29096c9afcbb254") }, nil, 200 + + mock.post "/people/2/addresses.xml", { 'Authorization' => blank_digest_auth_header("/people/2/addresses.xml", "966dab13620421f928d051f2b9d7b9af") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.post "/people/2/addresses.xml", { 'Authorization' => request_digest_auth_header("/people/2/addresses.xml", "ed540d032c63f8ee34959116c090ec45") }, nil, 201, 'Location' => '/people/1/addresses/5' + + mock.head "/people/2.xml", { 'Authorization' => blank_digest_auth_header("/people/2.xml", "2854eeb92cce2aed29350ea0ce7ba1e2") }, nil, 401, { 'WWW-Authenticate' => response_digest_auth_header } + mock.head "/people/2.xml", { 'Authorization' => request_digest_auth_header("/people/2.xml", "07cd4d247e9c130f92ba2501a080b328") }, nil, 200 + end + + # Make client nonce deterministic + class << @authenticated_conn + private + + def client_nonce + 'i-am-a-client-nonce' + end end end def test_authorization_header - authorization_header = @authenticated_conn.__send__(:authorization_header) - assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_username_but_no_password @conn = ActiveResource::Connection.new("http://david:@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_password_but_no_username @conn = ActiveResource::Connection.new("http://:test123@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split - + assert_equal "Basic", authorization[0] assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end - + def test_authorization_header_with_decoded_credentials_from_url @conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost") - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -58,8 +87,8 @@ class AuthorizationTest < Test::Unit::TestCase @authenticated_conn = ActiveResource::Connection.new("http://@localhost") @authenticated_conn.user = 'david' @authenticated_conn.password = 'test123' - authorization_header = @authenticated_conn.__send__(:authorization_header) - assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -69,7 +98,7 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_username_but_no_password @conn = ActiveResource::Connection.new("http://@localhost") @conn.user = "david" - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] @@ -79,38 +108,119 @@ class AuthorizationTest < Test::Unit::TestCase def test_authorization_header_explicitly_setting_password_but_no_username @conn = ActiveResource::Connection.new("http://@localhost") @conn.password = "test123" - authorization_header = @conn.__send__(:authorization_header) + authorization_header = @conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) authorization = authorization_header["Authorization"].to_s.split assert_equal "Basic", authorization[0] assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] end + def test_authorization_header_if_credentials_supplied_and_auth_type_is_basic + @authenticated_conn.auth_type = :basic + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal @basic_authorization_request_header['Authorization'], authorization_header['Authorization'] + authorization = authorization_header["Authorization"].to_s.split + + assert_equal "Basic", authorization[0] + assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1] + end + + def test_authorization_header_if_credentials_supplied_and_auth_type_is_digest + @authenticated_conn.auth_type = :digest + authorization_header = @authenticated_conn.__send__(:authorization_header, :get, URI.parse('/people/2.xml')) + assert_equal blank_digest_auth_header("/people/2.xml", "a10c9bd131c9d4d7755b8f4706fd04af"), authorization_header['Authorization'] + end + def test_get david = @authenticated_conn.get("/people/2.xml") assert_equal "David", david["name"] end - + def test_post response = @authenticated_conn.post("/people/2/addresses.xml") assert_equal "/people/1/addresses/5", response["Location"] end - + def test_put response = @authenticated_conn.put("/people/2.xml") assert_equal 204, response.code end - + def test_delete response = @authenticated_conn.delete("/people/2.xml") assert_equal 200, response.code end + def test_head + response = @authenticated_conn.head("/people/2.xml") + assert_equal 200, response.code + end + + def test_get_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.get("/people/2.xml") + assert_equal "David", response["name"] + end + + def test_post_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.post("/people/2/addresses.xml") + assert_equal "/people/1/addresses/5", response["Location"] + assert_equal 201, response.code + end + + def test_put_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.put("/people/2.xml") + assert_equal 204, response.code + end + + def test_delete_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.delete("/people/2.xml") + assert_equal 200, response.code + end + + def test_head_with_digest_auth_handles_initial_401_response_and_retries + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.head("/people/2.xml") + assert_equal 200, response.code + end + + def test_get_with_digest_auth_caches_nonce + @authenticated_conn.auth_type = :digest + response = @authenticated_conn.get("/people/2.xml") + assert_equal "David", response["name"] + + # There is no mock for this request with a non-cached nonce. + response = @authenticated_conn.get("/people/1.xml") + assert_equal "Matz", response["name"] + end + + def test_retry_on_401_only_happens_with_digest_auth + assert_raise(ActiveResource::UnauthorizedAccess) { @authenticated_conn.get("/people/1.xml") } + assert_equal "", @authenticated_conn.send(:response_auth_header) + end + def test_raises_invalid_request_on_unauthorized_requests - assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.xml") } + end + + def test_raises_invalid_request_on_unauthorized_requests_with_digest_auth + @conn.auth_type = :digest + assert_raise(ActiveResource::InvalidRequestError) { @conn.get("/people/2.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.post("/people/2/addresses.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.put("/people/2.xml") } assert_raise(ActiveResource::InvalidRequestError) { @conn.delete("/people/2.xml") } + assert_raise(ActiveResource::InvalidRequestError) { @conn.head("/people/2.xml") } + end + + def test_client_nonce_is_not_nil + assert_not_nil ActiveResource::Connection.new("http://david:test123@localhost").send(:client_nonce) end protected @@ -119,4 +229,16 @@ class AuthorizationTest < Test::Unit::TestCase @conn.__send__(:handle_response, Response.new(code)) end end + + def blank_digest_auth_header(uri, response) + %Q(Digest username="david", realm="", qop="", uri="#{uri}", nonce="", nc="0", cnonce="i-am-a-client-nonce", opaque="", response="#{response}") + end + + def request_digest_auth_header(uri, response) + %Q(Digest username="david", realm="RailsTestApp", qop="auth", uri="#{uri}", nonce="#{@nonce}", nc="0", cnonce="i-am-a-client-nonce", opaque="ef6dfb078ba22298d366f99567814ffb", response="#{response}") + end + + def response_digest_auth_header + %Q(Digest realm="RailsTestApp", qop="auth", algorithm=MD5, nonce="#{@nonce}", opaque="ef6dfb078ba22298d366f99567814ffb") + end end diff --git a/activeresource/test/cases/base/load_test.rb b/activeresource/test/cases/base/load_test.rb index 189a4d81fe..7745a9439b 100644 --- a/activeresource/test/cases/base/load_test.rb +++ b/activeresource/test/cases/base/load_test.rb @@ -1,7 +1,6 @@ require 'abstract_unit' require "fixtures/person" require "fixtures/street_address" -require 'active_support/core_ext/symbol' require 'active_support/core_ext/hash/conversions' module Highrise diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 1593e25595..1d3f7891ec 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -163,6 +163,12 @@ class BaseTest < Test::Unit::TestCase assert_equal('test123', Forum.connection.password) end + def test_should_accept_setting_auth_type + Forum.auth_type = :digest + assert_equal(:digest, Forum.auth_type) + assert_equal(:digest, Forum.connection.auth_type) + end + def test_should_accept_setting_timeout Forum.timeout = 5 assert_equal(5, Forum.timeout) diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb index 2a3e04272a..a2744d7531 100644 --- a/activeresource/test/connection_test.rb +++ b/activeresource/test/connection_test.rb @@ -225,6 +225,21 @@ class ConnectionTest < Test::Unit::TestCase assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') } end + def test_auth_type_can_be_string + @conn.auth_type = 'digest' + assert_equal(:digest, @conn.auth_type) + end + + def test_auth_type_defaults_to_basic + @conn.auth_type = nil + assert_equal(:basic, @conn.auth_type) + end + + def test_auth_type_ignores_nonsensical_values + @conn.auth_type = :wibble + assert_equal(:basic, @conn.auth_type) + end + protected def assert_response_raises(klass, code) assert_raise(klass, "Expected response code #{code} to raise #{klass}") do diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 9c5803c52a..4edeadf10c 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,9 @@ *Edge* +* Update Edinburgh TimeZone to use "Europe/London" instead of "Europe/Dublin" #3310 [Phil Ross] + +* Update bundled TZInfo to v0.3.15 [Geoff Buesing] + * JSON: +Object#to_json+ calls +as_json+ to coerce itself into something natively encodable like +Hash+, +Integer+, or +String+. Override +as_json+ instead of +to_json+ so you're JSON library agnostic. [Jeremy Kemper] * String #to_time and #to_datetime: handle fractional seconds #864 [Jason Frey] diff --git a/activesupport/Rakefile b/activesupport/Rakefile index c27167287d..2ada91830f 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -49,8 +49,6 @@ spec = eval(File.read('activesupport.gemspec')) Rake::GemPackageTask.new(spec) do |p| p.gem_spec = spec - p.need_tar = true - p.need_zip = true end desc "Publish the beta gem" @@ -61,7 +59,7 @@ task :pgem => [:package] do end desc "Publish the API documentation" -task :pdoc => [:rdoc] do +task :pdoc => [:rdoc] do require 'rake/contrib/sshpublisher' Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/as", "doc").upload end @@ -88,39 +86,39 @@ namespace :tzinfo do Rake::Task['tzinfo:cleanup_tmp'].invoke puts <<-EOV *** FINAL TZINFO BUNDLING STEPS *** - + 1. Update TZInfo version in lib/active_support/vendor.rb 2. gem uninstall tzinfo on local system before running tests, to ensure tests are running against bundled version - + If a test fails because a particular zone can't be found, it's likely because the TZInfo identifier in the ActiveSupport::TimeZone::MAPPING hash is referencing a linked timezone instead of referencing the timezone directly. In this case, just change the MAPPING value to the correct identifier, and unpack TZInfo again. EOV end - + task :unpack_gem do mkdir_p "tmp" cd "tmp" sh "gem unpack --version #{ENV['VERSION'] || "'> 0'"} tzinfo" cd ".." end - + task :copy_classes => :unpack_gem do - mkdir_p "#{destination_path}/tzinfo" - cp "#{tmp_path}/lib/tzinfo.rb", destination_path - comment_requires_for_excluded_classes!('tzinfo.rb') + mkdir_p "#{destination_path}/lib/tzinfo" + cp "#{tmp_path}/lib/tzinfo.rb", "#{destination_path}/lib" + comment_requires_for_excluded_classes!('lib/tzinfo.rb') files = FileList["#{tmp_path}/lib/tzinfo/*.rb"] files.each do |file| filename = File.basename(file) unless excluded_classes.include? filename.sub(/.rb$/, '') - cp "#{tmp_path}/lib/tzinfo/#{filename}", "#{destination_path}/tzinfo" - comment_requires_for_excluded_classes!("tzinfo/#{filename}") + cp "#{tmp_path}/lib/tzinfo/#{filename}", "#{destination_path}/lib/tzinfo" + comment_requires_for_excluded_classes!("lib/tzinfo/#{filename}") end end end - + task :copy_definitions => :unpack_gem do - definitions_path = "#{destination_path}/tzinfo/definitions/" + definitions_path = "#{destination_path}/lib/tzinfo/definitions/" mkdir_p definitions_path ActiveSupport::TimeZone::MAPPING.values.each do |zone| subdir = nil @@ -135,11 +133,11 @@ namespace :tzinfo do task :cleanup_tmp do rm_rf "tmp" end - + def comment_requires_for_excluded_classes!(file) lines = open("#{destination_path}/#{file}") {|f| f.readlines} updated = false - + new_lines = [] lines.each do |line| if Regexp.new("require 'tzinfo/(#{excluded_classes.join('|')})'") === line @@ -149,29 +147,29 @@ namespace :tzinfo do new_lines << line end end - + if updated open("#{destination_path}/#{file}", "w") {|f| f.write(new_lines.join)} end end - + def version ENV['VERSION'] ||= get_unpacked_version end - + def get_unpacked_version m = (FileList["tmp/tzinfo-*"].to_s.match /\d+\.\d+\.\d+/) m ? m[0] : raise(LoadError, "TZInfo gem must be installed locally. `gem install tzinfo` and try again") end - + def tmp_path "tmp/tzinfo-#{version}" end - + def destination_path "lib/active_support/vendor/tzinfo-#{version}" end - + def excluded_classes %w(country country_index_definition country_info country_timezone timezone_index_definition timezone_proxy tzdataparser) end diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb index 423d5448c3..63f7338a68 100644 --- a/activesupport/lib/active_support/autoload.rb +++ b/activesupport/lib/active_support/autoload.rb @@ -2,12 +2,14 @@ module ActiveSupport autoload :BacktraceCleaner, 'active_support/backtrace_cleaner' autoload :Base64, 'active_support/base64' autoload :BasicObject, 'active_support/basic_object' + autoload :Benchmarkable, 'active_support/benchmarkable' autoload :BufferedLogger, 'active_support/buffered_logger' autoload :Cache, 'active_support/cache' autoload :Callbacks, 'active_support/callbacks' autoload :Concern, 'active_support/concern' - autoload :ConcurrentHash, 'active_support/concurrent_hash' + autoload :Configurable, 'active_support/configurable' autoload :DependencyModule, 'active_support/dependency_module' + autoload :DeprecatedCallbacks, 'active_support/deprecated_callbacks' autoload :Deprecation, 'active_support/deprecation' autoload :Gzip, 'active_support/gzip' autoload :Inflector, 'active_support/inflector' @@ -15,11 +17,10 @@ module ActiveSupport autoload :MessageEncryptor, 'active_support/message_encryptor' autoload :MessageVerifier, 'active_support/message_verifier' autoload :Multibyte, 'active_support/multibyte' - autoload :NewCallbacks, 'active_support/new_callbacks' autoload :OptionMerger, 'active_support/option_merger' - autoload :Orchestra, 'active_support/orchestra' autoload :OrderedHash, 'active_support/ordered_hash' autoload :OrderedOptions, 'active_support/ordered_options' + autoload :Notifications, 'active_support/notifications' autoload :Rescuable, 'active_support/rescuable' autoload :SecureRandom, 'active_support/secure_random' autoload :StringInquirer, 'active_support/string_inquirer' diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb new file mode 100644 index 0000000000..6a41aab166 --- /dev/null +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -0,0 +1,59 @@ +require 'active_support/core_ext/benchmark' + +module ActiveSupport + module Benchmarkable + # Allows you to measure the execution time of a block + # in a template and records the result to the log. Wrap this block around + # expensive operations or possible bottlenecks to get a time reading + # for the operation. For example, let's say you thought your file + # processing method was taking too long; you could wrap it in a benchmark block. + # + # <% benchmark "Process data files" do %> + # <%= expensive_files_operation %> + # <% end %> + # + # That would add something like "Process data files (345.2ms)" to the log, + # which you can then use to compare timings when optimizing your code. + # + # You may give an optional logger level as the :level option. + # (:debug, :info, :warn, :error); the default value is :info. + # + # <% benchmark "Low-level files", :level => :debug do %> + # <%= lowlevel_files_operation %> + # <% end %> + # + # Finally, you can pass true as the third argument to silence all log activity + # inside the block. This is great for boiling down a noisy block to just a single statement: + # + # <% benchmark "Process data files", :level => :info, :silence => true do %> + # <%= expensive_and_chatty_files_operation %> + # <% end %> + def benchmark(message = "Benchmarking", options = {}) + if logger + if options.is_a?(Symbol) + ActiveSupport::Deprecation.warn("use benchmark('#{message}', :level => :#{options}) instead", caller) + options = { :level => options, :silence => false } + else + options.assert_valid_keys(:level, :silence) + options[:level] ||= :info + end + + result = nil + ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield } + logger.send(options[:level], '%s (%.1fms)' % [ message, ms ]) + result + else + yield + end + end + + # Silence the logger during the execution of the block. + # + def silence + old_logger_level, logger.level = logger.level, ::Logger::ERROR if logger + yield + ensure + logger.level = old_logger_level if logger + end + end +end diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index dfce507b33..29c3843d16 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -53,7 +53,6 @@ module ActiveSupport FileUtils.mkdir_p(File.dirname(log)) @log = open(log, (File::WRONLY | File::APPEND | File::CREAT)) @log.sync = true - @log.write("# Logfile created on %s" % [Time.now.to_s]) end end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index a415686020..f2d957f154 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -1,13 +1,9 @@ require 'benchmark' +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/benchmark' require 'active_support/core_ext/exception' require 'active_support/core_ext/class/attribute_accessors' - -%w(hash nil string time date date_time array big_decimal range object boolean).each do |library| - require "active_support/core_ext/#{library}/conversions" -end - -# require 'active_support/core_ext' # FIXME: pulling in all to_param extensions +require 'active_support/core_ext/object/to_param' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -48,7 +44,7 @@ module ActiveSupport # ActiveSupport::Cache.lookup_store(MyOwnCacheStore.new) # # => returns MyOwnCacheStore.new def self.lookup_store(*store_option) - store, *parameters = *([ store_option ].flatten) + store, *parameters = *Array.wrap(store_option).flatten case store when Symbol @@ -105,7 +101,7 @@ module ActiveSupport # cache.write("city", "Duckburgh") # cache.read("city") # => "Duckburgh" class Store - cattr_accessor :logger + cattr_accessor :logger, :instance_writter => false attr_reader :silence alias :silence? :silence @@ -122,6 +118,15 @@ module ActiveSupport @silence = previous_silence end + # Set to true if cache stores should be instrumented. By default is false. + def self.instrument=(boolean) + Thread.current[:instrument_cache_store] = boolean + end + + def self.instrument + Thread.current[:instrument_cache_store] || false + end + # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # @@ -219,7 +224,6 @@ module ActiveSupport end def increment(key, amount = 1) - log("incrementing", key, amount) if num = read(key) write(key, num + amount) else @@ -228,7 +232,6 @@ module ActiveSupport end def decrement(key, amount = 1) - log("decrementing", key, amount) if num = read(key) write(key, num - amount) else @@ -244,16 +247,20 @@ module ActiveSupport end def instrument(operation, key, options, &block) - payload = { :key => key } - payload.merge!(options) if options.is_a?(Hash) + log(operation, key, options) - event = ActiveSupport::Orchestra.instrument(:"cache_#{operation}", payload, &block) - log("#{operation} (%.1fms)" % event.duration, key, options) - event.result + if self.class.instrument + payload = { :key => key } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument(:"cache_#{operation}", payload, &block) + else + yield + end end def log(operation, key, options) - logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !silence? + return unless logger && !silence? + logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") end end end diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 516af99ce9..1b6b820ca4 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -38,6 +38,11 @@ module ActiveSupport # # If no addresses are specified, then MemCacheStore will connect to # localhost port 11211 (the default memcached port). + # + # Instead of addresses one can pass in a MemCache-like object. For example: + # + # require 'memcached' # gem install memcached; uses C bindings to libmemcached + # ActiveSupport::Cache::MemCacheStore.new(Memcached::Rails.new("localhost:11211")) def initialize(*addresses) if addresses.first.respond_to?(:get) @data = addresses.first @@ -103,17 +108,20 @@ module ActiveSupport end def increment(key, amount = 1) # :nodoc: - log("incrementing", key, amount) + response = instrument(:increment, key, :amount => amount) do + @data.incr(key, amount) + end - response = @data.incr(key, amount) response == Response::NOT_FOUND ? nil : response rescue MemCache::MemCacheError nil end def decrement(key, amount = 1) # :nodoc: - log("decrement", key, amount) - response = @data.decr(key, amount) + response = instrument(:decrement, key, :amount => amount) do + @data.decr(key, amount) + end + response == Response::NOT_FOUND ? nil : response rescue MemCache::MemCacheError nil diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 238d1b6804..67e9b0103f 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,4 +1,6 @@ -require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/class/inheritable_attributes' +require 'active_support/core_ext/kernel/reporting' module ActiveSupport # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic @@ -10,23 +12,23 @@ module ActiveSupport # class Storage # include ActiveSupport::Callbacks # - # define_callbacks :before_save, :after_save + # define_callbacks :save # end # # class ConfigStorage < Storage - # before_save :saving_message + # set_callback :save, :before, :saving_message # def saving_message # puts "saving..." # end # - # after_save do |object| + # set_callback :save, :after do |object| # puts "saved" # end # # def save - # run_callbacks(:before_save) - # puts "- save" - # run_callbacks(:after_save) + # run_callbacks :save do + # puts "- save" + # end # end # end # @@ -44,28 +46,28 @@ module ActiveSupport # class Storage # include ActiveSupport::Callbacks # - # define_callbacks :before_save, :after_save + # define_callbacks :save # - # before_save :prepare + # set_callback :save, :before, :prepare # def prepare # puts "preparing save" # end # end # # class ConfigStorage < Storage - # before_save :saving_message + # set_callback :save, :before, :saving_message # def saving_message # puts "saving..." # end # - # after_save do |object| + # set_callback :save, :after do |object| # puts "saved" # end # # def save - # run_callbacks(:before_save) - # puts "- save" - # run_callbacks(:after_save) + # run_callbacks :save do + # puts "- save" + # end # end # end # @@ -77,205 +79,483 @@ module ActiveSupport # saving... # - save # saved + # module Callbacks - class CallbackChain < Array - def self.build(kind, *methods, &block) - methods, options = extract_options(*methods, &block) - methods.map! { |method| Callback.new(kind, method, options) } - new(methods) + extend Concern + + def run_callbacks(kind, *args, &block) + send("_run_#{kind}_callbacks", *args, &block) + end + + class Callback + @@_callback_sequence = 0 + + attr_accessor :chain, :filter, :kind, :options, :per_key, :klass + + def initialize(chain, filter, kind, options, klass) + @chain, @kind, @klass = chain, kind, klass + normalize_options!(options) + + @per_key = options.delete(:per_key) + @raw_filter, @options = filter, options + @filter = _compile_filter(filter) + @compiled_options = _compile_options(options) + @callback_id = next_id + + _compile_per_key_options end - def run(object, options = {}, &terminator) - enumerator = options[:enumerator] || :each + def clone(chain, klass) + obj = super() + obj.chain = chain + obj.klass = klass + obj.per_key = @per_key.dup + obj.options = @options.dup + obj.per_key[:if] = @per_key[:if].dup + obj.per_key[:unless] = @per_key[:unless].dup + obj.options[:if] = @options[:if].dup + obj.options[:unless] = @options[:unless].dup + obj + end - unless block_given? - send(enumerator) { |callback| callback.call(object) } - else - send(enumerator) do |callback| - result = callback.call(object) - break result if terminator.call(result, object) + def normalize_options!(options) + options[:if] = Array.wrap(options[:if]) + options[:unless] = Array.wrap(options[:unless]) + + options[:per_key] ||= {} + options[:per_key][:if] = Array.wrap(options[:per_key][:if]) + options[:per_key][:unless] = Array.wrap(options[:per_key][:unless]) + end + + def name + chain.name + end + + def next_id + @@_callback_sequence += 1 + end + + def matches?(_kind, _filter) + @kind == _kind && @filter == _filter + end + + def _update_filter(filter_options, new_options) + filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless) + filter_options[:unless].push(new_options[:if]) if new_options.key?(:if) + end + + def recompile!(_options, _per_key) + _update_filter(self.options, _options) + _update_filter(self.per_key, _per_key) + + @callback_id = next_id + @filter = _compile_filter(@raw_filter) + @compiled_options = _compile_options(@options) + _compile_per_key_options + end + + def _compile_per_key_options + key_options = _compile_options(@per_key) + + @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def _one_time_conditions_valid_#{@callback_id}? + true #{key_options[0]} end - end + RUBY_EVAL end - # TODO: Decompose into more Array like behavior - def replace_or_append!(chain) - if index = index(chain) - self[index] = chain - else - self << chain + # This will supply contents for before and around filters, and no + # contents for after filters (for the forward pass). + def start(key=nil, object=nil) + return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") + + # options[0] is the compiled form of supplied conditions + # options[1] is the "end" for the conditional + # + if @kind == :before || @kind == :around + if @kind == :before + # if condition # before_save :filter_name, :if => :condition + # filter_name + # end + filter = <<-RUBY_EVAL + unless halted + result = #{@filter} + halted = (#{chain.config[:terminator]}) + end + RUBY_EVAL + + [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") + else + # Compile around filters with conditions into proxy methods + # that contain the conditions. + # + # For `around_save :filter_name, :if => :condition': + # + # def _conditional_callback_save_17 + # if condition + # filter_name do + # yield self + # end + # else + # yield self + # end + # end + # + name = "_conditional_callback_#{@kind}_#{next_id}" + txt, line = <<-RUBY_EVAL, __LINE__ + 1 + def #{name}(halted) + #{@compiled_options[0] || "if true"} && !halted + #{@filter} do + yield self + end + else + yield self + end + end + RUBY_EVAL + @klass.class_eval(txt, __FILE__, line) + "#{name}(halted) do" + end end - self end - def find(callback, &block) - select { |c| c == callback && (!block_given? || yield(c)) }.first - end + # This will supply contents for around and after filters, but not + # before filters (for the backward pass). + def end(key=nil, object=nil) + return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") - def delete(callback) - super(callback.is_a?(Callback) ? callback : find(callback)) + if @kind == :around || @kind == :after + # if condition # after_save :filter_name, :if => :condition + # filter_name + # end + if @kind == :after + [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") + else + "end" + end + end end private - def self.extract_options(*methods, &block) - methods.flatten! - options = methods.extract_options! - methods << block if block_given? - return methods, options - end - def extract_options(*methods, &block) - self.class.extract_options(*methods, &block) + # Options support the same options as filters themselves (and support + # symbols, string, procs, and objects), so compile a conditional + # expression based on the options + def _compile_options(options) + return [] if options[:if].empty? && options[:unless].empty? + + conditions = [] + + unless options[:if].empty? + conditions << Array.wrap(_compile_filter(options[:if])) end - end - class Callback - attr_reader :kind, :method, :identifier, :options + unless options[:unless].empty? + conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} + end - def initialize(kind, method, options = {}) - @kind = kind - @method = method - @identifier = options[:identifier] - @options = options + ["if #{conditions.flatten.join(" && ")}", "end"] end - def ==(other) - case other - when Callback - (self.identifier && self.identifier == other.identifier) || self.method == other.method + # Filters support: + # + # Arrays:: Used in conditions. This is used to specify + # multiple conditions. Used internally to + # merge conditions from skip_* filters + # Symbols:: A method to call + # Strings:: Some content to evaluate + # Procs:: A proc to call with the object + # Objects:: An object with a before_foo method on it to call + # + # All of these objects are compiled into methods and handled + # the same after this point: + # + # Arrays:: Merged together into a single filter + # Symbols:: Already methods + # Strings:: class_eval'ed into methods + # Procs:: define_method'ed into methods + # Objects:: + # a method is created that calls the before_foo method + # on the object. + # + def _compile_filter(filter) + method_name = "_callback_#{@kind}_#{next_id}" + case filter + when Array + filter.map {|f| _compile_filter(f)} + when Symbol + filter + when String + "(#{filter})" + when Proc + @klass.send(:define_method, method_name, &filter) + return method_name if filter.arity <= 0 + + method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else - (self.identifier && self.identifier == other) || self.method == other + @klass.send(:define_method, "#{method_name}_object") { filter } + + _normalize_legacy_filter(kind, filter) + scopes = Array.wrap(chain.config[:scope]) + method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_") + + @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def #{method_name}(&blk) + #{method_name}_object.send(:#{method_to_call}, self, &blk) + end + RUBY_EVAL + + method_name end end - def eql?(other) - self == other + def _normalize_legacy_filter(kind, filter) + if !filter.respond_to?(kind) && filter.respond_to?(:filter) + filter.metaclass.class_eval( + "def #{kind}(context, &block) filter(context, &block) end", + __FILE__, __LINE__ - 1) + elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around + def filter.around(context) + should_continue = before(context) + yield if should_continue + after(context) + end + end end + end + + # An Array with a compile method + class CallbackChain < Array + attr_reader :name, :config - def dup - self.class.new(@kind, @method, @options.dup) + def initialize(name, config) + @name = name + @config = { + :terminator => "false", + :rescuable => false, + :scope => [ :kind ] + }.merge(config) end - def hash - if @identifier - @identifier.hash - else - @method.hash + def compile(key=nil, object=nil) + method = [] + method << "value = nil" + method << "halted = false" + + each do |callback| + method << callback.start(key, object) + end + + if config[:rescuable] + method << "rescued_error = nil" + method << "begin" + end + + method << "value = yield if block_given? && !halted" + + if config[:rescuable] + method << "rescue Exception => e" + method << "rescued_error = e" + method << "end" + end + + reverse_each do |callback| + method << callback.end(key, object) end + + method << "raise rescued_error if rescued_error" if config[:rescuable] + method << "halted ? false : (block_given? ? value : true)" + method.compact.join("\n") end - def call(*args, &block) - evaluate_method(method, *args, &block) if should_run_callback?(*args) - rescue LocalJumpError - raise ArgumentError, - "Cannot yield from a Proc type filter. The Proc must take two " + - "arguments and execute #call on the second argument." + def clone(klass) + chain = CallbackChain.new(@name, @config.dup) + callbacks = map { |c| c.clone(chain, klass) } + chain.push(*callbacks) end + end - private - def evaluate_method(method, *args, &block) - case method - when Symbol - object = args.shift - object.send(method, *args, &block) - when String - eval(method, args.first.instance_eval { binding }) - when Proc, Method - method.call(*args, &block) - else - if method.respond_to?(kind) - method.send(kind, *args, &block) - else - raise ArgumentError, - "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + - "a block to be invoked, or an object responding to the callback method." + module ClassMethods + # Make the run_callbacks :save method. The generated method takes + # a block that it'll yield to. It'll call the before and around filters + # in order, yield the block, and then run the after filters. + # + # run_callbacks :save do + # save + # end + # + # The run_callbacks :save method can optionally take a key, which + # will be used to compile an optimized callback method for each + # key. See #define_callbacks for more information. + # + def __define_runner(symbol) #:nodoc: + body = send("_#{symbol}_callbacks").compile(nil) + + body, line = <<-RUBY_EVAL, __LINE__ + def _run_#{symbol}_callbacks(key = nil, &blk) + if key + name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" + + unless respond_to?(name) + self.class.__create_keyed_callback(name, :#{symbol}, self, &blk) end + + send(name, &blk) + else + #{body} + end end + private :_run_#{symbol}_callbacks + RUBY_EVAL + + silence_warnings do + undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") + class_eval body, __FILE__, line end + end - def should_run_callback?(*args) - [options[:if]].flatten.compact.all? { |a| evaluate_method(a, *args) } && - ![options[:unless]].flatten.compact.any? { |a| evaluate_method(a, *args) } + # This is called the first time a callback is called with a particular + # key. It creates a new callback method for the key, calculating + # which callbacks can be omitted because of per_key conditions. + # + def __create_keyed_callback(name, kind, object, &blk) #:nodoc: + @_keyed_callbacks ||= {} + @_keyed_callbacks[name] ||= begin + str = send("_#{kind}_callbacks").compile(name, object) + class_eval "def #{name}() #{str} end", __FILE__, __LINE__ + true end - end + end - def self.included(base) - base.extend ClassMethods - end + # This is used internally to append, prepend and skip callbacks to the + # CallbackChain. + # + def __update_callbacks(name, filters = [], block = nil) #:nodoc: + type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before + options = filters.last.is_a?(Hash) ? filters.pop : {} + filters.unshift(block) if block - module ClassMethods - def define_callbacks(*callbacks) - callbacks.each do |callback| - class_eval <<-"end_eval", __FILE__, __LINE__ + 1 - def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block) - callbacks = CallbackChain.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block) - @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new - @#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks - end # end - # - def self.#{callback}_callback_chain # def self.before_save_callback_chain - @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new - # - if superclass.respond_to?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain) - CallbackChain.new( # CallbackChain.new( - superclass.#{callback}_callback_chain + # superclass.before_save_callback_chain + - @#{callback}_callbacks # @before_save_callbacks - ) # ) - else # else - @#{callback}_callbacks # @before_save_callbacks - end # end - end # end - end_eval + chain = send("_#{name}_callbacks") + yield chain, type, filters, options if block_given? + + __define_runner(name) + end + + # Set callbacks for a previously defined callback. + # + # Syntax: + # set_callback :save, :before, :before_meth + # set_callback :save, :after, :after_meth, :if => :condition + # set_callback :save, :around, lambda { |r| stuff; yield; stuff } + # + # Use skip_callback to skip any defined one. + # + # When creating or skipping callbacks, you can specify conditions that + # are always the same for a given key. For instance, in ActionPack, + # we convert :only and :except conditions into per-key conditions. + # + # before_filter :authenticate, :except => "index" + # becomes + # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}} + # + # Per-Key conditions are evaluated only once per use of a given key. + # In the case of the above example, you would do: + # + # run_callbacks(:dispatch, action_name) { ... dispatch stuff ... } + # + # In that case, each action_name would get its own compiled callback + # method that took into consideration the per_key conditions. This + # is a speed improvement for ActionPack. + # + def set_callback(name, *filters, &block) + __update_callbacks(name, filters, block) do |chain, type, filters, options| + filters.map! do |filter| + chain.delete_if {|c| c.matches?(type, filter) } + Callback.new(chain, filter, type, options.dup, self) + end + + options[:prepend] ? chain.unshift(*filters) : chain.push(*filters) end end - end - # Runs all the callbacks defined for the given options. - # - # If a block is given it will be called after each callback receiving as arguments: - # - # * the result from the callback - # * the object which has the callback - # - # If the result from the block evaluates to +true+, the callback chain is stopped. - # - # Example: - # class Storage - # include ActiveSupport::Callbacks - # - # define_callbacks :before_save, :after_save - # end - # - # class ConfigStorage < Storage - # before_save :pass - # before_save :pass - # before_save :stop - # before_save :pass - # - # def pass - # puts "pass" - # end - # - # def stop - # puts "stop" - # return false - # end - # - # def save - # result = run_callbacks(:before_save) { |result, object| result == false } - # puts "- save" if result - # end - # end - # - # config = ConfigStorage.new - # config.save - # - # Output: - # pass - # pass - # stop - def run_callbacks(kind, options = {}, &block) - self.class.send("#{kind}_callback_chain").run(self, options, &block) + # Skip a previously defined callback for a given type. + # + def skip_callback(name, *filters, &block) + __update_callbacks(name, filters, block) do |chain, type, filters, options| + chain = send("_#{name}_callbacks=", chain.clone(self)) + + filters.each do |filter| + filter = chain.find {|c| c.matches?(type, filter) } + + if filter && options.any? + filter.recompile!(options, options[:per_key] || {}) + else + chain.delete(filter) + end + end + end + end + + # Reset callbacks for a given type. + # + def reset_callbacks(symbol) + send("_#{symbol}_callbacks").clear + __define_runner(symbol) + end + + # Define callbacks types. + # + # ==== Example + # + # define_callbacks :validate + # + # ==== Options + # + # * <tt>:terminator</tt> - Indicates when a before filter is considered + # to be halted. + # + # define_callbacks :validate, :terminator => "result == false" + # + # In the example above, if any before validate callbacks returns false, + # other callbacks are not executed. Defaults to "false". + # + # * <tt>:rescuable</tt> - By default, after filters are not executed if + # the given block or an before_filter raises an error. Supply :rescuable => true + # to change this behavior. + # + # * <tt>:scope</tt> - Show which methods should be executed when a class + # is giben as callback: + # + # define_callbacks :filters, :scope => [ :kind ] + # + # When a class is given: + # + # before_filter MyFilter + # + # It will call the type of the filter in the given class, which in this + # case, is "before". + # + # If, for instance, you supply the given scope: + # + # define_callbacks :validate, :scope => [ :kind, :name ] + # + # It will call "#{kind}_#{name}" in the given class. So "before_validate" + # will be called in the class below: + # + # before_validate MyValidation + # + # Defaults to :kind. + # + def define_callbacks(*symbols) + config = symbols.last.is_a?(Hash) ? symbols.pop : {} + symbols.each do |symbol| + extlib_inheritable_accessor("_#{symbol}_callbacks") do + CallbackChain.new(symbol, config) + end + + __define_runner(symbol) + end + end end end end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index dcf1e8152f..eb31f7cad4 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -1,11 +1,17 @@ -require 'active_support/dependency_module' - module ActiveSupport module Concern - include DependencyModule + def self.extended(base) + base.instance_variable_set("@_dependencies", []) + end def append_features(base) - if super + if base.instance_variable_defined?("@_dependencies") + base.instance_variable_get("@_dependencies") << self + return false + else + return false if base < self + @_dependencies.each { |dep| base.send(:include, dep) } + super base.extend const_get("ClassMethods") if const_defined?("ClassMethods") base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods") base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block") @@ -19,7 +25,5 @@ module ActiveSupport super end end - - alias_method :include, :depends_on end end diff --git a/activesupport/lib/active_support/concurrent_hash.rb b/activesupport/lib/active_support/concurrent_hash.rb deleted file mode 100644 index 40224765a7..0000000000 --- a/activesupport/lib/active_support/concurrent_hash.rb +++ /dev/null @@ -1,27 +0,0 @@ -module ActiveSupport - class ConcurrentHash - def initialize(hash = {}) - @backup_cache = hash.dup - @frozen_cache = hash.dup.freeze - @mutex = Mutex.new - end - - def []=(k,v) - @mutex.synchronize { @backup_cache[k] = v } - @frozen_cache = @backup_cache.dup.freeze - v - end - - def [](k) - if @frozen_cache.key?(k) - @frozen_cache[k] - else - @mutex.synchronize { @backup_cache[k] } - end - end - - def empty? - @backup_cache.empty? - end - end -end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb new file mode 100644 index 0000000000..890f465ce1 --- /dev/null +++ b/activesupport/lib/active_support/configurable.rb @@ -0,0 +1,35 @@ +require "active_support/concern" + +module ActiveSupport + module Configurable + extend ActiveSupport::Concern + + module ClassMethods + def get_config + module_parts = name.split("::") + modules = [Object] + module_parts.each {|name| modules.push modules.last.const_get(name) } + modules.reverse_each do |mod| + return mod.const_get(:DEFAULT_CONFIG) if const_defined?(:DEFAULT_CONFIG) + end + {} + end + + def config + self.config = get_config unless @config + @config + end + + def config=(hash) + @config = ActiveSupport::OrderedOptions.new + hash.each do |key, value| + @config[key] = value + end + end + end + + def config + self.class.config + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index c53cf3f530..db140225e8 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -40,22 +40,6 @@ class Array end end - - # Calls <tt>to_param</tt> on all its elements and joins the result with - # slashes. This is used by <tt>url_for</tt> in Action Pack. - def to_param - collect { |e| e.to_param }.join '/' - end - - # Converts an array into a string suitable for use as a URL query string, - # using the given +key+ as the param name. - # - # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" - def to_query(key) - prefix = "#{key}[]" - collect { |value| value.to_query(prefix) }.join '&' - end - # Converts a collection of elements into a formatted string by calling # <tt>to_s</tt> on all elements and joining them: # diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 0eb93a0ded..e211bdeeca 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -11,17 +11,12 @@ class Array # Array("foo\nbar") # => ["foo\n", "bar"], in Ruby 1.8 # Array.wrap("foo\nbar") # => ["foo\nbar"] def self.wrap(object) - case object - when nil + if object.nil? [] - when self - object + elsif object.respond_to?(:to_ary) + object.to_ary else - if object.respond_to?(:to_ary) - object.to_ary - else - [object] - end + [object] end end end diff --git a/activesupport/lib/active_support/core_ext/boolean.rb b/activesupport/lib/active_support/core_ext/boolean.rb deleted file mode 100644 index be834288f2..0000000000 --- a/activesupport/lib/active_support/core_ext/boolean.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/boolean/conversions'
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/boolean/conversions.rb b/activesupport/lib/active_support/core_ext/boolean/conversions.rb deleted file mode 100644 index 534ebb7118..0000000000 --- a/activesupport/lib/active_support/core_ext/boolean/conversions.rb +++ /dev/null @@ -1,11 +0,0 @@ -class TrueClass - def to_param - self - end -end - -class FalseClass - def to_param - self - end -end diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index 6c67df7f50..301c09fc73 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,39 +1,57 @@ require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/array/extract_options' class Class def superclass_delegating_reader(*names) - class_name_to_stop_searching_on = superclass.name.blank? ? "Object" : superclass.name + class_to_stop_searching_on = superclass.name.blank? ? "Object" : superclass.name + options = names.extract_options! + names.each do |name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def self.#{name} # def self.only_reader - if defined?(@#{name}) # if defined?(@only_reader) - @#{name} # @only_reader - elsif superclass < #{class_name_to_stop_searching_on} && # elsif superclass < Object && - superclass.respond_to?(:#{name}) # superclass.respond_to?(:only_reader) - superclass.#{name} # superclass.only_reader - end # end - end # end - def #{name} # def only_reader - self.class.#{name} # self.class.only_reader - end # end - def self.#{name}? # def self.only_reader? - !!#{name} # !!only_reader - end # end - def #{name}? # def only_reader? - !!#{name} # !!only_reader - end # end + # def self.only_reader + # if defined?(@only_reader) + # @only_reader + # elsif superclass < Object && superclass.respond_to?(:only_reader) + # superclass.only_reader + # end + # end + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def self.#{name} + if defined?(@#{name}) + @#{name} + elsif superclass < #{class_to_stop_searching_on} && superclass.respond_to?(:#{name}) + superclass.#{name} + end + end EOS + + unless options[:instance_reader] == false + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def #{name} # def only_reader + self.class.#{name} # self.class.only_reader + end # end + def self.#{name}? # def self.only_reader? + !!#{name} # !!only_reader + end # end + def #{name}? # def only_reader? + !!#{name} # !!only_reader + end # end + EOS + end end end def superclass_delegating_writer(*names, &block) + options = names.extract_options! + names.each do |name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) + class_eval <<-EOS, __FILE__, __LINE__ + 1 def self.#{name}=(value) # def self.property=(value) @#{name} = value # @property = value end # end EOS - self.send("#{name}=", yield) if block_given? + + self.send(:"#{name}=", yield) if block_given? end end 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 8bac2dff19..e4d22516c1 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/array/extract_options' # Retain for backward compatibility. Methods are now included in Class. module ClassInheritableAttributes # :nodoc: @@ -10,16 +11,19 @@ end # children, which is unlike the regular class-level attributes that are shared across the entire hierarchy. class Class # :nodoc: def class_inheritable_reader(*syms) + options = syms.extract_options! syms.each do |sym| next if sym.is_a?(Hash) class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def self.#{sym} # def self.after_add - read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add) - end # end - - def #{sym} # def after_add - self.class.#{sym} # self.class.after_add - end # end + def self.#{sym} # def self.after_add + read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add) + end # end + # + #{" # + def #{sym} # def after_add + self.class.#{sym} # self.class.after_add + end # end + " unless options[:instance_reader] == false } # # the reader above is generated unless options[:instance_reader] == false EOS end end @@ -156,7 +160,7 @@ class Class # moving on). In particular, this makes the return value of this function # less useful. def extlib_inheritable_reader(*ivars) - instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash) + options = ivars.extract_options! ivars.each do |ivar| self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -164,10 +168,10 @@ class Class return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar}) ivar = superclass.#{ivar} return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}") - @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar + @#{ivar} = ivar.duplicable? ? ivar.dup : ivar end RUBY - unless instance_reader == false + unless options[:instance_reader] == false self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{ivar} self.class.#{ivar} @@ -190,14 +194,15 @@ class Class # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere. def extlib_inheritable_writer(*ivars) - instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash) + options = ivars.extract_options! + ivars.each do |ivar| self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def self.#{ivar}=(obj) @#{ivar} = obj end RUBY - unless instance_writer == false + unless options[:instance_writer] == false self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{ivar}=(obj) self.class.#{ivar} = obj end RUBY diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb deleted file mode 100644 index 6672129076..0000000000 --- a/activesupport/lib/active_support/core_ext/date.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'date' - -require 'active_support/core_ext/date/acts_like' -require 'active_support/core_ext/date/freeze' - -require 'active_support/core_ext/date/calculations' -require 'active_support/core_ext/date/conversions' diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index ce3bebc25a..3dd61334d0 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,4 +1,5 @@ require 'active_support/duration' +require 'active_support/core_ext/time/zones' class Date class << self diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 54facf4430..f6c870035b 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,6 +1,4 @@ require 'active_support/inflector' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/conversions' class Date DATE_FORMATS = { diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb deleted file mode 100644 index 004fd0ad29..0000000000 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/time' -require 'active_support/core_ext/date_time/acts_like' -require 'active_support/core_ext/date_time/calculations' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date_time/zones' diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 41cf020f94..e93abfa4a3 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -1,4 +1,4 @@ -require 'rational' +require 'rational' unless RUBY_VERSION >= '1.9.2' class DateTime class << self diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 15a303cf04..b11c916f61 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -101,15 +101,6 @@ module Enumerable size = block_given? ? select(&block).size : self.size size > 1 end - - # Returns true if none of the elements match the given block. - # - # success = responses.none? {|r| r.status / 100 == 5 } - # - # This is a builtin method in Ruby 1.8.7 and later. - def none?(&block) - !any?(&block) - end unless [].respond_to?(:none?) end class Range #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index aa686ec2eb..c6b6dc1e5e 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,6 +1,5 @@ require 'active_support/time' -require 'active_support/core_ext/object/conversions' -require 'active_support/core_ext/array/conversions' +require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/hash/reverse_merge' class Hash @@ -68,21 +67,6 @@ class Hash ) end - # Converts a hash into a string suitable for use as a URL query string. An optional <tt>namespace</tt> can be - # passed to enclose the param names (see example below). - # - # ==== Examples - # { :name => 'David', :nationality => 'Danish' }.to_query # => "name=David&nationality=Danish" - # - # { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" - def to_query(namespace = nil) - collect do |key, value| - value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort * '&' - end - - alias_method :to_param, :to_query - # Returns a string containing an XML representation of its receiver: # # {"foo" => 1, "bar" => 2}.to_xml @@ -203,7 +187,7 @@ class Hash case value.class.to_s when 'Hash' if value['type'] == 'array' - child_key, entries = value.detect { |k,v| k != 'type' } # child_key is throwaway + child_key, entries = Array.wrap(value.detect { |k,v| k != 'type' }) # child_key is throwaway if entries.nil? || (c = value['__content__'] && c.blank?) [] else diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 7aa394d7bf..e4a864c20f 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -29,4 +29,10 @@ class Hash replace(hash) omit end + + def extract!(*keys) + result = {} + keys.each {|key| result[key] = delete(key) } + result + end end diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb index 7ba018ed7f..a44a1b4c74 100644 --- a/activesupport/lib/active_support/core_ext/integer.rb +++ b/activesupport/lib/active_support/core_ext/integer.rb @@ -1,3 +1,3 @@ -require 'active_support/core_ext/integer/even_odd' +require 'active_support/core_ext/integer/multiple' require 'active_support/core_ext/integer/inflections' require 'active_support/core_ext/integer/time' diff --git a/activesupport/lib/active_support/core_ext/integer/even_odd.rb b/activesupport/lib/active_support/core_ext/integer/even_odd.rb deleted file mode 100644 index 8f9a97b44c..0000000000 --- a/activesupport/lib/active_support/core_ext/integer/even_odd.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Integer - # Check whether the integer is evenly divisible by the argument. - def multiple_of?(number) - self % number == 0 - end - - # Is the integer a multiple of 2? - def even? - multiple_of? 2 - end unless method_defined?(:even?) - - # Is the integer not a multiple of 2? - def odd? - !even? - end unless method_defined?(:odd?) -end diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb new file mode 100644 index 0000000000..40bea54c67 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb @@ -0,0 +1,6 @@ +class Integer + # Check whether the integer is evenly divisible by the argument. + def multiple_of?(number) + self % number == 0 + end +end diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index 0813a51383..59e03e3df7 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -7,6 +7,7 @@ module Kernel end end + undef :breakpoint if respond_to?(:breakpoint) def breakpoint message = "\n***** The 'breakpoint' command has been renamed 'debugger' -- please change *****\n" defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message) diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index cd165626c8..e1ebd4f91c 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -1,7 +1,9 @@ class NameError # Extract the name of the missing constant from the exception message. def missing_name - $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message + if /undefined local variable or method/ !~ message + $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message + end end # Was this exception raised because the given name was missing? diff --git a/activesupport/lib/active_support/core_ext/nil.rb b/activesupport/lib/active_support/core_ext/nil.rb deleted file mode 100644 index e9f63c4802..0000000000 --- a/activesupport/lib/active_support/core_ext/nil.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/nil/conversions'
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/nil/conversions.rb b/activesupport/lib/active_support/core_ext/nil/conversions.rb deleted file mode 100644 index 6ceb500a2a..0000000000 --- a/activesupport/lib/active_support/core_ext/nil/conversions.rb +++ /dev/null @@ -1,5 +0,0 @@ -class NilClass - def to_param - self - end -end diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb index 638f0decc1..540f7aadb0 100644 --- a/activesupport/lib/active_support/core_ext/object/conversions.rb +++ b/activesupport/lib/active_support/core_ext/object/conversions.rb @@ -1,18 +1,4 @@ +require 'active_support/core_ext/object/to_param' +require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/conversions' - -class Object - # Alias of <tt>to_s</tt>. - def to_param - to_s - end - - # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the - # param name. - # - # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. - def to_query(key) - require 'cgi' unless defined?(CGI) && defined?(CGI::escape) - "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}" - end -end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 1722726ca2..a2d4d50076 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -1,6 +1,6 @@ class Object # Can you safely .dup this object? - # False for nil, false, true, symbols, numbers, and class objects; true otherwise. + # False for nil, false, true, symbols, numbers, class and module objects; true otherwise. def duplicable? true end @@ -41,3 +41,9 @@ class Class #:nodoc: false end end + +class Module #:nodoc: + def duplicable? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index bbf6f8563b..0cc74c8298 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -50,31 +50,4 @@ class Object def extend_with_included_modules_from(object) #:nodoc: object.extended_by.each { |mod| extend mod } end - - unless defined? instance_exec # 1.9 - module InstanceExecMethods #:nodoc: - end - include InstanceExecMethods - - # Evaluate the block with the given arguments within the context of - # this object, so self is set to the method receiver. - # - # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec - def instance_exec(*args, &block) - begin - old_critical, Thread.critical = Thread.critical, true - n = 0 - n += 1 while respond_to?(method_name = "__instance_exec#{n}") - InstanceExecMethods.module_eval { define_method(method_name, &block) } - ensure - Thread.critical = old_critical - end - - begin - send(method_name, *args) - ensure - InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil - end - end - end end diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 4ecaab3bbb..866317b17a 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -1,11 +1,4 @@ class Object - # Available in 1.8.6 and later. - unless respond_to?(:instance_variable_defined?) - def instance_variable_defined?(variable) - instance_variables.include?(variable.to_s) - end - end - # Returns a hash that maps instance variable names without "@" to their # corresponding values. Keys are strings both in Ruby 1.8 and 1.9. # diff --git a/activesupport/lib/active_support/core_ext/object/misc.rb b/activesupport/lib/active_support/core_ext/object/misc.rb index 80011dfbed..3e3af03cc5 100644 --- a/activesupport/lib/active_support/core_ext/object/misc.rb +++ b/activesupport/lib/active_support/core_ext/object/misc.rb @@ -1,3 +1,2 @@ require 'active_support/core_ext/object/returning' -require 'active_support/core_ext/object/tap' require 'active_support/core_ext/object/with_options' diff --git a/activesupport/lib/active_support/core_ext/object/tap.rb b/activesupport/lib/active_support/core_ext/object/tap.rb deleted file mode 100644 index db7e715e2d..0000000000 --- a/activesupport/lib/active_support/core_ext/object/tap.rb +++ /dev/null @@ -1,16 +0,0 @@ -class Object - # Yields <code>x</code> to the block, and then returns <code>x</code>. - # The primary purpose of this method is to "tap into" a method chain, - # in order to perform operations on intermediate results within the chain. - # - # (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a. - # tap { |x| puts "array: #{x.inspect}" }. - # select { |x| x%2 == 0 }. - # tap { |x| puts "evens: #{x.inspect}" }. - # map { |x| x*x }. - # tap { |x| puts "squares: #{x.inspect}" } - def tap - yield self - self - end unless Object.respond_to?(:tap) -end diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb new file mode 100644 index 0000000000..a5e2260791 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -0,0 +1,47 @@ +class Object + # Alias of <tt>to_s</tt>. + def to_param + to_s + end +end + +class NilClass + def to_param + self + end +end + +class TrueClass + def to_param + self + end +end + +class FalseClass + def to_param + self + end +end + +class Array + # Calls <tt>to_param</tt> on all its elements and joins the result with + # slashes. This is used by <tt>url_for</tt> in Action Pack. + def to_param + collect { |e| e.to_param }.join '/' + end +end + +class Hash + # Converts a hash into a string suitable for use as a URL query string. An optional <tt>namespace</tt> can be + # passed to enclose the param names (see example below). + # + # ==== Examples + # { :name => 'David', :nationality => 'Danish' }.to_query # => "name=David&nationality=Danish" + # + # { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + def to_param(namespace = nil) + collect do |key, value| + value.to_query(namespace ? "#{namespace}[#{key}]" : key) + end.sort * '&' + end +end diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb new file mode 100644 index 0000000000..3f1540f685 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -0,0 +1,27 @@ +require 'active_support/core_ext/object/to_param' + +class Object + # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the + # param name. + # + # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. + def to_query(key) + require 'cgi' unless defined?(CGI) && defined?(CGI::escape) + "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}" + end +end + +class Array + # Converts an array into a string suitable for use as a URL query string, + # using the given +key+ as the param name. + # + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" + def to_query(key) + prefix = "#{key}[]" + collect { |value| value.to_query(prefix) }.join '&' + end +end + +class Hash + alias_method :to_query, :to_param +end diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index 95d06ee6ee..784145f5fb 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -1,27 +1,5 @@ class Regexp #:nodoc: - def number_of_captures - Regexp.new("|#{source}").match('').captures.length - end - def multiline? options & MULTILINE == MULTILINE end - - class << self - def optionalize(pattern) - return pattern if pattern == "" - - case unoptionalize(pattern) - when /\A(.|\(.*\))\Z/ then "#{pattern}?" - else "(?:#{pattern})?" - end - end - - def unoptionalize(pattern) - [/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp| - return $1 if regexp =~ pattern - end - return pattern - end - end end diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index 6c52f12712..0365b6af1c 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -4,7 +4,6 @@ require 'active_support/core_ext/string/multibyte' require 'active_support/core_ext/string/starts_ends_with' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/iterators' require 'active_support/core_ext/string/xchar' require 'active_support/core_ext/string/behavior' require 'active_support/core_ext/string/interpolation' diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index bf11d16365..64bc8f6cea 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -1,3 +1,5 @@ +require "active_support/multibyte" + class String unless '1.9'.respond_to?(:force_encoding) # Returns the character at the +position+ treating the string as an array (where 0 is the first character). diff --git a/activesupport/lib/active_support/core_ext/string/bytesize.rb b/activesupport/lib/active_support/core_ext/string/bytesize.rb deleted file mode 100644 index ed051b921e..0000000000 --- a/activesupport/lib/active_support/core_ext/string/bytesize.rb +++ /dev/null @@ -1,5 +0,0 @@ -unless '1.9'.respond_to?(:bytesize) - class String - alias :bytesize :size - end -end diff --git a/activesupport/lib/active_support/core_ext/string/interpolation.rb b/activesupport/lib/active_support/core_ext/string/interpolation.rb index d9159b690a..41a061c1a4 100644 --- a/activesupport/lib/active_support/core_ext/string/interpolation.rb +++ b/activesupport/lib/active_support/core_ext/string/interpolation.rb @@ -6,7 +6,6 @@ =end if RUBY_VERSION < '1.9' - require 'active_support/core_ext/string/bytesize' # KeyError is raised by String#% when the string contains a named placeholder # that is not contained in the given arguments hash. Ruby 1.9 includes and diff --git a/activesupport/lib/active_support/core_ext/string/iterators.rb b/activesupport/lib/active_support/core_ext/string/iterators.rb deleted file mode 100644 index 2f8aa84024..0000000000 --- a/activesupport/lib/active_support/core_ext/string/iterators.rb +++ /dev/null @@ -1,13 +0,0 @@ -unless '1.9'.respond_to?(:each_char) - class String - # Yields a single-character string for each character in the string. - # When $KCODE = 'UTF8', multi-byte characters are yielded appropriately. - def each_char - require 'strscan' unless defined? ::StringScanner - scanner, char = ::StringScanner.new(self), /./mu - while c = scanner.scan(char) - yield c - end - end - end -end diff --git a/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb b/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb index f65bb8f75b..641acf62d0 100644 --- a/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb +++ b/activesupport/lib/active_support/core_ext/string/starts_ends_with.rb @@ -1,18 +1,4 @@ class String - unless '1.8.7 and up'.respond_to?(:start_with?) - # Does the string start with the specified +prefix+? - def start_with?(prefix) - prefix = prefix.to_s - self[0, prefix.length] == prefix - end - - # Does the string end with the specified +suffix+? - def end_with?(suffix) - suffix = suffix.to_s - self[-suffix.length, suffix.length] == suffix - end - end - alias_method :starts_with?, :start_with? alias_method :ends_with?, :end_with? end diff --git a/activesupport/lib/active_support/core_ext/symbol.rb b/activesupport/lib/active_support/core_ext/symbol.rb deleted file mode 100644 index c103cd9dcf..0000000000 --- a/activesupport/lib/active_support/core_ext/symbol.rb +++ /dev/null @@ -1 +0,0 @@ -require 'active_support/core_ext/symbol/to_proc' diff --git a/activesupport/lib/active_support/core_ext/symbol/to_proc.rb b/activesupport/lib/active_support/core_ext/symbol/to_proc.rb deleted file mode 100644 index 520369452b..0000000000 --- a/activesupport/lib/active_support/core_ext/symbol/to_proc.rb +++ /dev/null @@ -1,14 +0,0 @@ -class Symbol - # Turns the symbol into a simple proc, which is especially useful for enumerations. Examples: - # - # # The same as people.collect { |p| p.name } - # people.collect(&:name) - # - # # The same as people.select { |p| p.manager? }.collect { |p| p.salary } - # people.select(&:manager?).collect(&:salary) - # - # This is a builtin method in Ruby 1.8.7 and later. - def to_proc - Proc.new { |*args| args.shift.__send__(self, *args) } - end unless :to_proc.respond_to?(:to_proc) -end diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb deleted file mode 100644 index b28f7f1a32..0000000000 --- a/activesupport/lib/active_support/core_ext/time.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'date' -require 'time' - -require 'active_support/core_ext/time/publicize_conversion_methods' -require 'active_support/core_ext/time/marshal_with_utc_flag' - -require 'active_support/core_ext/time/acts_like' -require 'active_support/core_ext/time/calculations' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index 9a1c61d99b..ca1be8b7e9 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -4,7 +4,8 @@ if RUBY_VERSION >= '1.9' str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. str.force_encoding(Encoding::UTF_8) if str.respond_to?(:force_encoding) - unless str == URI.unescape(URI.escape(str)) + parser = URI::Parser.new + unless str == parser.unescape(parser.escape(str)) URI::Parser.class_eval do remove_method :unescape def unescape(str, escaped = @regexp[:ESCAPED]) diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 7f6f012721..e858bcdc80 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -339,7 +339,7 @@ module ActiveSupport #:nodoc: next end [ nesting_camel ] - end.flatten.compact.uniq + end.compact.flatten.compact.uniq end # Search for a file in load_paths matching the provided suffix. diff --git a/activesupport/lib/active_support/dependency_module.rb b/activesupport/lib/active_support/dependency_module.rb deleted file mode 100644 index 6847c0f86a..0000000000 --- a/activesupport/lib/active_support/dependency_module.rb +++ /dev/null @@ -1,17 +0,0 @@ -module ActiveSupport - module DependencyModule - def append_features(base) - return false if base < self - (@_dependencies ||= []).each { |dep| base.send(:include, dep) } - super - end - - def depends_on(*mods) - mods.each do |mod| - next if self < mod - @_dependencies ||= [] - @_dependencies << mod - end - end - end -end diff --git a/activesupport/lib/active_support/deprecated_callbacks.rb b/activesupport/lib/active_support/deprecated_callbacks.rb new file mode 100644 index 0000000000..f56fef0b6d --- /dev/null +++ b/activesupport/lib/active_support/deprecated_callbacks.rb @@ -0,0 +1,284 @@ +require 'active_support/core_ext/array/extract_options' +require 'active_support/core_ext/array/wrap' + +module ActiveSupport + # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic + # before or after an alteration of the object state. + # + # Mixing in this module allows you to define callbacks in your class. + # + # Example: + # class Storage + # include ActiveSupport::DeprecatedCallbacks + # + # define_callbacks :before_save, :after_save + # end + # + # class ConfigStorage < Storage + # before_save :saving_message + # def saving_message + # puts "saving..." + # end + # + # after_save do |object| + # puts "saved" + # end + # + # def save + # run_callbacks(:before_save) + # puts "- save" + # run_callbacks(:after_save) + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # saving... + # - save + # saved + # + # Callbacks from parent classes are inherited. + # + # Example: + # class Storage + # include ActiveSupport::DeprecatedCallbacks + # + # define_callbacks :before_save, :after_save + # + # before_save :prepare + # def prepare + # puts "preparing save" + # end + # end + # + # class ConfigStorage < Storage + # before_save :saving_message + # def saving_message + # puts "saving..." + # end + # + # after_save do |object| + # puts "saved" + # end + # + # def save + # run_callbacks(:before_save) + # puts "- save" + # run_callbacks(:after_save) + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # preparing save + # saving... + # - save + # saved + module DeprecatedCallbacks + class CallbackChain < Array + def self.build(kind, *methods, &block) + methods, options = extract_options(*methods, &block) + methods.map! { |method| Callback.new(kind, method, options) } + new(methods) + end + + def run(object, options = {}, &terminator) + enumerator = options[:enumerator] || :each + + unless block_given? + send(enumerator) { |callback| callback.call(object) } + else + send(enumerator) do |callback| + result = callback.call(object) + break result if terminator.call(result, object) + end + end + end + + # TODO: Decompose into more Array like behavior + def replace_or_append!(chain) + if index = index(chain) + self[index] = chain + else + self << chain + end + self + end + + def find(callback, &block) + select { |c| c == callback && (!block_given? || yield(c)) }.first + end + + def delete(callback) + super(callback.is_a?(Callback) ? callback : find(callback)) + end + + private + def self.extract_options(*methods, &block) + methods.flatten! + options = methods.extract_options! + methods << block if block_given? + return methods, options + end + + def extract_options(*methods, &block) + self.class.extract_options(*methods, &block) + end + end + + class Callback + attr_reader :kind, :method, :identifier, :options + + def initialize(kind, method, options = {}) + @kind = kind + @method = method + @identifier = options[:identifier] + @options = options + end + + def ==(other) + case other + when Callback + (self.identifier && self.identifier == other.identifier) || self.method == other.method + else + (self.identifier && self.identifier == other) || self.method == other + end + end + + def eql?(other) + self == other + end + + def dup + self.class.new(@kind, @method, @options.dup) + end + + def hash + if @identifier + @identifier.hash + else + @method.hash + end + end + + def call(*args, &block) + evaluate_method(method, *args, &block) if should_run_callback?(*args) + rescue LocalJumpError + raise ArgumentError, + "Cannot yield from a Proc type filter. The Proc must take two " + + "arguments and execute #call on the second argument." + end + + private + def evaluate_method(method, *args, &block) + case method + when Symbol + object = args.shift + object.send(method, *args, &block) + when String + eval(method, args.first.instance_eval { binding }) + when Proc, Method + method.call(*args, &block) + else + if method.respond_to?(kind) + method.send(kind, *args, &block) + else + raise ArgumentError, + "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " + + "a block to be invoked, or an object responding to the callback method." + end + end + end + + def should_run_callback?(*args) + Array.wrap(options[:if]).flatten.compact.all? { |a| evaluate_method(a, *args) } && + !Array.wrap(options[:unless]).flatten.compact.any? { |a| evaluate_method(a, *args) } + end + end + + def self.included(base) + base.extend ClassMethods + end + + module ClassMethods + def define_callbacks(*callbacks) + ActiveSupport::Deprecation.warn('ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks', caller) + + callbacks.each do |callback| + class_eval <<-"end_eval", __FILE__, __LINE__ + 1 + def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block) + callbacks = CallbackChain.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block) + @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new + @#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks + end # end + # + def self.#{callback}_callback_chain # def self.before_save_callback_chain + @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new + # + if superclass.respond_to?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain) + CallbackChain.new( # CallbackChain.new( + superclass.#{callback}_callback_chain + # superclass.before_save_callback_chain + + @#{callback}_callbacks # @before_save_callbacks + ) # ) + else # else + @#{callback}_callbacks # @before_save_callbacks + end # end + end # end + end_eval + end + end + end + + # Runs all the callbacks defined for the given options. + # + # If a block is given it will be called after each callback receiving as arguments: + # + # * the result from the callback + # * the object which has the callback + # + # If the result from the block evaluates to +true+, the callback chain is stopped. + # + # Example: + # class Storage + # include ActiveSupport::DeprecatedCallbacks + # + # define_callbacks :before_save, :after_save + # end + # + # class ConfigStorage < Storage + # before_save :pass + # before_save :pass + # before_save :stop + # before_save :pass + # + # def pass + # puts "pass" + # end + # + # def stop + # puts "stop" + # return false + # end + # + # def save + # result = run_callbacks(:before_save) { |result, object| result == false } + # puts "- save" if result + # end + # end + # + # config = ConfigStorage.new + # config.save + # + # Output: + # pass + # pass + # stop + def run_callbacks(kind, options = {}, &block) + self.class.send("#{kind}_callback_chain").run(self, options, &block) + end + end +end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index c531a1aa58..ca23d45666 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -23,7 +23,13 @@ module ActiveSupport $stderr.puts callstack.join("\n ") if debug }, 'development' => Proc.new { |message, callstack| - logger = defined?(Rails) ? Rails.logger : Logger.new($stderr) + logger = + if defined?(Rails) && Rails.logger + Rails.logger + else + require 'logger' + Logger.new($stderr) + end logger.warn message logger.debug callstack.join("\n ") if debug } diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index 67aea2782f..215a60eba7 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,410 +1,7 @@ -# encoding: utf-8 -require 'iconv' -require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/string/access' -require 'active_support/core_ext/string/multibyte' - -module ActiveSupport - # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, - # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept - # in inflections.rb. - # - # The Rails core team has stated patches for the inflections library will not be accepted - # in order to avoid breaking legacy applications which may be relying on errant inflections. - # If you discover an incorrect inflection and require it for your application, you'll need - # to correct it yourself (explained below). - module Inflector - extend self - - # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional - # inflection rules. Examples: - # - # ActiveSupport::Inflector.inflections do |inflect| - # inflect.plural /^(ox)$/i, '\1\2en' - # inflect.singular /^(ox)en/i, '\1' - # - # inflect.irregular 'octopus', 'octopi' - # - # inflect.uncountable "equipment" - # end - # - # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the - # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may - # already have been loaded. - class Inflections - def self.instance - @__instance__ ||= new - end - - attr_reader :plurals, :singulars, :uncountables, :humans - - def initialize - @plurals, @singulars, @uncountables, @humans = [], [], [], [] - end - - # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. - # The replacement should always be a string that may include references to the matched data from the rule. - def plural(rule, replacement) - @uncountables.delete(rule) if rule.is_a?(String) - @uncountables.delete(replacement) - @plurals.insert(0, [rule, replacement]) - end - - # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. - # The replacement should always be a string that may include references to the matched data from the rule. - def singular(rule, replacement) - @uncountables.delete(rule) if rule.is_a?(String) - @uncountables.delete(replacement) - @singulars.insert(0, [rule, replacement]) - end - - # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used - # for strings, not regular expressions. You simply pass the irregular in singular and plural form. - # - # Examples: - # irregular 'octopus', 'octopi' - # irregular 'person', 'people' - def irregular(singular, plural) - @uncountables.delete(singular) - @uncountables.delete(plural) - if singular[0,1].upcase == plural[0,1].upcase - plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) - plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1]) - singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) - else - plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) - plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) - singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) - singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) - end - end - - # Add uncountable words that shouldn't be attempted inflected. - # - # Examples: - # uncountable "money" - # uncountable "money", "information" - # uncountable %w( money information rice ) - def uncountable(*words) - (@uncountables << words).flatten! - end - - # Specifies a humanized form of a string by a regular expression rule or by a string mapping. - # When using a regular expression based replacement, the normal humanize formatting is called after the replacement. - # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name') - # - # Examples: - # human /_cnt$/i, '\1_count' - # human "legacy_col_person_name", "Name" - def human(rule, replacement) - @humans.insert(0, [rule, replacement]) - end - - # Clears the loaded inflections within a given scope (default is <tt>:all</tt>). - # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>, - # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>. - # - # Examples: - # clear :all - # clear :plurals - def clear(scope = :all) - case scope - when :all - @plurals, @singulars, @uncountables = [], [], [] - else - instance_variable_set "@#{scope}", [] - end - end - end - - # Yields a singleton instance of Inflector::Inflections so you can specify additional - # inflector rules. - # - # Example: - # ActiveSupport::Inflector.inflections do |inflect| - # inflect.uncountable "rails" - # end - def inflections - if block_given? - yield Inflections.instance - else - Inflections.instance - end - end - - # Returns the plural form of the word in the string. - # - # Examples: - # "post".pluralize # => "posts" - # "octopus".pluralize # => "octopi" - # "sheep".pluralize # => "sheep" - # "words".pluralize # => "words" - # "CamelOctopus".pluralize # => "CamelOctopi" - def pluralize(word) - result = word.to_s.dup - - if word.empty? || inflections.uncountables.include?(result.downcase) - result - else - inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # The reverse of +pluralize+, returns the singular form of a word in a string. - # - # Examples: - # "posts".singularize # => "post" - # "octopi".singularize # => "octopus" - # "sheep".singularize # => "sheep" - # "word".singularize # => "word" - # "CamelOctopi".singularize # => "CamelOctopus" - def singularize(word) - result = word.to_s.dup - - if inflections.uncountables.include?(result.downcase) - result - else - inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result - end - end - - # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ - # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. - # - # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. - # - # Examples: - # "active_record".camelize # => "ActiveRecord" - # "active_record".camelize(:lower) # => "activeRecord" - # "active_record/errors".camelize # => "ActiveRecord::Errors" - # "active_record/errors".camelize(:lower) # => "activeRecord::Errors" - def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) - if first_letter_in_uppercase - lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } - else - lower_case_and_underscored_word.to_s.first.downcase + camelize(lower_case_and_underscored_word)[1..-1] - end - end - - # Capitalizes all the words and replaces some characters in the string to create - # a nicer looking title. +titleize+ is meant for creating pretty output. It is not - # used in the Rails internals. - # - # +titleize+ is also aliased as as +titlecase+. - # - # Examples: - # "man from the boondocks".titleize # => "Man From The Boondocks" - # "x-men: the last stand".titleize # => "X Men: The Last Stand" - def titleize(word) - humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } - end - - # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. - # - # Changes '::' to '/' to convert namespaces to paths. - # - # Examples: - # "ActiveRecord".underscore # => "active_record" - # "ActiveRecord::Errors".underscore # => active_record/errors - def underscore(camel_cased_word) - camel_cased_word.to_s.gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). - gsub(/([a-z\d])([A-Z])/,'\1_\2'). - tr("-", "_"). - downcase - end - - # Replaces underscores with dashes in the string. - # - # Example: - # "puni_puni" # => "puni-puni" - def dasherize(underscored_word) - underscored_word.gsub(/_/, '-') - end - - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. - # - # Examples: - # "employee_salary" # => "Employee salary" - # "author_id" # => "Author" - def humanize(lower_case_and_underscored_word) - result = lower_case_and_underscored_word.to_s.dup - - inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } - result.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - # Removes the module part from the expression in the string. - # - # Examples: - # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" - # "Inflections".demodulize # => "Inflections" - def demodulize(class_name_in_module) - class_name_in_module.to_s.gsub(/^.*::/, '') - end - - # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. - # - # ==== Examples - # - # class Person - # def to_param - # "#{id}-#{name.parameterize}" - # end - # end - # - # @person = Person.find(1) - # # => #<Person id: 1, name: "Donald E. Knuth"> - # - # <%= link_to(@person.name, person_path(@person)) %> - # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> - def parameterize(string, sep = '-') - # replace accented chars with their ascii equivalents - parameterized_string = transliterate(string) - # Turn unwanted chars into the separator - parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep) - unless sep.blank? - re_sep = Regexp.escape(sep) - # No more than one of the separator in a row. - parameterized_string.gsub!(/#{re_sep}{2,}/, sep) - # Remove leading/trailing separator. - parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') - end - parameterized_string.downcase - end - - - # Replaces accented characters with their ascii equivalents. - def transliterate(string) - Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s - end - - if RUBY_VERSION >= '1.9' - undef_method :transliterate - def transliterate(string) - warn "Ruby 1.9 doesn't support Unicode normalization yet" - string.dup - end - - # The iconv transliteration code doesn't function correctly - # on some platforms, but it's very fast where it does function. - elsif "foo" != (Inflector.transliterate("föö") rescue nil) - undef_method :transliterate - def transliterate(string) - string.mb_chars.normalize(:kd). # Decompose accented characters - gsub(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics). - end - end - - # Create the name of a table like Rails does for models to table names. This method - # uses the +pluralize+ method on the last word in the string. - # - # Examples - # "RawScaledScorer".tableize # => "raw_scaled_scorers" - # "egg_and_ham".tableize # => "egg_and_hams" - # "fancyCategory".tableize # => "fancy_categories" - def tableize(class_name) - pluralize(underscore(class_name)) - end - - # Create a class name from a plural table name like Rails does for table names to models. - # Note that this returns a string and not a Class. (To convert to an actual class - # follow +classify+ with +constantize+.) - # - # Examples: - # "egg_and_hams".classify # => "EggAndHam" - # "posts".classify # => "Post" - # - # Singular names are not handled correctly: - # "business".classify # => "Busines" - def classify(table_name) - # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) - end - - # Creates a foreign key name from a class name. - # +separate_class_name_and_id_with_underscore+ sets whether - # the method should put '_' between the name and 'id'. - # - # Examples: - # "Message".foreign_key # => "message_id" - # "Message".foreign_key(false) # => "messageid" - # "Admin::Post".foreign_key # => "post_id" - def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) - underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") - end - - # Ruby 1.9 introduces an inherit argument for Module#const_get and - # #const_defined? and changes their default behavior. - if Module.method(:const_get).arity == 1 - # Tries to find a constant with the name specified in the argument string: - # - # "Module".constantize # => Module - # "Test::Unit".constantize # => Test::Unit - # - # The name is assumed to be the one of a top-level constant, no matter whether - # it starts with "::" or not. No lexical context is taken into account: - # - # C = 'outside' - # module M - # C = 'inside' - # C # => 'inside' - # "C".constantize # => 'outside', same as ::C - # end - # - # NameError is raised when the name is not in CamelCase or the constant is - # unknown. - def constantize(camel_cased_word) - names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? - - constant = Object - names.each do |name| - constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) - end - constant - end - else - def constantize(camel_cased_word) #:nodoc: - names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? - - constant = Object - names.each do |name| - constant = constant.const_get(name, false) || constant.const_missing(name) - end - constant - end - end - - # Turns a number into an ordinal string used to denote the position in an - # ordered sequence such as 1st, 2nd, 3rd, 4th. - # - # Examples: - # ordinalize(1) # => "1st" - # ordinalize(2) # => "2nd" - # ordinalize(1002) # => "1002nd" - # ordinalize(1003) # => "1003rd" - def ordinalize(number) - if (11..13).include?(number.to_i % 100) - "#{number}th" - else - case number.to_i % 10 - when 1; "#{number}st" - when 2; "#{number}nd" - when 3; "#{number}rd" - else "#{number}th" - end - end - end - end -end - # in case active_support/inflector is required without the rest of active_support +require 'active_support/inflector/inflections' +require 'active_support/inflector/transliterate' +require 'active_support/inflector/methods' + require 'active_support/inflections' require 'active_support/core_ext/string/inflections' diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb new file mode 100644 index 0000000000..785e245ea4 --- /dev/null +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -0,0 +1,211 @@ +module ActiveSupport + module Inflector + # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional + # inflection rules. Examples: + # + # ActiveSupport::Inflector.inflections do |inflect| + # inflect.plural /^(ox)$/i, '\1\2en' + # inflect.singular /^(ox)en/i, '\1' + # + # inflect.irregular 'octopus', 'octopi' + # + # inflect.uncountable "equipment" + # end + # + # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the + # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may + # already have been loaded. + class Inflections + def self.instance + @__instance__ ||= new + end + + attr_reader :plurals, :singulars, :uncountables, :humans + + def initialize + @plurals, @singulars, @uncountables, @humans = [], [], [], [] + end + + # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. + # The replacement should always be a string that may include references to the matched data from the rule. + def plural(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) + @plurals.insert(0, [rule, replacement]) + end + + # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. + # The replacement should always be a string that may include references to the matched data from the rule. + def singular(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) + @singulars.insert(0, [rule, replacement]) + end + + # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used + # for strings, not regular expressions. You simply pass the irregular in singular and plural form. + # + # Examples: + # irregular 'octopus', 'octopi' + # irregular 'person', 'people' + def irregular(singular, plural) + @uncountables.delete(singular) + @uncountables.delete(plural) + if singular[0,1].upcase == plural[0,1].upcase + plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) + plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1]) + singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) + else + plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) + plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) + plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1]) + plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1]) + singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1]) + singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1]) + end + end + + # Add uncountable words that shouldn't be attempted inflected. + # + # Examples: + # uncountable "money" + # uncountable "money", "information" + # uncountable %w( money information rice ) + def uncountable(*words) + (@uncountables << words).flatten! + end + + # Specifies a humanized form of a string by a regular expression rule or by a string mapping. + # When using a regular expression based replacement, the normal humanize formatting is called after the replacement. + # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name') + # + # Examples: + # human /_cnt$/i, '\1_count' + # human "legacy_col_person_name", "Name" + def human(rule, replacement) + @humans.insert(0, [rule, replacement]) + end + + # Clears the loaded inflections within a given scope (default is <tt>:all</tt>). + # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>, + # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>. + # + # Examples: + # clear :all + # clear :plurals + def clear(scope = :all) + case scope + when :all + @plurals, @singulars, @uncountables = [], [], [] + else + instance_variable_set "@#{scope}", [] + end + end + end + + # Yields a singleton instance of Inflector::Inflections so you can specify additional + # inflector rules. + # + # Example: + # ActiveSupport::Inflector.inflections do |inflect| + # inflect.uncountable "rails" + # end + def inflections + if block_given? + yield Inflections.instance + else + Inflections.instance + end + end + + # Returns the plural form of the word in the string. + # + # Examples: + # "post".pluralize # => "posts" + # "octopus".pluralize # => "octopi" + # "sheep".pluralize # => "sheep" + # "words".pluralize # => "words" + # "CamelOctopus".pluralize # => "CamelOctopi" + def pluralize(word) + result = word.to_s.dup + + if word.empty? || inflections.uncountables.include?(result.downcase) + result + else + inflections.plurals.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + + # The reverse of +pluralize+, returns the singular form of a word in a string. + # + # Examples: + # "posts".singularize # => "post" + # "octopi".singularize # => "octopus" + # "sheep".singularize # => "sheep" + # "word".singularize # => "word" + # "CamelOctopi".singularize # => "CamelOctopus" + def singularize(word) + result = word.to_s.dup + + if inflections.uncountables.include?(result.downcase) + result + else + inflections.singulars.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result + end + end + + # Capitalizes the first word and turns underscores into spaces and strips a + # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output. + # + # Examples: + # "employee_salary" # => "Employee salary" + # "author_id" # => "Author" + def humanize(lower_case_and_underscored_word) + result = lower_case_and_underscored_word.to_s.dup + + inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) } + result.gsub(/_id$/, "").gsub(/_/, " ").capitalize + end + + # Capitalizes all the words and replaces some characters in the string to create + # a nicer looking title. +titleize+ is meant for creating pretty output. It is not + # used in the Rails internals. + # + # +titleize+ is also aliased as as +titlecase+. + # + # Examples: + # "man from the boondocks".titleize # => "Man From The Boondocks" + # "x-men: the last stand".titleize # => "X Men: The Last Stand" + def titleize(word) + humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize } + end + + # Create the name of a table like Rails does for models to table names. This method + # uses the +pluralize+ method on the last word in the string. + # + # Examples + # "RawScaledScorer".tableize # => "raw_scaled_scorers" + # "egg_and_ham".tableize # => "egg_and_hams" + # "fancyCategory".tableize # => "fancy_categories" + def tableize(class_name) + pluralize(underscore(class_name)) + end + + # Create a class name from a plural table name like Rails does for table names to models. + # Note that this returns a string and not a Class. (To convert to an actual class + # follow +classify+ with +constantize+.) + # + # Examples: + # "egg_and_hams".classify # => "EggAndHam" + # "posts".classify # => "Post" + # + # Singular names are not handled correctly: + # "business".classify # => "Busines" + def classify(table_name) + # strip out any leading schema name + camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + end + end +end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb new file mode 100644 index 0000000000..41277893e3 --- /dev/null +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -0,0 +1,139 @@ +module ActiveSupport + # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, + # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept + # in inflections.rb. + # + # The Rails core team has stated patches for the inflections library will not be accepted + # in order to avoid breaking legacy applications which may be relying on errant inflections. + # If you discover an incorrect inflection and require it for your application, you'll need + # to correct it yourself (explained below). + module Inflector + extend self + + # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+ + # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase. + # + # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces. + # + # Examples: + # "active_record".camelize # => "ActiveRecord" + # "active_record".camelize(:lower) # => "activeRecord" + # "active_record/errors".camelize # => "ActiveRecord::Errors" + # "active_record/errors".camelize(:lower) # => "activeRecord::Errors" + def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) + if first_letter_in_uppercase + lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } + else + lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] + end + end + + # The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string. + # + # Changes '::' to '/' to convert namespaces to paths. + # + # Examples: + # "ActiveRecord".underscore # => "active_record" + # "ActiveRecord::Errors".underscore # => active_record/errors + def underscore(camel_cased_word) + camel_cased_word.to_s.gsub(/::/, '/'). + gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). + gsub(/([a-z\d])([A-Z])/,'\1_\2'). + tr("-", "_"). + downcase + end + + # Replaces underscores with dashes in the string. + # + # Example: + # "puni_puni" # => "puni-puni" + def dasherize(underscored_word) + underscored_word.gsub(/_/, '-') + end + + # Removes the module part from the expression in the string. + # + # Examples: + # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections" + # "Inflections".demodulize # => "Inflections" + def demodulize(class_name_in_module) + class_name_in_module.to_s.gsub(/^.*::/, '') + end + + # Creates a foreign key name from a class name. + # +separate_class_name_and_id_with_underscore+ sets whether + # the method should put '_' between the name and 'id'. + # + # Examples: + # "Message".foreign_key # => "message_id" + # "Message".foreign_key(false) # => "messageid" + # "Admin::Post".foreign_key # => "post_id" + def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) + underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") + end + + # Ruby 1.9 introduces an inherit argument for Module#const_get and + # #const_defined? and changes their default behavior. + if Module.method(:const_get).arity == 1 + # Tries to find a constant with the name specified in the argument string: + # + # "Module".constantize # => Module + # "Test::Unit".constantize # => Test::Unit + # + # The name is assumed to be the one of a top-level constant, no matter whether + # it starts with "::" or not. No lexical context is taken into account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # "C".constantize # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + names = camel_cased_word.split('::') + names.shift if names.empty? || names.first.empty? + + constant = Object + names.each do |name| + constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) + end + constant + end + else + def constantize(camel_cased_word) #:nodoc: + names = camel_cased_word.split('::') + names.shift if names.empty? || names.first.empty? + + constant = Object + names.each do |name| + constant = constant.const_get(name, false) || constant.const_missing(name) + end + constant + end + end + + # Turns a number into an ordinal string used to denote the position in an + # ordered sequence such as 1st, 2nd, 3rd, 4th. + # + # Examples: + # ordinalize(1) # => "1st" + # ordinalize(2) # => "2nd" + # ordinalize(1002) # => "1002nd" + # ordinalize(1003) # => "1003rd" + def ordinalize(number) + if (11..13).include?(number.to_i % 100) + "#{number}th" + else + case number.to_i % 10 + when 1; "#{number}st" + when 2; "#{number}nd" + when 3; "#{number}rd" + else "#{number}th" + end + end + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb new file mode 100644 index 0000000000..30a9072ee1 --- /dev/null +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -0,0 +1,61 @@ +# encoding: utf-8 +require 'iconv' +require 'active_support/core_ext/string/multibyte' + +module ActiveSupport + module Inflector + extend self + + # Replaces accented characters with their ascii equivalents. + def transliterate(string) + Iconv.iconv('ascii//ignore//translit', 'utf-8', string).to_s + end + + if RUBY_VERSION >= '1.9' + undef_method :transliterate + def transliterate(string) + warn "Ruby 1.9 doesn't support Unicode normalization yet" + string.dup + end + + # The iconv transliteration code doesn't function correctly + # on some platforms, but it's very fast where it does function. + elsif "foo" != (Inflector.transliterate("föö") rescue nil) + undef_method :transliterate + def transliterate(string) + string.mb_chars.normalize(:kd). # Decompose accented characters + gsub(/[^\x00-\x7F]+/, '') # Remove anything non-ASCII entirely (e.g. diacritics). + end + end + + # Replaces special characters in a string so that it may be used as part of a 'pretty' URL. + # + # ==== Examples + # + # class Person + # def to_param + # "#{id}-#{name.parameterize}" + # end + # end + # + # @person = Person.find(1) + # # => #<Person id: 1, name: "Donald E. Knuth"> + # + # <%= link_to(@person.name, person_path(@person)) %> + # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> + def parameterize(string, sep = '-') + # replace accented chars with their ascii equivalents + parameterized_string = transliterate(string) + # Turn unwanted chars into the separator + parameterized_string.gsub!(/[^a-z0-9\-_\+]+/i, sep) + unless sep.nil? || sep.empty? + re_sep = Regexp.escape(sep) + # No more than one of the separator in a row. + parameterized_string.gsub!(/#{re_sep}{2,}/, sep) + # Remove leading/trailing separator. + parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') + end + parameterized_string.downcase + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/json/backends/jsongem.rb b/activesupport/lib/active_support/json/backends/jsongem.rb index c6c17a3c4e..cfe28d7bb9 100644 --- a/activesupport/lib/active_support/json/backends/jsongem.rb +++ b/activesupport/lib/active_support/json/backends/jsongem.rb @@ -23,15 +23,18 @@ module ActiveSupport private def convert_dates_from(data) case data - when DATE_REGEX - DateTime.parse(data) - when Array - data.map! { |d| convert_dates_from(d) } - when Hash - data.each do |key, value| - data[key] = convert_dates_from(value) - end - else data + when nil + nil + when DATE_REGEX + DateTime.parse(data) + when Array + data.map! { |d| convert_dates_from(d) } + when Hash + data.each do |key, value| + data[key] = convert_dates_from(value) + end + else + data end end end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index f440d6ce58..3c15056c41 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -6,10 +6,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/object/instance_variables' require 'active_support/deprecation' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/time/conversions' -require 'active_support/time_with_zone' -require 'active_support/values/time_zone' +require 'active_support/time' # Hack to load json gem first so we can overwrite its to_json. begin diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb index b197fbc9bf..f810f53029 100644 --- a/activesupport/lib/active_support/memoizable.rb +++ b/activesupport/lib/active_support/memoizable.rb @@ -2,19 +2,6 @@ require 'active_support/core_ext/object/metaclass' require 'active_support/core_ext/module/aliasing' module ActiveSupport - module SafelyMemoizable - def safely_memoize(*symbols) - symbols.each do |symbol| - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{symbol}(*args) - memoized = @_memoized_#{symbol} || ::ActiveSupport::ConcurrentHash.new - memoized[args] ||= memoized_#{symbol}(*args) - end - RUBY - end - end - end - module Memoizable def self.memoized_ivar_for(symbol) "@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}".to_sym diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 282346b1a6..87e4b1ad33 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/bytesize' - module ActiveSupport # MessageVerifier makes it easy to generate and verify messages which are signed # to prevent tampering. diff --git a/activesupport/lib/active_support/multibyte.rb b/activesupport/lib/active_support/multibyte.rb index 6f2016a409..7e6f7d754b 100644 --- a/activesupport/lib/active_support/multibyte.rb +++ b/activesupport/lib/active_support/multibyte.rb @@ -4,6 +4,12 @@ require 'active_support/core_ext/module/attribute_accessors' module ActiveSupport #:nodoc: module Multibyte + autoload :EncodingError, 'active_support/multibyte/exceptions' + autoload :Chars, 'active_support/multibyte/chars' + autoload :UnicodeDatabase, 'active_support/multibyte/unicode_database' + autoload :Codepoint, 'active_support/multibyte/unicode_database' + autoload :UCD, 'active_support/multibyte/unicode_database' + # A list of all available normalization forms. See http://www.unicode.org/reports/tr15/tr15-29.html for more # information about normalization. NORMALIZATION_FORMS = [:c, :kc, :d, :kd] @@ -53,7 +59,4 @@ module ActiveSupport #:nodoc: end end -require 'active_support/multibyte/chars' -require 'active_support/multibyte/exceptions' -require 'active_support/multibyte/unicode_database' -require 'active_support/multibyte/utils' +require 'active_support/multibyte/utils'
\ No newline at end of file diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 579ccc124d..c7225fec06 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/behavior' module ActiveSupport #:nodoc: @@ -197,7 +198,7 @@ module ActiveSupport #:nodoc: # 'Café périferôl'.mb_chars.index('ô') #=> 12 # 'Café périferôl'.mb_chars.index(/\w/u) #=> 0 def index(needle, offset=0) - wrapped_offset = self.first(offset).wrapped_string.length + wrapped_offset = first(offset).wrapped_string.length index = @wrapped_string.index(needle, wrapped_offset) index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil end @@ -211,7 +212,7 @@ module ActiveSupport #:nodoc: # 'Café périferôl'.mb_chars.rindex(/\w/u) #=> 13 def rindex(needle, offset=nil) offset ||= length - wrapped_offset = self.first(offset).wrapped_string.length + wrapped_offset = first(offset).wrapped_string.length index = @wrapped_string.rindex(needle, wrapped_offset) index ? (self.class.u_unpack(@wrapped_string.slice(0...index)).size) : nil end @@ -321,7 +322,7 @@ module ActiveSupport #:nodoc: # Example: # 'Café'.mb_chars.reverse.to_s #=> 'éfaC' def reverse - chars(self.class.u_unpack(@wrapped_string).reverse.pack('U*')) + chars(self.class.g_unpack(@wrapped_string).reverse.flatten.pack('U*')) end # Implements Unicode-aware slice with codepoints. Slicing on one point returns the codepoints for that @@ -363,6 +364,16 @@ module ActiveSupport #:nodoc: slice end + # Limit the byte size of the string to a number of bytes without breaking characters. Usable + # when the storage for a string is limited for some reason. + # + # Example: + # s = 'こんにちは' + # s.mb_chars.limit(7) #=> "こに" + def limit(limit) + slice(0...translate_offset(limit)) + end + # Returns the codepoint of the first character in the string. # # Example: @@ -651,24 +662,20 @@ module ActiveSupport #:nodoc: end protected - + def translate_offset(byte_offset) #:nodoc: return nil if byte_offset.nil? return 0 if @wrapped_string == '' - chunk = @wrapped_string[0..byte_offset] + + if @wrapped_string.respond_to?(:force_encoding) + @wrapped_string = @wrapped_string.dup.force_encoding(Encoding::ASCII_8BIT) + end + begin - begin - chunk.unpack('U*').length - 1 - rescue ArgumentError => e - chunk = @wrapped_string[0..(byte_offset+=1)] - # Stop retrying at the end of the string - raise e unless byte_offset < chunk.length - # We damaged a character, retry - retry - end - # Catch the ArgumentError so we can throw our own - rescue ArgumentError - raise EncodingError, 'malformed UTF-8 character' + @wrapped_string[0...byte_offset].unpack('U*').length + rescue ArgumentError => e + byte_offset -= 1 + retry end end diff --git a/activesupport/lib/active_support/multibyte/utils.rb b/activesupport/lib/active_support/multibyte/utils.rb index 8e47763d39..b243df46d8 100644 --- a/activesupport/lib/active_support/multibyte/utils.rb +++ b/activesupport/lib/active_support/multibyte/utils.rb @@ -26,11 +26,11 @@ module ActiveSupport #:nodoc: else def self.verify(string) if expression = valid_character - for c in string.split(//) - return false unless expression.match(c) - end + # Splits the string on character boundaries, which are determined based on $KCODE. + string.split(//).all? { |c| expression.match(c) } + else + true end - true end end @@ -49,9 +49,8 @@ module ActiveSupport #:nodoc: else def self.clean(string) if expression = valid_character - stripped = []; for c in string.split(//) - stripped << c if expression.match(c) - end; stripped.join + # Splits the string on character boundaries, which are determined based on $KCODE. + string.split(//).grep(expression).join else string end diff --git a/activesupport/lib/active_support/new_callbacks.rb b/activesupport/lib/active_support/new_callbacks.rb deleted file mode 100644 index 2f0853d84a..0000000000 --- a/activesupport/lib/active_support/new_callbacks.rb +++ /dev/null @@ -1,563 +0,0 @@ -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/class/inheritable_attributes' -require 'active_support/core_ext/kernel/reporting' - -module ActiveSupport - # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic - # before or after an alteration of the object state. - # - # Mixing in this module allows you to define callbacks in your class. - # - # Example: - # class Storage - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # end - # - # class ConfigStorage < Storage - # set_callback :save, :before, :saving_message - # def saving_message - # puts "saving..." - # end - # - # set_callback :save, :after do |object| - # puts "saved" - # end - # - # def save - # _run_set_callback :save,s do - # puts "- save" - # end - # end - # end - # - # config = ConfigStorage.new - # config.save - # - # Output: - # saving... - # - save - # saved - # - # Callbacks from parent classes are inherited. - # - # Example: - # class Storage - # include ActiveSupport::Callbacks - # - # define_callbacks :save - # - # set_callback :save, :before, :prepare - # def prepare - # puts "preparing save" - # end - # end - # - # class ConfigStorage < Storage - # set_callback :save, :before, :saving_message - # def saving_message - # puts "saving..." - # end - # - # set_callback :save, :after do |object| - # puts "saved" - # end - # - # def save - # _run_set_callback :save,s do - # puts "- save" - # end - # end - # end - # - # config = ConfigStorage.new - # config.save - # - # Output: - # preparing save - # saving... - # - save - # saved - # - module NewCallbacks - def self.included(klass) - klass.extend ClassMethods - end - - def run_callbacks(kind, options = {}, &blk) - send("_run_#{kind}_callbacks", &blk) - end - - class Callback - @@_callback_sequence = 0 - - attr_accessor :chain, :filter, :kind, :options, :per_key, :klass - - def initialize(chain, filter, kind, options, klass) - @chain, @kind, @klass = chain, kind, klass - normalize_options!(options) - - @per_key = options.delete(:per_key) - @raw_filter, @options = filter, options - @filter = _compile_filter(filter) - @compiled_options = _compile_options(options) - @callback_id = next_id - - _compile_per_key_options - end - - def clone(chain, klass) - obj = super() - obj.chain = chain - obj.klass = klass - obj.per_key = @per_key.dup - obj.options = @options.dup - obj.per_key[:if] = @per_key[:if].dup - obj.per_key[:unless] = @per_key[:unless].dup - obj.options[:if] = @options[:if].dup - obj.options[:unless] = @options[:unless].dup - obj - end - - def normalize_options!(options) - options[:if] = Array.wrap(options[:if]) - options[:unless] = Array.wrap(options[:unless]) - - options[:per_key] ||= {} - options[:per_key][:if] = Array.wrap(options[:per_key][:if]) - options[:per_key][:unless] = Array.wrap(options[:per_key][:unless]) - end - - def name - chain.name - end - - def next_id - @@_callback_sequence += 1 - end - - def matches?(_kind, _filter) - @kind == _kind && @filter == _filter - end - - def _update_filter(filter_options, new_options) - filter_options[:if].push(new_options[:unless]) if new_options.key?(:unless) - filter_options[:unless].push(new_options[:if]) if new_options.key?(:if) - end - - def recompile!(_options, _per_key) - _update_filter(self.options, _options) - _update_filter(self.per_key, _per_key) - - @callback_id = next_id - @filter = _compile_filter(@raw_filter) - @compiled_options = _compile_options(@options) - _compile_per_key_options - end - - def _compile_per_key_options - key_options = _compile_options(@per_key) - - @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def _one_time_conditions_valid_#{@callback_id}? - true #{key_options[0]} - end - RUBY_EVAL - end - - # This will supply contents for before and around filters, and no - # contents for after filters (for the forward pass). - def start(key=nil, object=nil) - return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") - - # options[0] is the compiled form of supplied conditions - # options[1] is the "end" for the conditional - # - if @kind == :before || @kind == :around - if @kind == :before - # if condition # before_save :filter_name, :if => :condition - # filter_name - # end - filter = <<-RUBY_EVAL - unless halted - result = #{@filter} - halted = (#{chain.config[:terminator]}) - end - RUBY_EVAL - - [@compiled_options[0], filter, @compiled_options[1]].compact.join("\n") - else - # Compile around filters with conditions into proxy methods - # that contain the conditions. - # - # For `around_save :filter_name, :if => :condition': - # - # def _conditional_callback_save_17 - # if condition - # filter_name do - # yield self - # end - # else - # yield self - # end - # end - # - name = "_conditional_callback_#{@kind}_#{next_id}" - txt, line = <<-RUBY_EVAL, __LINE__ + 1 - def #{name}(halted) - #{@compiled_options[0] || "if true"} && !halted - #{@filter} do - yield self - end - else - yield self - end - end - RUBY_EVAL - @klass.class_eval(txt, __FILE__, line) - "#{name}(halted) do" - end - end - end - - # This will supply contents for around and after filters, but not - # before filters (for the backward pass). - def end(key=nil, object=nil) - return if key && !object.send("_one_time_conditions_valid_#{@callback_id}?") - - if @kind == :around || @kind == :after - # if condition # after_save :filter_name, :if => :condition - # filter_name - # end - if @kind == :after - [@compiled_options[0], @filter, @compiled_options[1]].compact.join("\n") - else - "end" - end - end - end - - private - - # Options support the same options as filters themselves (and support - # symbols, string, procs, and objects), so compile a conditional - # expression based on the options - def _compile_options(options) - return [] if options[:if].empty? && options[:unless].empty? - - conditions = [] - - unless options[:if].empty? - conditions << Array.wrap(_compile_filter(options[:if])) - end - - unless options[:unless].empty? - conditions << Array.wrap(_compile_filter(options[:unless])).map {|f| "!#{f}"} - end - - ["if #{conditions.flatten.join(" && ")}", "end"] - end - - # Filters support: - # - # Arrays:: Used in conditions. This is used to specify - # multiple conditions. Used internally to - # merge conditions from skip_* filters - # Symbols:: A method to call - # Strings:: Some content to evaluate - # Procs:: A proc to call with the object - # Objects:: An object with a before_foo method on it to call - # - # All of these objects are compiled into methods and handled - # the same after this point: - # - # Arrays:: Merged together into a single filter - # Symbols:: Already methods - # Strings:: class_eval'ed into methods - # Procs:: define_method'ed into methods - # Objects:: - # a method is created that calls the before_foo method - # on the object. - # - def _compile_filter(filter) - method_name = "_callback_#{@kind}_#{next_id}" - case filter - when Array - filter.map {|f| _compile_filter(f)} - when Symbol - filter - when String - "(#{filter})" - when Proc - @klass.send(:define_method, method_name, &filter) - return method_name if filter.arity <= 0 - - method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") - else - @klass.send(:define_method, "#{method_name}_object") { filter } - - _normalize_legacy_filter(kind, filter) - scopes = Array.wrap(chain.config[:scope]) - method_to_call = scopes.map{ |s| s.is_a?(Symbol) ? send(s) : s }.join("_") - - @klass.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - def #{method_name}(&blk) - #{method_name}_object.send(:#{method_to_call}, self, &blk) - end - RUBY_EVAL - - method_name - end - end - - def _normalize_legacy_filter(kind, filter) - if !filter.respond_to?(kind) && filter.respond_to?(:filter) - filter.metaclass.class_eval( - "def #{kind}(context, &block) filter(context, &block) end", - __FILE__, __LINE__ - 1) - elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around - def filter.around(context) - should_continue = before(context) - yield if should_continue - after(context) - end - end - end - end - - # An Array with a compile method - class CallbackChain < Array - attr_reader :name, :config - - def initialize(name, config) - @name = name - @config = { - :terminator => "false", - :rescuable => false, - :scope => [ :kind ] - }.merge(config) - end - - def compile(key=nil, object=nil) - method = [] - method << "value = nil" - method << "halted = false" - - each do |callback| - method << callback.start(key, object) - end - - if config[:rescuable] - method << "rescued_error = nil" - method << "begin" - end - - method << "value = yield if block_given? && !halted" - - if config[:rescuable] - method << "rescue Exception => e" - method << "rescued_error = e" - method << "end" - end - - reverse_each do |callback| - method << callback.end(key, object) - end - - method << "raise rescued_error if rescued_error" if config[:rescuable] - method << "halted ? false : (block_given? ? value : true)" - method.compact.join("\n") - end - - def clone(klass) - chain = CallbackChain.new(@name, @config.dup) - callbacks = map { |c| c.clone(chain, klass) } - chain.push(*callbacks) - end - end - - module ClassMethods - # Make the _run_set_callback :save method. The generated method takes - # a block that it'll yield to. It'll call the before and around filters - # in order, yield the block, and then run the after filters. - # - # _run_set_callback :save do - # save - # end - # - # The _run_set_callback :save method can optionally take a key, which - # will be used to compile an optimized callback method for each - # key. See #define_callbacks for more information. - # - def __define_runner(symbol) #:nodoc: - body = send("_#{symbol}_callbacks").compile(nil) - - body, line = <<-RUBY_EVAL, __LINE__ - def _run_#{symbol}_callbacks(key = nil, &blk) - if key - name = "_run__\#{self.class.name.hash.abs}__#{symbol}__\#{key.hash.abs}__callbacks" - - unless respond_to?(name) - self.class.__create_keyed_callback(name, :#{symbol}, self, &blk) - end - - send(name, &blk) - else - #{body} - end - end - RUBY_EVAL - - silence_warnings do - undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks") - class_eval body, __FILE__, line - end - end - - # This is called the first time a callback is called with a particular - # key. It creates a new callback method for the key, calculating - # which callbacks can be omitted because of per_key conditions. - # - def __create_keyed_callback(name, kind, object, &blk) #:nodoc: - @_keyed_callbacks ||= {} - @_keyed_callbacks[name] ||= begin - str = send("_#{kind}_callbacks").compile(name, object) - class_eval "def #{name}() #{str} end", __FILE__, __LINE__ - true - end - end - - # This is used internally to append, prepend and skip callbacks to the - # CallbackChain. - # - def __update_callbacks(name, filters = [], block = nil) #:nodoc: - type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before - options = filters.last.is_a?(Hash) ? filters.pop : {} - filters.unshift(block) if block - - chain = send("_#{name}_callbacks") - yield chain, type, filters, options if block_given? - - __define_runner(name) - end - - # Set callbacks for a previously defined callback. - # - # Syntax: - # set_callback :save, :before, :before_meth - # set_callback :save, :after, :after_meth, :if => :condition - # set_callback :save, :around, lambda { |r| stuff; yield; stuff } - # - # It also updates the _run_<name>_callbacks method, which is the public - # API to run the callbacks. Use skip_callback to skip any defined one. - # - # When creating or skipping callbacks, you can specify conditions that - # are always the same for a given key. For instance, in ActionPack, - # we convert :only and :except conditions into per-key conditions. - # - # before_filter :authenticate, :except => "index" - # becomes - # dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}} - # - # Per-Key conditions are evaluated only once per use of a given key. - # In the case of the above example, you would do: - # - # _run_dispatch_callbacks(action_name) { ... dispatch stuff ... } - # - # In that case, each action_name would get its own compiled callback - # method that took into consideration the per_key conditions. This - # is a speed improvement for ActionPack. - # - def set_callback(name, *filters, &block) - __update_callbacks(name, filters, block) do |chain, type, filters, options| - filters.map! do |filter| - chain.delete_if {|c| c.matches?(type, filter) } - Callback.new(chain, filter, type, options.dup, self) - end - - options[:prepend] ? chain.unshift(*filters) : chain.push(*filters) - end - end - - # Skip a previously defined callback for a given type. - # - def skip_callback(name, *filters, &block) - __update_callbacks(name, filters, block) do |chain, type, filters, options| - chain = send("_#{name}_callbacks=", chain.clone(self)) - - filters.each do |filter| - filter = chain.find {|c| c.matches?(type, filter) } - - if filter && options.any? - filter.recompile!(options, options[:per_key] || {}) - else - chain.delete(filter) - end - end - end - end - - # Reset callbacks for a given type. - # - def reset_callbacks(symbol) - send("_#{symbol}_callbacks").clear - __define_runner(symbol) - end - - # Define callbacks types. - # - # ==== Example - # - # define_callbacks :validate - # - # ==== Options - # - # * <tt>:terminator</tt> - Indicates when a before filter is considered - # to be halted. - # - # define_callbacks :validate, :terminator => "result == false" - # - # In the example above, if any before validate callbacks returns false, - # other callbacks are not executed. Defaults to "false". - # - # * <tt>:rescuable</tt> - By default, after filters are not executed if - # the given block or an before_filter raises an error. Supply :rescuable => true - # to change this behavior. - # - # * <tt>:scope</tt> - Show which methods should be executed when a class - # is giben as callback: - # - # define_callbacks :filters, :scope => [ :kind ] - # - # When a class is given: - # - # before_filter MyFilter - # - # It will call the type of the filter in the given class, which in this - # case, is "before". - # - # If, for instance, you supply the given scope: - # - # define_callbacks :validate, :scope => [ :kind, :name ] - # - # It will call "#{kind}_#{name}" in the given class. So "before_validate" - # will be called in the class below: - # - # before_validate MyValidation - # - # Defaults to :kind. - # - def define_callbacks(*symbols) - config = symbols.last.is_a?(Hash) ? symbols.pop : {} - symbols.each do |symbol| - extlib_inheritable_accessor("_#{symbol}_callbacks") do - CallbackChain.new(symbol, config) - end - - __define_runner(symbol) - end - end - end - end -end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb new file mode 100644 index 0000000000..7a9f76b26a --- /dev/null +++ b/activesupport/lib/active_support/notifications.rb @@ -0,0 +1,189 @@ +require 'thread' +require 'active_support/core_ext/module/delegation' +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/secure_random' + +module ActiveSupport + # Notifications provides an instrumentation API for Ruby. To instrument an + # action in Ruby you just need to do: + # + # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # render :text => "Foo" + # end + # + # You can consume those events and the information they provide by registering + # a subscriber. For instance, let's store all instrumented events in an array: + # + # @events = [] + # + # ActiveSupport::Notifications.subscribe do |*args| + # @events << ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument(:render, :extra => :information) do + # render :text => "Foo" + # end + # + # event = @events.first + # event.name #=> :render + # event.duration #=> 10 (in miliseconds) + # event.result #=> "Foo" + # event.payload #=> { :extra => :information } + # + # When subscribing to Notifications, you can pass a pattern, to only consume + # events that match the pattern: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @render_events << event + # end + # + # Notifications ships with a queue implementation that consumes and publish events + # to subscribers in a thread. You can use any queue implementation you want. + # + module Notifications + mattr_accessor :queue + + class << self + delegate :instrument, :transaction_id, :transaction, :to => :instrumenter + + def instrumenter + Thread.current[:notifications_instrumeter] ||= Instrumenter.new(publisher) + end + + def publisher + @publisher ||= Publisher.new(queue) + end + + def subscribe(pattern=nil, &block) + Subscriber.new(queue).bind(pattern).subscribe(&block) + end + end + + class Instrumenter + def initialize(publisher) + @publisher = publisher + @id = random_id + end + + def transaction + @id, old_id = random_id, @id + yield + ensure + @id = old_id + end + + def transaction_id + @id + end + + def instrument(name, payload={}) + time = Time.now + result = yield if block_given? + ensure + @publisher.publish(name, time, Time.now, result, @id, payload) + end + + private + def random_id + SecureRandom.hex(10) + end + end + + class Publisher + def initialize(queue) + @queue = queue + end + + def publish(*args) + @queue.publish(*args) + end + end + + class Subscriber + def initialize(queue) + @queue = queue + end + + def bind(pattern) + @pattern = pattern + self + end + + def subscribe + @queue.subscribe(@pattern) do |*args| + yield(*args) + end + end + end + + class Event + attr_reader :name, :time, :end, :transaction_id, :result, :payload + + def initialize(name, start, ending, result, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start + @transaction_id = transaction_id + @end = ending + @result = result + end + + def duration + @duration ||= 1000.0 * (@end - @time) + end + + def parent_of?(event) + start = (self.time - event.time) * 1000 + start <= 0 && (start + duration >= event.duration) + end + end + + # This is a default queue implementation that ships with Notifications. It + # consumes events in a thread and publish them to all registered subscribers. + # + class LittleFanout + def initialize + @listeners = [] + end + + def publish(*args) + @listeners.each { |l| l.publish(*args) } + end + + def subscribe(pattern=nil, &block) + @listeners << Listener.new(pattern, &block) + end + + def drained? + @listeners.all? &:drained? + end + + class Listener + def initialize(pattern, &block) + @pattern = pattern + @subscriber = block + @queue = Queue.new + Thread.new { consume } + end + + def publish(name, *args) + if !@pattern || @pattern === name.to_s + @queue << args.unshift(name) + end + end + + def consume + while args = @queue.shift + @subscriber.call(*args) + end + end + + def drained? + @queue.size.zero? + end + end + end + end + + Notifications.queue = Notifications::LittleFanout.new +end diff --git a/activesupport/lib/active_support/orchestra.rb b/activesupport/lib/active_support/orchestra.rb deleted file mode 100644 index efe30669d8..0000000000 --- a/activesupport/lib/active_support/orchestra.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'thread' - -module ActiveSupport - # Orchestra provides an instrumentation API for Ruby. To instrument an action - # in Ruby you just need to: - # - # ActiveSupport::Orchestra.instrument(:render, :extra => :information) do - # render :text => "Foo" - # end - # - # Those actions are consumed by listeners. A listener is anything that responds - # to push. You can even register an array: - # - # @listener = [] - # ActiveSupport::Orchestra.register @listener - # - # ActiveSupport::Orchestra.instrument(:render, :extra => :information) do - # render :text => "Foo" - # end - # - # event #=> ActiveSupport::Orchestra::Event - # event.name #=> :render - # event.duration #=> 10 (in miliseconds) - # event.result #=> "Foo" - # event.payload #=> { :extra => :information } - # - # Orchestra ships with a default listener implementation which puts events in - # a stream and consume them in a Thread. This implementation is thread safe - # and is available at ActiveSupport::Orchestra::Listener. - # - module Orchestra - @stacked_events = Hash.new { |h,k| h[k] = [] } - @listeners = [] - - def self.instrument(name, payload=nil) - stack = @stacked_events[Thread.current.object_id] - event = Event.new(name, stack.last, payload) - stack << event - event.result = yield - event - ensure - event.finish! - stack.delete(event) - @listeners.each { |s| s.push(event) } - end - - def self.register(listener) - @listeners << listener - end - - def self.unregister(listener) - @listeners.delete(listener) - end - - class Event - attr_reader :name, :time, :duration, :parent, :thread_id, :payload - attr_accessor :result - - def initialize(name, parent=nil, payload=nil) - @name = name - @time = Time.now - @thread_id = Thread.current.object_id - @parent = parent - @payload = payload - end - - def finish! - @duration = 1000 * (Time.now.to_f - @time.to_f) - end - end - - class Listener - attr_reader :mutex, :signaler, :thread - - def initialize - @mutex, @signaler = Mutex.new, ConditionVariable.new - @stream = [] - @thread = Thread.new do - loop do - (event = @stream.shift) ? consume(event) : wait - end - end - end - - def wait - @mutex.synchronize do - @signaler.wait(@mutex) - end - end - - def push(event) - @mutex.synchronize do - @stream.push(event) - @signaler.broadcast - end - end - - def consume(event) - raise NotImplementedError - end - end - end -end diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb index 4324e40cbb..b492648610 100644 --- a/activesupport/lib/active_support/ordered_hash.rb +++ b/activesupport/lib/active_support/ordered_hash.rb @@ -120,6 +120,13 @@ module ActiveSupport dup.merge!(other_hash) end + # When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not. + def replace(other) + super + @keys = other.keys + self + end + def inspect "#<OrderedHash #{super}>" end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index a7258c870a..f0119f5994 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -4,11 +4,11 @@ require 'active_support/core_ext/proc' module ActiveSupport # Rescuable module adds support for easier exception handling. module Rescuable - def self.included(base) # :nodoc: - base.class_inheritable_accessor :rescue_handlers - base.rescue_handlers = [] + extend Concern - base.extend(ClassMethods) + included do + class_inheritable_accessor :rescue_handlers + self.rescue_handlers = [] end module ClassMethods @@ -81,7 +81,7 @@ module ActiveSupport def handler_for_rescue(exception) # We go from right to left because pairs are pushed onto rescue_handlers # as rescue_from declarations are found. - _, rescuer = Array(rescue_handlers).reverse.detect do |klass_name, handler| + _, rescuer = rescue_handlers.reverse.detect do |klass_name, handler| # The purpose of allowing strings in rescue_from is to support the # declaration of handler associations for exception classes whose # definition is yet unknown. diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index 37c57c485a..f811239077 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -4,21 +4,15 @@ # Date next_year, next_month # DateTime to_date, to_datetime, xmlschema # Enumerable group_by, each_with_object, none? -# Integer even?, odd? -# Object tap # Process Process.daemon # REXML security fix # String ord -# Symbol to_proc # Time to_date, to_time, to_datetime require 'active_support' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/enumerable' -require 'active_support/core_ext/integer/even_odd' -require 'active_support/core_ext/object/tap' require 'active_support/core_ext/process/daemon' require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/rexml' -require 'active_support/core_ext/symbol/to_proc' require 'active_support/core_ext/time/conversions' diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index bec303f6ab..c75b59c284 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/load_error' - module ActiveSupport module Testing class ProxyTestResult diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index b59ac79e7b..0e998d2dbe 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -1,11 +1,9 @@ -require 'active_support/callbacks' - module ActiveSupport module Testing module SetupAndTeardown def self.included(base) base.class_eval do - include ActiveSupport::Callbacks + include ActiveSupport::DeprecatedCallbacks define_callbacks :setup, :teardown if defined?(MiniTest::Assertions) && TestCase < MiniTest::Assertions @@ -23,18 +21,18 @@ module ActiveSupport run_callbacks :setup result = super rescue Exception => e - result = runner.puke(self.class, self.name, e) + result = runner.puke(self.class, method_name, e) ensure begin run_callbacks :teardown, :enumerator => :reverse_each rescue Exception => e - result = runner.puke(self.class, self.name, e) + result = runner.puke(self.class, method_name, e) end end result end end - + module ForClassicTestUnit # For compatibility with Ruby < 1.8.6 PASSTHROUGH_EXCEPTIONS = Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS rescue [NoMemoryError, SignalException, Interrupt, SystemExit] diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index d36a683601..0f421421d0 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -1,7 +1,4 @@ require 'active_support' -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'active_support/core_ext/date_time' module ActiveSupport autoload :Duration, 'active_support/duration' @@ -12,3 +9,26 @@ module ActiveSupport [Duration, TimeWithZone, TimeZone] end end + +require 'date' +require 'time' + +require 'active_support/core_ext/time/publicize_conversion_methods' +require 'active_support/core_ext/time/marshal_with_utc_flag' +require 'active_support/core_ext/time/acts_like' +require 'active_support/core_ext/time/calculations' +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/time/zones' + +require 'active_support/core_ext/date/acts_like' +require 'active_support/core_ext/date/freeze' +require 'active_support/core_ext/date/calculations' +require 'active_support/core_ext/date/conversions' + +require 'active_support/core_ext/date_time/acts_like' +require 'active_support/core_ext/date_time/calculations' +require 'active_support/core_ext/date_time/conversions' +require 'active_support/core_ext/date_time/zones' + +require 'active_support/core_ext/integer/time' +require 'active_support/core_ext/numeric/time' diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4907fae9d6..8304f6c434 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,9 +1,4 @@ -require 'active_support/duration' -require 'active_support/core_ext/numeric/time' -require 'active_support/core_ext/integer/time' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/date_time/conversions' +require "active_support/values/time_zone" module ActiveSupport # A Time-like class that can represent a time in any time zone. Necessary because standard Ruby Time instances are diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 564528bfe2..cbb8e890ae 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,6 +1,4 @@ -require 'active_support/core_ext/time' -require 'active_support/core_ext/date' -require 'active_support/core_ext/date_time' +require 'active_support/core_ext/object/blank' # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. It allows us to do the following: # @@ -63,7 +61,7 @@ module ActiveSupport "Azores" => "Atlantic/Azores", "Cape Verde Is." => "Atlantic/Cape_Verde", "Dublin" => "Europe/Dublin", - "Edinburgh" => "Europe/Dublin", + "Edinburgh" => "Europe/London", "Lisbon" => "Europe/Lisbon", "London" => "Europe/London", "Casablanca" => "Africa/Casablanca", diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index 9f464c8246..eb5080888c 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -1,16 +1,19 @@ +require 'pathname' + def ActiveSupport.requirable?(file) $LOAD_PATH.any? { |p| Dir.glob("#{p}/#{file}.*").any? } end -[%w(builder 2.1.2), %w(i18n 0.1.3), %w(memcache-client 1.7.5), %w(tzinfo 0.3.13)].each do |lib, version| +[%w(builder 2.1.2), %w(i18n 0.1.3), %w(memcache-client 1.7.5), %w(tzinfo 0.3.15)].each do |lib, version| # If the lib is not already requirable unless ActiveSupport.requirable? lib # Try to activate a gem ~> satisfying the requested version first. begin - gem lib, "~> #{version}" + gem lib, ">= #{version}" # Use the vendored lib if the gem's missing or we aren't using RubyGems. rescue LoadError, NoMethodError - $LOAD_PATH.unshift File.expand_path("#{File.dirname(__FILE__)}/vendor/#{lib}-#{version}/lib") + # There could be symlinks + $LOAD_PATH.unshift Pathname.new(__FILE__).dirname.join("vendor/#{lib}-#{version}/lib").realpath.to_s end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb deleted file mode 100644 index 8f4dd31dbb..0000000000 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb +++ /dev/null @@ -1,166 +0,0 @@ -require 'tzinfo/timezone_definition' - -module TZInfo - module Definitions - module America - module Argentina - module Buenos_Aires - include TimezoneDefinition - - timezone 'America/Argentina/Buenos_Aires' do |tz| - tz.offset :o0, -14028, 0, :LMT - tz.offset :o1, -15408, 0, :CMT - tz.offset :o2, -14400, 0, :ART - tz.offset :o3, -14400, 3600, :ARST - tz.offset :o4, -10800, 0, :ART - tz.offset :o5, -10800, 3600, :ARST - - tz.transition 1894, 10, :o1, 17374555169, 7200 - tz.transition 1920, 5, :o2, 1453467407, 600 - tz.transition 1930, 12, :o3, 7278935, 3 - tz.transition 1931, 4, :o2, 19411461, 8 - tz.transition 1931, 10, :o3, 7279889, 3 - tz.transition 1932, 3, :o2, 19414141, 8 - tz.transition 1932, 11, :o3, 7281038, 3 - tz.transition 1933, 3, :o2, 19417061, 8 - tz.transition 1933, 11, :o3, 7282133, 3 - tz.transition 1934, 3, :o2, 19419981, 8 - tz.transition 1934, 11, :o3, 7283228, 3 - tz.transition 1935, 3, :o2, 19422901, 8 - tz.transition 1935, 11, :o3, 7284323, 3 - tz.transition 1936, 3, :o2, 19425829, 8 - tz.transition 1936, 11, :o3, 7285421, 3 - tz.transition 1937, 3, :o2, 19428749, 8 - tz.transition 1937, 11, :o3, 7286516, 3 - tz.transition 1938, 3, :o2, 19431669, 8 - tz.transition 1938, 11, :o3, 7287611, 3 - tz.transition 1939, 3, :o2, 19434589, 8 - tz.transition 1939, 11, :o3, 7288706, 3 - tz.transition 1940, 3, :o2, 19437517, 8 - tz.transition 1940, 7, :o3, 7289435, 3 - tz.transition 1941, 6, :o2, 19441285, 8 - tz.transition 1941, 10, :o3, 7290848, 3 - tz.transition 1943, 8, :o2, 19447501, 8 - tz.transition 1943, 10, :o3, 7293038, 3 - tz.transition 1946, 3, :o2, 19455045, 8 - tz.transition 1946, 10, :o3, 7296284, 3 - tz.transition 1963, 10, :o2, 19506429, 8 - tz.transition 1963, 12, :o3, 7315136, 3 - tz.transition 1964, 3, :o2, 19507645, 8 - tz.transition 1964, 10, :o3, 7316051, 3 - tz.transition 1965, 3, :o2, 19510565, 8 - tz.transition 1965, 10, :o3, 7317146, 3 - tz.transition 1966, 3, :o2, 19513485, 8 - tz.transition 1966, 10, :o3, 7318241, 3 - tz.transition 1967, 4, :o2, 19516661, 8 - tz.transition 1967, 10, :o3, 7319294, 3 - tz.transition 1968, 4, :o2, 19519629, 8 - tz.transition 1968, 10, :o3, 7320407, 3 - tz.transition 1969, 4, :o2, 19522541, 8 - tz.transition 1969, 10, :o4, 7321499, 3 - tz.transition 1974, 1, :o5, 128142000 - tz.transition 1974, 5, :o4, 136605600 - tz.transition 1988, 12, :o5, 596948400 - tz.transition 1989, 3, :o4, 605066400 - tz.transition 1989, 10, :o5, 624423600 - tz.transition 1990, 3, :o4, 636516000 - tz.transition 1990, 10, :o5, 656478000 - tz.transition 1991, 3, :o4, 667965600 - tz.transition 1991, 10, :o5, 687927600 - tz.transition 1992, 3, :o4, 699415200 - tz.transition 1992, 10, :o5, 719377200 - tz.transition 1993, 3, :o4, 731469600 - tz.transition 1999, 10, :o3, 938919600 - tz.transition 2000, 3, :o4, 952052400 - tz.transition 2007, 12, :o5, 1198983600 - tz.transition 2008, 3, :o4, 1205632800 - tz.transition 2008, 10, :o5, 1224385200 - tz.transition 2009, 3, :o4, 1237082400 - tz.transition 2009, 10, :o5, 1255834800 - tz.transition 2010, 3, :o4, 1269136800 - tz.transition 2010, 10, :o5, 1287284400 - tz.transition 2011, 3, :o4, 1300586400 - tz.transition 2011, 10, :o5, 1318734000 - tz.transition 2012, 3, :o4, 1332036000 - tz.transition 2012, 10, :o5, 1350788400 - tz.transition 2013, 3, :o4, 1363485600 - tz.transition 2013, 10, :o5, 1382238000 - tz.transition 2014, 3, :o4, 1394935200 - tz.transition 2014, 10, :o5, 1413687600 - tz.transition 2015, 3, :o4, 1426384800 - tz.transition 2015, 10, :o5, 1445137200 - tz.transition 2016, 3, :o4, 1458439200 - tz.transition 2016, 10, :o5, 1476586800 - tz.transition 2017, 3, :o4, 1489888800 - tz.transition 2017, 10, :o5, 1508036400 - tz.transition 2018, 3, :o4, 1521338400 - tz.transition 2018, 10, :o5, 1540090800 - tz.transition 2019, 3, :o4, 1552788000 - tz.transition 2019, 10, :o5, 1571540400 - tz.transition 2020, 3, :o4, 1584237600 - tz.transition 2020, 10, :o5, 1602990000 - tz.transition 2021, 3, :o4, 1616292000 - tz.transition 2021, 10, :o5, 1634439600 - tz.transition 2022, 3, :o4, 1647741600 - tz.transition 2022, 10, :o5, 1665889200 - tz.transition 2023, 3, :o4, 1679191200 - tz.transition 2023, 10, :o5, 1697338800 - tz.transition 2024, 3, :o4, 1710640800 - tz.transition 2024, 10, :o5, 1729393200 - tz.transition 2025, 3, :o4, 1742090400 - tz.transition 2025, 10, :o5, 1760842800 - tz.transition 2026, 3, :o4, 1773540000 - tz.transition 2026, 10, :o5, 1792292400 - tz.transition 2027, 3, :o4, 1805594400 - tz.transition 2027, 10, :o5, 1823742000 - tz.transition 2028, 3, :o4, 1837044000 - tz.transition 2028, 10, :o5, 1855191600 - tz.transition 2029, 3, :o4, 1868493600 - tz.transition 2029, 10, :o5, 1887246000 - tz.transition 2030, 3, :o4, 1899943200 - tz.transition 2030, 10, :o5, 1918695600 - tz.transition 2031, 3, :o4, 1931392800 - tz.transition 2031, 10, :o5, 1950145200 - tz.transition 2032, 3, :o4, 1963447200 - tz.transition 2032, 10, :o5, 1981594800 - tz.transition 2033, 3, :o4, 1994896800 - tz.transition 2033, 10, :o5, 2013044400 - tz.transition 2034, 3, :o4, 2026346400 - tz.transition 2034, 10, :o5, 2044494000 - tz.transition 2035, 3, :o4, 2057796000 - tz.transition 2035, 10, :o5, 2076548400 - tz.transition 2036, 3, :o4, 2089245600 - tz.transition 2036, 10, :o5, 2107998000 - tz.transition 2037, 3, :o4, 2120695200 - tz.transition 2037, 10, :o5, 2139447600 - tz.transition 2038, 3, :o4, 29586043, 12 - tz.transition 2038, 10, :o5, 19725709, 8 - tz.transition 2039, 3, :o4, 29590411, 12 - tz.transition 2039, 10, :o5, 19728621, 8 - tz.transition 2040, 3, :o4, 29594779, 12 - tz.transition 2040, 10, :o5, 19731589, 8 - tz.transition 2041, 3, :o4, 29599147, 12 - tz.transition 2041, 10, :o5, 19734501, 8 - tz.transition 2042, 3, :o4, 29603515, 12 - tz.transition 2042, 10, :o5, 19737413, 8 - tz.transition 2043, 3, :o4, 29607883, 12 - tz.transition 2043, 10, :o5, 19740325, 8 - tz.transition 2044, 3, :o4, 29612335, 12 - tz.transition 2044, 10, :o5, 19743237, 8 - tz.transition 2045, 3, :o4, 29616703, 12 - tz.transition 2045, 10, :o5, 19746149, 8 - tz.transition 2046, 3, :o4, 29621071, 12 - tz.transition 2046, 10, :o5, 19749117, 8 - tz.transition 2047, 3, :o4, 29625439, 12 - tz.transition 2047, 10, :o5, 19752029, 8 - tz.transition 2048, 3, :o4, 29629807, 12 - tz.transition 2048, 10, :o5, 19754941, 8 - tz.transition 2049, 3, :o4, 29634259, 12 - tz.transition 2049, 10, :o5, 19757853, 8 - tz.transition 2050, 3, :o4, 29638627, 12 - end - end - end - end - end -end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Karachi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Karachi.rb deleted file mode 100644 index 4f187b4ad4..0000000000 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Karachi.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'tzinfo/timezone_definition' - -module TZInfo - module Definitions - module Asia - module Karachi - include TimezoneDefinition - - timezone 'Asia/Karachi' do |tz| - tz.offset :o0, 16092, 0, :LMT - tz.offset :o1, 19800, 0, :IST - tz.offset :o2, 19800, 3600, :IST - tz.offset :o3, 18000, 0, :KART - tz.offset :o4, 18000, 0, :PKT - tz.offset :o5, 18000, 3600, :PKST - - tz.transition 1906, 12, :o1, 1934061051, 800 - tz.transition 1942, 8, :o2, 116668957, 48 - tz.transition 1945, 10, :o1, 116723675, 48 - tz.transition 1951, 9, :o3, 116828125, 48 - tz.transition 1971, 3, :o4, 38775600 - tz.transition 2002, 4, :o5, 1018119660 - tz.transition 2002, 10, :o4, 1033840860 - tz.transition 2008, 5, :o5, 1212260400 - tz.transition 2008, 10, :o4, 1225476000 - tz.transition 2009, 4, :o5, 1239735600 - tz.transition 2009, 10, :o4, 1257012000 - end - end - end - end -end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo.rb index c8bdbeec5d..c8bdbeec5d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone.rb index 5eccbdf0db..5eccbdf0db 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone_info.rb index a45d94554b..a45d94554b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/data_timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/data_timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Algiers.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Algiers.rb index 8c5f25577f..8c5f25577f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Algiers.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Algiers.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Cairo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Cairo.rb index 6e6daf3522..b7ed8e8244 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Cairo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Cairo.rb @@ -129,89 +129,89 @@ module TZInfo tz.transition 2008, 4, :o2, 1209074400 tz.transition 2008, 8, :o1, 1219957200 tz.transition 2009, 4, :o2, 1240524000 - tz.transition 2009, 8, :o1, 1251406800 + tz.transition 2009, 8, :o1, 1250802000 tz.transition 2010, 4, :o2, 1272578400 - tz.transition 2010, 8, :o1, 1282856400 + tz.transition 2010, 9, :o1, 1285880400 tz.transition 2011, 4, :o2, 1304028000 - tz.transition 2011, 8, :o1, 1314306000 + tz.transition 2011, 9, :o1, 1317330000 tz.transition 2012, 4, :o2, 1335477600 - tz.transition 2012, 8, :o1, 1346360400 + tz.transition 2012, 9, :o1, 1348779600 tz.transition 2013, 4, :o2, 1366927200 - tz.transition 2013, 8, :o1, 1377810000 + tz.transition 2013, 9, :o1, 1380229200 tz.transition 2014, 4, :o2, 1398376800 - tz.transition 2014, 8, :o1, 1409259600 + tz.transition 2014, 9, :o1, 1411678800 tz.transition 2015, 4, :o2, 1429826400 - tz.transition 2015, 8, :o1, 1440709200 + tz.transition 2015, 9, :o1, 1443128400 tz.transition 2016, 4, :o2, 1461880800 - tz.transition 2016, 8, :o1, 1472158800 + tz.transition 2016, 9, :o1, 1475182800 tz.transition 2017, 4, :o2, 1493330400 - tz.transition 2017, 8, :o1, 1504213200 + tz.transition 2017, 9, :o1, 1506632400 tz.transition 2018, 4, :o2, 1524780000 - tz.transition 2018, 8, :o1, 1535662800 + tz.transition 2018, 9, :o1, 1538082000 tz.transition 2019, 4, :o2, 1556229600 - tz.transition 2019, 8, :o1, 1567112400 + tz.transition 2019, 9, :o1, 1569531600 tz.transition 2020, 4, :o2, 1587679200 - tz.transition 2020, 8, :o1, 1598562000 + tz.transition 2020, 9, :o1, 1600981200 tz.transition 2021, 4, :o2, 1619733600 - tz.transition 2021, 8, :o1, 1630011600 + tz.transition 2021, 9, :o1, 1633035600 tz.transition 2022, 4, :o2, 1651183200 - tz.transition 2022, 8, :o1, 1661461200 + tz.transition 2022, 9, :o1, 1664485200 tz.transition 2023, 4, :o2, 1682632800 - tz.transition 2023, 8, :o1, 1693515600 + tz.transition 2023, 9, :o1, 1695934800 tz.transition 2024, 4, :o2, 1714082400 - tz.transition 2024, 8, :o1, 1724965200 + tz.transition 2024, 9, :o1, 1727384400 tz.transition 2025, 4, :o2, 1745532000 - tz.transition 2025, 8, :o1, 1756414800 + tz.transition 2025, 9, :o1, 1758834000 tz.transition 2026, 4, :o2, 1776981600 - tz.transition 2026, 8, :o1, 1787864400 + tz.transition 2026, 9, :o1, 1790283600 tz.transition 2027, 4, :o2, 1809036000 - tz.transition 2027, 8, :o1, 1819314000 + tz.transition 2027, 9, :o1, 1822338000 tz.transition 2028, 4, :o2, 1840485600 - tz.transition 2028, 8, :o1, 1851368400 + tz.transition 2028, 9, :o1, 1853787600 tz.transition 2029, 4, :o2, 1871935200 - tz.transition 2029, 8, :o1, 1882818000 + tz.transition 2029, 9, :o1, 1885237200 tz.transition 2030, 4, :o2, 1903384800 - tz.transition 2030, 8, :o1, 1914267600 + tz.transition 2030, 9, :o1, 1916686800 tz.transition 2031, 4, :o2, 1934834400 - tz.transition 2031, 8, :o1, 1945717200 + tz.transition 2031, 9, :o1, 1948136400 tz.transition 2032, 4, :o2, 1966888800 - tz.transition 2032, 8, :o1, 1977166800 + tz.transition 2032, 9, :o1, 1980190800 tz.transition 2033, 4, :o2, 1998338400 - tz.transition 2033, 8, :o1, 2008616400 + tz.transition 2033, 9, :o1, 2011640400 tz.transition 2034, 4, :o2, 2029788000 - tz.transition 2034, 8, :o1, 2040670800 + tz.transition 2034, 9, :o1, 2043090000 tz.transition 2035, 4, :o2, 2061237600 - tz.transition 2035, 8, :o1, 2072120400 + tz.transition 2035, 9, :o1, 2074539600 tz.transition 2036, 4, :o2, 2092687200 - tz.transition 2036, 8, :o1, 2103570000 + tz.transition 2036, 9, :o1, 2105989200 tz.transition 2037, 4, :o2, 2124136800 - tz.transition 2037, 8, :o1, 2135019600 + tz.transition 2037, 9, :o1, 2137438800 tz.transition 2038, 4, :o2, 29586521, 12 - tz.transition 2038, 8, :o1, 19725299, 8 + tz.transition 2038, 9, :o1, 19725579, 8 tz.transition 2039, 4, :o2, 29590889, 12 - tz.transition 2039, 8, :o1, 19728211, 8 + tz.transition 2039, 9, :o1, 19728491, 8 tz.transition 2040, 4, :o2, 29595257, 12 - tz.transition 2040, 8, :o1, 19731179, 8 + tz.transition 2040, 9, :o1, 19731403, 8 tz.transition 2041, 4, :o2, 29599625, 12 - tz.transition 2041, 8, :o1, 19734091, 8 + tz.transition 2041, 9, :o1, 19734315, 8 tz.transition 2042, 4, :o2, 29603993, 12 - tz.transition 2042, 8, :o1, 19737003, 8 + tz.transition 2042, 9, :o1, 19737227, 8 tz.transition 2043, 4, :o2, 29608361, 12 - tz.transition 2043, 8, :o1, 19739915, 8 + tz.transition 2043, 9, :o1, 19740139, 8 tz.transition 2044, 4, :o2, 29612813, 12 - tz.transition 2044, 8, :o1, 19742827, 8 + tz.transition 2044, 9, :o1, 19743107, 8 tz.transition 2045, 4, :o2, 29617181, 12 - tz.transition 2045, 8, :o1, 19745795, 8 + tz.transition 2045, 9, :o1, 19746019, 8 tz.transition 2046, 4, :o2, 29621549, 12 - tz.transition 2046, 8, :o1, 19748707, 8 + tz.transition 2046, 9, :o1, 19748931, 8 tz.transition 2047, 4, :o2, 29625917, 12 - tz.transition 2047, 8, :o1, 19751619, 8 + tz.transition 2047, 9, :o1, 19751843, 8 tz.transition 2048, 4, :o2, 29630285, 12 - tz.transition 2048, 8, :o1, 19754531, 8 + tz.transition 2048, 9, :o1, 19754755, 8 tz.transition 2049, 4, :o2, 29634737, 12 - tz.transition 2049, 8, :o1, 19757443, 8 + tz.transition 2049, 9, :o1, 19757723, 8 tz.transition 2050, 4, :o2, 29639105, 12 - tz.transition 2050, 8, :o1, 19760355, 8 + tz.transition 2050, 9, :o1, 19760635, 8 end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Casablanca.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Casablanca.rb index 18d73c93a0..18d73c93a0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Casablanca.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Casablanca.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Harare.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Harare.rb index 070c95ae0f..070c95ae0f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Harare.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Harare.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Johannesburg.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Johannesburg.rb index f0af0d8e33..f0af0d8e33 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Johannesburg.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Johannesburg.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Monrovia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Monrovia.rb index 40e711fa44..40e711fa44 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Monrovia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Monrovia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Nairobi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Nairobi.rb index 7b0a2f43be..7b0a2f43be 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Africa/Nairobi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Africa/Nairobi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb new file mode 100644 index 0000000000..307f9546de --- /dev/null +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/Buenos_Aires.rb @@ -0,0 +1,84 @@ +require 'tzinfo/timezone_definition' + +module TZInfo + module Definitions + module America + module Argentina + module Buenos_Aires + include TimezoneDefinition + + timezone 'America/Argentina/Buenos_Aires' do |tz| + tz.offset :o0, -14028, 0, :LMT + tz.offset :o1, -15408, 0, :CMT + tz.offset :o2, -14400, 0, :ART + tz.offset :o3, -14400, 3600, :ARST + tz.offset :o4, -10800, 0, :ART + tz.offset :o5, -10800, 3600, :ARST + + tz.transition 1894, 10, :o1, 17374555169, 7200 + tz.transition 1920, 5, :o2, 1453467407, 600 + tz.transition 1930, 12, :o3, 7278935, 3 + tz.transition 1931, 4, :o2, 19411461, 8 + tz.transition 1931, 10, :o3, 7279889, 3 + tz.transition 1932, 3, :o2, 19414141, 8 + tz.transition 1932, 11, :o3, 7281038, 3 + tz.transition 1933, 3, :o2, 19417061, 8 + tz.transition 1933, 11, :o3, 7282133, 3 + tz.transition 1934, 3, :o2, 19419981, 8 + tz.transition 1934, 11, :o3, 7283228, 3 + tz.transition 1935, 3, :o2, 19422901, 8 + tz.transition 1935, 11, :o3, 7284323, 3 + tz.transition 1936, 3, :o2, 19425829, 8 + tz.transition 1936, 11, :o3, 7285421, 3 + tz.transition 1937, 3, :o2, 19428749, 8 + tz.transition 1937, 11, :o3, 7286516, 3 + tz.transition 1938, 3, :o2, 19431669, 8 + tz.transition 1938, 11, :o3, 7287611, 3 + tz.transition 1939, 3, :o2, 19434589, 8 + tz.transition 1939, 11, :o3, 7288706, 3 + tz.transition 1940, 3, :o2, 19437517, 8 + tz.transition 1940, 7, :o3, 7289435, 3 + tz.transition 1941, 6, :o2, 19441285, 8 + tz.transition 1941, 10, :o3, 7290848, 3 + tz.transition 1943, 8, :o2, 19447501, 8 + tz.transition 1943, 10, :o3, 7293038, 3 + tz.transition 1946, 3, :o2, 19455045, 8 + tz.transition 1946, 10, :o3, 7296284, 3 + tz.transition 1963, 10, :o2, 19506429, 8 + tz.transition 1963, 12, :o3, 7315136, 3 + tz.transition 1964, 3, :o2, 19507645, 8 + tz.transition 1964, 10, :o3, 7316051, 3 + tz.transition 1965, 3, :o2, 19510565, 8 + tz.transition 1965, 10, :o3, 7317146, 3 + tz.transition 1966, 3, :o2, 19513485, 8 + tz.transition 1966, 10, :o3, 7318241, 3 + tz.transition 1967, 4, :o2, 19516661, 8 + tz.transition 1967, 10, :o3, 7319294, 3 + tz.transition 1968, 4, :o2, 19519629, 8 + tz.transition 1968, 10, :o3, 7320407, 3 + tz.transition 1969, 4, :o2, 19522541, 8 + tz.transition 1969, 10, :o4, 7321499, 3 + tz.transition 1974, 1, :o5, 128142000 + tz.transition 1974, 5, :o4, 136605600 + tz.transition 1988, 12, :o5, 596948400 + tz.transition 1989, 3, :o4, 605066400 + tz.transition 1989, 10, :o5, 624423600 + tz.transition 1990, 3, :o4, 636516000 + tz.transition 1990, 10, :o5, 656478000 + tz.transition 1991, 3, :o4, 667965600 + tz.transition 1991, 10, :o5, 687927600 + tz.transition 1992, 3, :o4, 699415200 + tz.transition 1992, 10, :o5, 719377200 + tz.transition 1993, 3, :o4, 731469600 + tz.transition 1999, 10, :o3, 938919600 + tz.transition 2000, 3, :o4, 952052400 + tz.transition 2007, 12, :o5, 1198983600 + tz.transition 2008, 3, :o4, 1205632800 + tz.transition 2008, 10, :o5, 1224385200 + tz.transition 2009, 3, :o4, 1237082400 + end + end + end + end + end +end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/San_Juan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/San_Juan.rb index ba8be4705f..ba8be4705f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Argentina/San_Juan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Argentina/San_Juan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Bogota.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Bogota.rb index ef96435c6a..ef96435c6a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Bogota.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Bogota.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Caracas.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Caracas.rb index 27392a540a..27392a540a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Caracas.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Caracas.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chicago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chicago.rb index 0996857cf0..0996857cf0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chicago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chicago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chihuahua.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chihuahua.rb index 1710b57c79..1710b57c79 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Chihuahua.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Chihuahua.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Denver.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Denver.rb index 1c1efb5ff3..1c1efb5ff3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Denver.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Denver.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Godthab.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Godthab.rb index 1e05518b0d..1e05518b0d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Godthab.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Godthab.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Guatemala.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Guatemala.rb index a2bf73401c..a2bf73401c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Guatemala.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Guatemala.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Halifax.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Halifax.rb index d25ae775b3..d25ae775b3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Halifax.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Halifax.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb index f1430f6c24..f1430f6c24 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Indiana/Indianapolis.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Juneau.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Juneau.rb index f646f3f54a..f646f3f54a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Juneau.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Juneau.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/La_Paz.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/La_Paz.rb index 45c907899f..45c907899f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/La_Paz.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/La_Paz.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Lima.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Lima.rb index af68ac29f7..af68ac29f7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Lima.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Lima.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Los_Angeles.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Los_Angeles.rb index 16007fd675..16007fd675 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Los_Angeles.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Los_Angeles.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mazatlan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mazatlan.rb index ba9e6efcf1..ba9e6efcf1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mazatlan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mazatlan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mexico_City.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mexico_City.rb index 2347fce647..2347fce647 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Mexico_City.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Mexico_City.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Monterrey.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Monterrey.rb index 5816a9eab1..5816a9eab1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Monterrey.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Monterrey.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/New_York.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/New_York.rb index 7d802bd2de..7d802bd2de 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/New_York.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/New_York.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Phoenix.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Phoenix.rb index b514e0c0f9..b514e0c0f9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Phoenix.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Phoenix.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Regina.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Regina.rb index ebdb68814a..ebdb68814a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Regina.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Regina.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Santiago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Santiago.rb index 0287c9ebc4..0287c9ebc4 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Santiago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Santiago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Sao_Paulo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Sao_Paulo.rb index 0524f81c04..0524f81c04 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Sao_Paulo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Sao_Paulo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/St_Johns.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/St_Johns.rb index e4a3599d35..e4a3599d35 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/St_Johns.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/St_Johns.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Tijuana.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Tijuana.rb index 423059da46..423059da46 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/America/Tijuana.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/America/Tijuana.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Almaty.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Almaty.rb index 9ee18970f1..9ee18970f1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Almaty.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Almaty.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baghdad.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baghdad.rb index 774dca1587..774dca1587 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baghdad.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baghdad.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baku.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baku.rb index e86340ebfa..e86340ebfa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Baku.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Baku.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Bangkok.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Bangkok.rb index 139194e5e5..139194e5e5 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Bangkok.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Bangkok.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Chongqing.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Chongqing.rb index 8c94b4ba86..8c94b4ba86 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Chongqing.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Chongqing.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Colombo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Colombo.rb index f6531fa819..f6531fa819 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Colombo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Colombo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Dhaka.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Dhaka.rb index ccd0265503..46dce9a0d0 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Dhaka.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Dhaka.rb @@ -13,6 +13,7 @@ module TZInfo tz.offset :o3, 19800, 0, :IST tz.offset :o4, 21600, 0, :DACT tz.offset :o5, 21600, 0, :BDT + tz.offset :o6, 21600, 3600, :BDST tz.transition 1889, 12, :o1, 2083422167, 864 tz.transition 1941, 9, :o2, 524937943, 216 @@ -20,6 +21,7 @@ module TZInfo tz.transition 1942, 8, :o2, 116668957, 48 tz.transition 1951, 9, :o4, 116828123, 48 tz.transition 1971, 3, :o5, 38772000 + tz.transition 2009, 6, :o6, 1245430800 end end end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Hong_Kong.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Hong_Kong.rb index f1edd75ac8..f1edd75ac8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Hong_Kong.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Hong_Kong.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Irkutsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Irkutsk.rb index 2d47d9580b..2d47d9580b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Irkutsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Irkutsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jakarta.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jakarta.rb index cc58fa173b..cc58fa173b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jakarta.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jakarta.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jerusalem.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jerusalem.rb index 9b737b899e..9b737b899e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Jerusalem.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Jerusalem.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kabul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kabul.rb index 669c09790a..669c09790a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kabul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kabul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kamchatka.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kamchatka.rb index 2f1690b3a9..2f1690b3a9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kamchatka.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kamchatka.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Karachi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Karachi.rb new file mode 100644 index 0000000000..dfe02c5cf6 --- /dev/null +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Karachi.rb @@ -0,0 +1,114 @@ +require 'tzinfo/timezone_definition' + +module TZInfo + module Definitions + module Asia + module Karachi + include TimezoneDefinition + + timezone 'Asia/Karachi' do |tz| + tz.offset :o0, 16092, 0, :LMT + tz.offset :o1, 19800, 0, :IST + tz.offset :o2, 19800, 3600, :IST + tz.offset :o3, 18000, 0, :KART + tz.offset :o4, 18000, 0, :PKT + tz.offset :o5, 18000, 3600, :PKST + + tz.transition 1906, 12, :o1, 1934061051, 800 + tz.transition 1942, 8, :o2, 116668957, 48 + tz.transition 1945, 10, :o1, 116723675, 48 + tz.transition 1951, 9, :o3, 116828125, 48 + tz.transition 1971, 3, :o4, 38775600 + tz.transition 2002, 4, :o5, 1018119660 + tz.transition 2002, 10, :o4, 1033840860 + tz.transition 2008, 5, :o5, 1212260400 + tz.transition 2008, 10, :o4, 1225476000 + tz.transition 2009, 4, :o5, 1239735600 + tz.transition 2009, 10, :o4, 1257012000 + tz.transition 2010, 4, :o5, 1271271600 + tz.transition 2010, 10, :o4, 1288548000 + tz.transition 2011, 4, :o5, 1302807600 + tz.transition 2011, 10, :o4, 1320084000 + tz.transition 2012, 4, :o5, 1334430000 + tz.transition 2012, 10, :o4, 1351706400 + tz.transition 2013, 4, :o5, 1365966000 + tz.transition 2013, 10, :o4, 1383242400 + tz.transition 2014, 4, :o5, 1397502000 + tz.transition 2014, 10, :o4, 1414778400 + tz.transition 2015, 4, :o5, 1429038000 + tz.transition 2015, 10, :o4, 1446314400 + tz.transition 2016, 4, :o5, 1460660400 + tz.transition 2016, 10, :o4, 1477936800 + tz.transition 2017, 4, :o5, 1492196400 + tz.transition 2017, 10, :o4, 1509472800 + tz.transition 2018, 4, :o5, 1523732400 + tz.transition 2018, 10, :o4, 1541008800 + tz.transition 2019, 4, :o5, 1555268400 + tz.transition 2019, 10, :o4, 1572544800 + tz.transition 2020, 4, :o5, 1586890800 + tz.transition 2020, 10, :o4, 1604167200 + tz.transition 2021, 4, :o5, 1618426800 + tz.transition 2021, 10, :o4, 1635703200 + tz.transition 2022, 4, :o5, 1649962800 + tz.transition 2022, 10, :o4, 1667239200 + tz.transition 2023, 4, :o5, 1681498800 + tz.transition 2023, 10, :o4, 1698775200 + tz.transition 2024, 4, :o5, 1713121200 + tz.transition 2024, 10, :o4, 1730397600 + tz.transition 2025, 4, :o5, 1744657200 + tz.transition 2025, 10, :o4, 1761933600 + tz.transition 2026, 4, :o5, 1776193200 + tz.transition 2026, 10, :o4, 1793469600 + tz.transition 2027, 4, :o5, 1807729200 + tz.transition 2027, 10, :o4, 1825005600 + tz.transition 2028, 4, :o5, 1839351600 + tz.transition 2028, 10, :o4, 1856628000 + tz.transition 2029, 4, :o5, 1870887600 + tz.transition 2029, 10, :o4, 1888164000 + tz.transition 2030, 4, :o5, 1902423600 + tz.transition 2030, 10, :o4, 1919700000 + tz.transition 2031, 4, :o5, 1933959600 + tz.transition 2031, 10, :o4, 1951236000 + tz.transition 2032, 4, :o5, 1965582000 + tz.transition 2032, 10, :o4, 1982858400 + tz.transition 2033, 4, :o5, 1997118000 + tz.transition 2033, 10, :o4, 2014394400 + tz.transition 2034, 4, :o5, 2028654000 + tz.transition 2034, 10, :o4, 2045930400 + tz.transition 2035, 4, :o5, 2060190000 + tz.transition 2035, 10, :o4, 2077466400 + tz.transition 2036, 4, :o5, 2091812400 + tz.transition 2036, 10, :o4, 2109088800 + tz.transition 2037, 4, :o5, 2123348400 + tz.transition 2037, 10, :o4, 2140624800 + tz.transition 2038, 4, :o5, 59172679, 24 + tz.transition 2038, 10, :o4, 9862913, 4 + tz.transition 2039, 4, :o5, 59181439, 24 + tz.transition 2039, 10, :o4, 9864373, 4 + tz.transition 2040, 4, :o5, 59190223, 24 + tz.transition 2040, 10, :o4, 9865837, 4 + tz.transition 2041, 4, :o5, 59198983, 24 + tz.transition 2041, 10, :o4, 9867297, 4 + tz.transition 2042, 4, :o5, 59207743, 24 + tz.transition 2042, 10, :o4, 9868757, 4 + tz.transition 2043, 4, :o5, 59216503, 24 + tz.transition 2043, 10, :o4, 9870217, 4 + tz.transition 2044, 4, :o5, 59225287, 24 + tz.transition 2044, 10, :o4, 9871681, 4 + tz.transition 2045, 4, :o5, 59234047, 24 + tz.transition 2045, 10, :o4, 9873141, 4 + tz.transition 2046, 4, :o5, 59242807, 24 + tz.transition 2046, 10, :o4, 9874601, 4 + tz.transition 2047, 4, :o5, 59251567, 24 + tz.transition 2047, 10, :o4, 9876061, 4 + tz.transition 2048, 4, :o5, 59260351, 24 + tz.transition 2048, 10, :o4, 9877525, 4 + tz.transition 2049, 4, :o5, 59269111, 24 + tz.transition 2049, 10, :o4, 9878985, 4 + tz.transition 2050, 4, :o5, 59277871, 24 + tz.transition 2050, 10, :o4, 9880445, 4 + end + end + end + end +end diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kathmandu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kathmandu.rb index 37b241612e..37b241612e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kathmandu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kathmandu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kolkata.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kolkata.rb index 1b6ffbd59d..1b6ffbd59d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kolkata.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kolkata.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb index d6c503c155..d6c503c155 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Krasnoyarsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb index 77a0c206fa..77a0c206fa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuala_Lumpur.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuwait.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuwait.rb index 5bd5283197..5bd5283197 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Kuwait.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Kuwait.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Magadan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Magadan.rb index 302093693e..302093693e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Magadan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Magadan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Muscat.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Muscat.rb index 604f651dfa..604f651dfa 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Muscat.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Muscat.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Novosibirsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Novosibirsk.rb index a4e7796e75..a4e7796e75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Novosibirsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Novosibirsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Rangoon.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Rangoon.rb index 759b82d77a..759b82d77a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Rangoon.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Rangoon.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Riyadh.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Riyadh.rb index 7add410620..7add410620 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Riyadh.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Riyadh.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Seoul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Seoul.rb index 795d2a75df..795d2a75df 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Seoul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Seoul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Shanghai.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Shanghai.rb index 34b13d59ae..34b13d59ae 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Shanghai.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Shanghai.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Singapore.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Singapore.rb index b323a78f74..b323a78f74 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Singapore.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Singapore.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Taipei.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Taipei.rb index 3ba12108fb..3ba12108fb 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Taipei.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Taipei.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tashkent.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tashkent.rb index c205c7934d..c205c7934d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tashkent.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tashkent.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tbilisi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tbilisi.rb index 15792a5651..15792a5651 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tbilisi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tbilisi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tehran.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tehran.rb index d8df964a46..d8df964a46 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tehran.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tehran.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tokyo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tokyo.rb index 51c9e16421..51c9e16421 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Tokyo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Tokyo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb index 2854f5c5fd..2854f5c5fd 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Ulaanbaatar.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Urumqi.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Urumqi.rb index d793ff1341..d793ff1341 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Urumqi.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Urumqi.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Vladivostok.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Vladivostok.rb index bd9e3d60ec..bd9e3d60ec 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Vladivostok.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Vladivostok.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yakutsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yakutsk.rb index 56435a788f..56435a788f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yakutsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yakutsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yekaterinburg.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yekaterinburg.rb index 8ef8df4a41..8ef8df4a41 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yekaterinburg.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yekaterinburg.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yerevan.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yerevan.rb index e7f160861f..e7f160861f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Asia/Yerevan.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Asia/Yerevan.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Azores.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Azores.rb index 1bd16a75ac..1bd16a75ac 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Azores.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Azores.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb index 61c8c15043..61c8c15043 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/Cape_Verde.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/South_Georgia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/South_Georgia.rb index 6a4cbafb9f..6a4cbafb9f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Atlantic/South_Georgia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Atlantic/South_Georgia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Adelaide.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Adelaide.rb index c5d561cc1e..c5d561cc1e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Adelaide.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Adelaide.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Brisbane.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Brisbane.rb index dd85ddae94..dd85ddae94 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Brisbane.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Brisbane.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Darwin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Darwin.rb index 17de88124d..17de88124d 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Darwin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Darwin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Hobart.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Hobart.rb index 11384b9840..11384b9840 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Hobart.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Hobart.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Melbourne.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Melbourne.rb index c1304488ea..c1304488ea 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Melbourne.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Melbourne.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Perth.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Perth.rb index d9e66f14a8..d9e66f14a8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Perth.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Perth.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Sydney.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Sydney.rb index 9062bd7c3c..9062bd7c3c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Australia/Sydney.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Australia/Sydney.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Etc/UTC.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Etc/UTC.rb index 28b2c6a04c..28b2c6a04c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Etc/UTC.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Etc/UTC.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Amsterdam.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Amsterdam.rb index 2d0c95c4bc..2d0c95c4bc 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Amsterdam.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Amsterdam.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Athens.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Athens.rb index 4e21e535ca..4e21e535ca 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Athens.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Athens.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Belgrade.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Belgrade.rb index 4dbd893d75..4dbd893d75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Belgrade.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Belgrade.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Berlin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Berlin.rb index 721054236c..721054236c 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Berlin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Berlin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bratislava.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bratislava.rb index 7a731a0b6a..7a731a0b6a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bratislava.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bratislava.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Brussels.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Brussels.rb index 6b0a242944..6b0a242944 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Brussels.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Brussels.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bucharest.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bucharest.rb index 521c3c932e..521c3c932e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Bucharest.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Bucharest.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Budapest.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Budapest.rb index 1f3a9738b7..1f3a9738b7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Budapest.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Budapest.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Copenhagen.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Copenhagen.rb index 47cbaf14a7..47cbaf14a7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Copenhagen.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Copenhagen.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Dublin.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Dublin.rb index 0560bb5436..0560bb5436 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Dublin.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Dublin.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Helsinki.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Helsinki.rb index 13a806bcc7..13a806bcc7 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Helsinki.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Helsinki.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Istanbul.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Istanbul.rb index 8306c47536..8306c47536 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Istanbul.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Istanbul.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Kiev.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Kiev.rb index 513d3308be..513d3308be 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Kiev.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Kiev.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Lisbon.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Lisbon.rb index 1c6d2a3d30..1c6d2a3d30 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Lisbon.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Lisbon.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Ljubljana.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Ljubljana.rb index a9828e6ef8..a9828e6ef8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Ljubljana.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Ljubljana.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/London.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/London.rb index 64ce41e900..64ce41e900 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/London.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/London.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Madrid.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Madrid.rb index 1fb568239a..1fb568239a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Madrid.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Madrid.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Minsk.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Minsk.rb index fa15816cc8..fa15816cc8 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Minsk.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Minsk.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Moscow.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Moscow.rb index ef269b675b..ef269b675b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Moscow.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Moscow.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Paris.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Paris.rb index e3236c0ba1..e3236c0ba1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Paris.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Paris.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Prague.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Prague.rb index bcabee96c1..bcabee96c1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Prague.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Prague.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Riga.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Riga.rb index 784837f758..784837f758 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Riga.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Riga.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Rome.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Rome.rb index aa7b43d9d2..aa7b43d9d2 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Rome.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Rome.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sarajevo.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sarajevo.rb index 068c5fe6ad..068c5fe6ad 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sarajevo.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sarajevo.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Skopje.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Skopje.rb index 10b71f285e..10b71f285e 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Skopje.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Skopje.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sofia.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sofia.rb index 38a70eceb9..38a70eceb9 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Sofia.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Sofia.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Stockholm.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Stockholm.rb index 43db70fa61..43db70fa61 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Stockholm.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Stockholm.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Tallinn.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Tallinn.rb index de5a8569f3..de5a8569f3 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Tallinn.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Tallinn.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vienna.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vienna.rb index 990aabab66..990aabab66 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vienna.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vienna.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vilnius.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vilnius.rb index d89d095a75..d89d095a75 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Vilnius.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Vilnius.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Warsaw.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Warsaw.rb index 7fa51c2691..7fa51c2691 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Warsaw.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Warsaw.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Zagreb.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Zagreb.rb index ecdd903d28..ecdd903d28 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Europe/Zagreb.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Europe/Zagreb.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Auckland.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Auckland.rb index a524fd6b6b..a524fd6b6b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Auckland.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Auckland.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Fiji.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Fiji.rb index 5fe9bbd9a6..5fe9bbd9a6 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Fiji.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Fiji.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Guam.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Guam.rb index d4c1a0a682..d4c1a0a682 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Guam.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Guam.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Honolulu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Honolulu.rb index 204b226537..204b226537 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Honolulu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Honolulu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Majuro.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Majuro.rb index 32adad92c1..32adad92c1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Majuro.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Majuro.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Midway.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Midway.rb index 97784fcc10..97784fcc10 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Midway.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Midway.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Noumea.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Noumea.rb index 70173db8ab..70173db8ab 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Noumea.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Noumea.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Pago_Pago.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Pago_Pago.rb index c8fcd7d527..c8fcd7d527 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Pago_Pago.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Pago_Pago.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Port_Moresby.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Port_Moresby.rb index f06cf6d54f..f06cf6d54f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Port_Moresby.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Port_Moresby.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Tongatapu.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Tongatapu.rb index 7578d92f38..7578d92f38 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/definitions/Pacific/Tongatapu.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/definitions/Pacific/Tongatapu.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/info_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/info_timezone.rb index 001303c594..001303c594 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/info_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/info_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone.rb index f8ec4fca87..f8ec4fca87 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone_info.rb index 8197ff3e81..8197ff3e81 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/linked_timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/linked_timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/offset_rationals.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/offset_rationals.rb index b1f10b2b63..b1f10b2b63 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/offset_rationals.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/offset_rationals.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/ruby_core_support.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/ruby_core_support.rb index b65eeaaae7..9a0441206b 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/ruby_core_support.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/ruby_core_support.rb @@ -1,56 +1,56 @@ -#-- -# Copyright (c) 2008 Philip Ross -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -#++ - -require 'date' -require 'rational' - -module TZInfo - - # Methods to support different versions of Ruby. - module RubyCoreSupport #:nodoc: - - # Use Rational.new! for performance reasons in Ruby 1.8. - # This has been removed from 1.9, but Rational performs better. - if Rational.respond_to? :new! - def self.rational_new!(numerator, denominator = 1) - Rational.new!(numerator, denominator) - end - else - def self.rational_new!(numerator, denominator = 1) - Rational(numerator, denominator) - end - end - - # Ruby 1.8.6 introduced new! and deprecated new0. - # Ruby 1.9.0 removed new0. - # We still need to support new0 for older versions of Ruby. - if DateTime.respond_to? :new! - def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY) - DateTime.new!(ajd, of, sg) - end - else - def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY) - DateTime.new0(ajd, of, sg) - end - end - end +#--
+# Copyright (c) 2008 Philip Ross
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#++
+
+require 'date'
+require 'rational'
+
+module TZInfo
+
+ # Methods to support different versions of Ruby.
+ module RubyCoreSupport #:nodoc:
+
+ # Use Rational.new! for performance reasons in Ruby 1.8.
+ # This has been removed from 1.9, but Rational performs better.
+ if Rational.respond_to? :new!
+ def self.rational_new!(numerator, denominator = 1)
+ Rational.new!(numerator, denominator)
+ end
+ else
+ def self.rational_new!(numerator, denominator = 1)
+ Rational(numerator, denominator)
+ end
+ end
+
+ # Ruby 1.8.6 introduced new! and deprecated new0.
+ # Ruby 1.9.0 removed new0.
+ # We still need to support new0 for older versions of Ruby.
+ if DateTime.respond_to? :new!
+ def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
+ DateTime.new!(ajd, of, sg)
+ end
+ else
+ def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
+ DateTime.new0(ajd, of, sg)
+ end
+ end
+ end
end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/time_or_datetime.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/time_or_datetime.rb index 264517f3ee..264517f3ee 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/time_or_datetime.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/time_or_datetime.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone.rb index ef4ecd8ae1..ef4ecd8ae1 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_definition.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_definition.rb index 39ca8bfa53..39ca8bfa53 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_definition.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_definition.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_info.rb index 68e38c35fb..68e38c35fb 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_offset_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_offset_info.rb index 6a0bbca46f..6a0bbca46f 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_offset_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_offset_info.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_period.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_period.rb index 00888fcfdc..00888fcfdc 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_period.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_period.rb diff --git a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_transition_info.rb b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_transition_info.rb index 6b0669cc4a..6b0669cc4a 100644 --- a/activesupport/lib/active_support/vendor/tzinfo-0.3.13/lib/tzinfo/timezone_transition_info.rb +++ b/activesupport/lib/active_support/vendor/tzinfo-0.3.15/lib/tzinfo/timezone_transition_info.rb diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index 36fe9510ba..c4aaba7ab3 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -43,7 +43,14 @@ class NilClass private def method_missing(method, *args, &block) - raise_nil_warning_for METHOD_CLASS_MAP[method], method, caller + # Ruby 1.9.2: disallow explicit coercion via method_missing. + if method == :to_ary || method == :to_str + super + elsif klass = METHOD_CLASS_MAP[method] + raise_nil_warning_for klass, method, caller + else + super + end end # Raises a NoMethodError when you attempt to call a method on +nil+. @@ -55,4 +62,3 @@ class NilClass raise NoMethodError, message, with_caller || caller end end - diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 2ae22c35fb..0f7ba1918b 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -13,8 +13,6 @@ module ActiveSupport data = StringIO.new(data || '') end - LibXML::XML.default_keep_blanks = false - char = data.getc if char.nil? {} @@ -44,9 +42,9 @@ module LibXML #:nodoc: # hash:: # Hash to merge the converted element into. def to_hash(hash={}) - if text? - raise LibXML::XML::Error if content.length >= LIB_XML_LIMIT - hash[CONTENT_ROOT] = content + if text? || cdata? + raise LibXML::XML::Error if hash[CONTENT_ROOT].to_s.length + content.length >= LIB_XML_LIMIT + hash[CONTENT_ROOT] = hash[CONTENT_ROOT].to_s + content else sub_hash = insert_name_into_hash(hash, name) attributes_to_hash(sub_hash) @@ -88,6 +86,11 @@ module LibXML #:nodoc: # Hash to merge the children into. def children_to_hash(hash={}) each { |child| child.to_hash(hash) } + + if hash.length > 1 && hash[CONTENT_ROOT].blank? + hash.delete(CONTENT_ROOT) + end + attributes_to_hash(hash) hash end diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index af9656615c..dda139372e 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -1,17 +1,17 @@ ORIG_ARGV = ARGV.dup -require 'test/unit' - begin - require 'mocha' + require File.expand_path('../../../vendor/gems/environment', __FILE__) rescue LoadError - $stderr.puts 'Loading rubygems' - require 'rubygems' - require 'mocha' end +lib = File.expand_path("#{File.dirname(__FILE__)}/../lib") +$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib) + +require 'test/unit' +require 'mocha' + ENV['NO_RELOAD'] = '1' -$:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_support' require 'active_support/test_case' diff --git a/actionpack/test/template/benchmark_helper_test.rb b/activesupport/test/benchmarkable_test.rb index ac31fc6503..766956f50f 100644 --- a/actionpack/test/template/benchmark_helper_test.rb +++ b/activesupport/test/benchmarkable_test.rb @@ -1,17 +1,10 @@ require 'abstract_unit' -require 'action_view/helpers/benchmark_helper' -class BenchmarkHelperTest < ActionView::TestCase - tests ActionView::Helpers::BenchmarkHelper - - def setup - super - controller.logger = ActiveSupport::BufferedLogger.new(StringIO.new) - controller.logger.auto_flushing = false - end +class BenchmarkableTest < ActiveSupport::TestCase + include ActiveSupport::Benchmarkable def teardown - controller.logger.send(:clear_buffer) + logger.send(:clear_buffer) end def test_without_block @@ -45,22 +38,22 @@ class BenchmarkHelperTest < ActionView::TestCase end def test_within_level - controller.logger.level = ActiveSupport::BufferedLogger::DEBUG + logger.level = ActiveSupport::BufferedLogger::DEBUG benchmark('included_debug_run', :level => :debug) { } assert_last_logged 'included_debug_run' end def test_outside_level - controller.logger.level = ActiveSupport::BufferedLogger::ERROR + logger.level = ActiveSupport::BufferedLogger::ERROR benchmark('skipped_debug_run', :level => :debug) { } assert_no_match(/skipped_debug_run/, buffer.last) ensure - controller.logger.level = ActiveSupport::BufferedLogger::DEBUG + logger.level = ActiveSupport::BufferedLogger::DEBUG end def test_without_silencing benchmark('debug_run', :silence => false) do - controller.logger.info "not silenced!" + logger.info "not silenced!" end assert_equal 2, buffer.size @@ -68,18 +61,25 @@ class BenchmarkHelperTest < ActionView::TestCase def test_with_silencing benchmark('debug_run', :silence => true) do - controller.logger.info "silenced!" + logger.info "silenced!" end assert_equal 1, buffer.size end - private + def logger + @logger ||= begin + logger = ActiveSupport::BufferedLogger.new(StringIO.new) + logger.auto_flushing = false + logger + end + end + def buffer - controller.logger.send(:buffer) + logger.send(:buffer) end - + def assert_last_logged(message = 'Benchmarking') assert_match(/^#{message} \(.*\)$/, buffer.last) end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 892aa97ad7..00e05f76fe 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -30,7 +30,9 @@ class CacheStoreSettingTest < ActiveSupport::TestCase def test_mem_cache_fragment_cache_store_with_given_mem_cache_like_object MemCache.expects(:new).never - store = ActiveSupport::Cache.lookup_store :mem_cache_store, stub("memcache", :get => true) + memcache = Object.new + def memcache.get() true end + store = ActiveSupport::Cache.lookup_store :mem_cache_store, memcache assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) end @@ -154,13 +156,13 @@ class FileStoreTest < ActiveSupport::TestCase File.stubs(:mtime).returns(time) @cache.write('foo', 'bar') - cache_read = lambda { @cache.read('foo', :expires_in => 1.minute) } + cache_read = lambda { @cache.read('foo', :expires_in => 60) } assert_equal 'bar', cache_read.call - Time.stubs(:now).returns(time + 30.seconds) + Time.stubs(:now).returns(time + 30) assert_equal 'bar', cache_read.call - Time.stubs(:now).returns(time + 2.minutes) + Time.stubs(:now).returns(time + 120) assert_nil cache_read.call end end diff --git a/activesupport/test/new_callback_inheritance_test.rb b/activesupport/test/callback_inheritance_test.rb index fe316ae3da..18721eab19 100644 --- a/activesupport/test/new_callback_inheritance_test.rb +++ b/activesupport/test/callback_inheritance_test.rb @@ -3,35 +3,35 @@ $:.unshift "#{File.dirname(__FILE__)}/../lib" require 'active_support' class GrandParent - include ActiveSupport::NewCallbacks - + include ActiveSupport::Callbacks + attr_reader :log, :action_name def initialize(action_name) @action_name, @log = action_name, [] end - + define_callbacks :dispatch set_callback :dispatch, :before, :before1, :before2, :per_key => {:if => proc {|c| c.action_name == "index" || c.action_name == "update" }} - set_callback :dispatch, :after, :after1, :after2, :per_key => {:if => proc {|c| c.action_name == "update" || c.action_name == "delete" }} - + set_callback :dispatch, :after, :after1, :after2, :per_key => {:if => proc {|c| c.action_name == "update" || c.action_name == "delete" }} + def before1 @log << "before1" end - + def before2 @log << "before2" end - - def after1 + + def after1 @log << "after1" end - + def after2 @log << "after2" end - + def dispatch - _run_dispatch_callbacks(action_name) do + run_callbacks(:dispatch, action_name) do @log << action_name end self @@ -45,11 +45,11 @@ end class Child < GrandParent skip_callback :dispatch, :before, :before2, :per_key => {:unless => proc {|c| c.action_name == "update" }}, :if => :state_open? - + def state_open? @state == :open end - + def initialize(action_name, state) super(action_name) @state = state @@ -64,15 +64,15 @@ class BasicCallbacksTest < Test::Unit::TestCase @delete = GrandParent.new("delete").dispatch @unknown = GrandParent.new("unknown").dispatch end - + def test_basic_per_key1 assert_equal %w(before1 before2 index), @index.log end - + def test_basic_per_key2 assert_equal %w(before1 before2 update after2 after1), @update.log end - + def test_basic_per_key3 assert_equal %w(delete after2 after1), @delete.log end @@ -85,15 +85,15 @@ class InheritedCallbacksTest < Test::Unit::TestCase @delete = Parent.new("delete").dispatch @unknown = Parent.new("unknown").dispatch end - + def test_inherited_excluded assert_equal %w(before1 index), @index.log end - + def test_inherited_not_excluded assert_equal %w(before1 before2 update after1), @update.log end - + def test_partially_excluded assert_equal %w(delete after2 after1), @delete.log end @@ -104,11 +104,11 @@ class InheritedCallbacksTest2 < Test::Unit::TestCase @update1 = Child.new("update", :open).dispatch @update2 = Child.new("update", :closed).dispatch end - + def test_crazy_mix_on assert_equal %w(before1 update after2 after1), @update1.log end - + def test_crazy_mix_off assert_equal %w(before1 before2 update after2 after1), @update2.log end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 2f747e2238..df98644436 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -1,188 +1,528 @@ -require 'abstract_unit' +# require 'abstract_unit' +require 'test/unit' +$:.unshift "#{File.dirname(__FILE__)}/../lib" +require 'active_support' -class Record - include ActiveSupport::Callbacks +module CallbacksTest + class Record + include ActiveSupport::Callbacks - define_callbacks :before_save, :after_save + define_callbacks :save - class << self - def callback_symbol(callback_method) - method_name = "#{callback_method}_method" - define_method(method_name) do - history << [callback_method, :symbol] + def self.before_save(*filters, &blk) + set_callback(:save, :before, *filters, &blk) + end + + def self.after_save(*filters, &blk) + set_callback(:save, :after, *filters, &blk) + end + + class << self + def callback_symbol(callback_method) + method_name = :"#{callback_method}_method" + define_method(method_name) do + history << [callback_method, :symbol] + end + method_name + end + + def callback_string(callback_method) + "history << [#{callback_method.to_sym.inspect}, :string]" + end + + def callback_proc(callback_method) + Proc.new { |model| model.history << [callback_method, :proc] } + end + + def callback_object(callback_method) + klass = Class.new + klass.send(:define_method, callback_method) do |model| + model.history << [:"#{callback_method}_save", :object] + end + klass.new end - method_name end - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" + def history + @history ||= [] + end + end + + class Person < Record + [:before_save, :after_save].each do |callback_method| + callback_method_sym = callback_method.to_sym + send(callback_method, callback_symbol(callback_method_sym)) + send(callback_method, callback_string(callback_method_sym)) + send(callback_method, callback_proc(callback_method_sym)) + send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ''))) + send(callback_method) { |model| model.history << [callback_method_sym, :block] } end - def callback_proc(callback_method) - Proc.new { |model| model.history << [callback_method, :proc] } + def save + run_callbacks :save end + end + + class PersonSkipper < Person + skip_callback :save, :before, :before_save_method, :if => :yes + skip_callback :save, :after, :before_save_method, :unless => :yes + skip_callback :save, :after, :before_save_method, :if => :no + skip_callback :save, :before, :before_save_method, :unless => :no + def yes; true; end + def no; false; end + end + + class ParentController + include ActiveSupport::Callbacks + + define_callbacks :dispatch + + set_callback :dispatch, :before, :log, :per_key => {:unless => proc {|c| c.action_name == :index || c.action_name == :show }} + set_callback :dispatch, :after, :log2 - def callback_object(callback_method) - klass = Class.new - klass.send(:define_method, callback_method) do |model| - model.history << [callback_method, :object] + attr_reader :action_name, :logger + def initialize(action_name) + @action_name, @logger = action_name, [] + end + + def log + @logger << action_name + end + + def log2 + @logger << action_name + end + + def dispatch + run_callbacks :dispatch, action_name do + @logger << "Done" end - klass.new + self end end - def history - @history ||= [] + class Child < ParentController + skip_callback :dispatch, :before, :log, :per_key => {:if => proc {|c| c.action_name == :update} } + skip_callback :dispatch, :after, :log2 end -end -class Person < Record - [:before_save, :after_save].each do |callback_method| - callback_method_sym = callback_method.to_sym - send(callback_method, callback_symbol(callback_method_sym)) - send(callback_method, callback_string(callback_method_sym)) - send(callback_method, callback_proc(callback_method_sym)) - send(callback_method, callback_object(callback_method_sym)) - send(callback_method) { |model| model.history << [callback_method_sym, :block] } + class OneTimeCompile < Record + @@starts_true, @@starts_false = true, false + + def initialize + super + end + + before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :per_key => {:if => :starts_true} + before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :per_key => {:if => :starts_false} + before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :per_key => {:unless => :starts_true} + before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :per_key => {:unless => :starts_false} + + def starts_true + if @@starts_true + @@starts_true = false + return true + end + @@starts_true + end + + def starts_false + unless @@starts_false + @@starts_false = true + return false + end + @@starts_false + end + + def save + run_callbacks :save, :action + end end - def save - run_callbacks(:before_save) - run_callbacks(:after_save) + class OneTimeCompileTest < Test::Unit::TestCase + def test_optimized_first_compile + around = OneTimeCompile.new + around.save + assert_equal [ + [:before_save, :starts_true, :if], + [:before_save, :starts_true, :unless] + ], around.history + end end -end -class ConditionalPerson < Record - # proc - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } - before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } - before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } - # symbol - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes - before_save Proc.new { |r| r.history << "b00m" }, :if => :no - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes - # string - before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes' - before_save Proc.new { |r| r.history << "b00m" }, :if => 'no' - before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no' - before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes' - # Array with conditions - before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :if => [:yes, :other_yes] - before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, :no] - before_save Proc.new { |r| r.history << [:before_save, :symbol_array] }, :unless => [:no, :other_no] - before_save Proc.new { |r| r.history << "b00m" }, :unless => [:yes, :no] - # Combined if and unless - before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes - # Array with different types of conditions - before_save Proc.new { |r| r.history << [:before_save, :symbol_proc_string_array] }, :if => [:yes, Proc.new { |r| true }, 'yes'] - before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'] - # Array with different types of conditions comibned if and unless - before_save Proc.new { |r| r.history << [:before_save, :combined_symbol_proc_string_array] }, - :if => [:yes, Proc.new { |r| true }, 'yes'], :unless => [:no, 'no'] - before_save Proc.new { |r| r.history << "b00m" }, :if => [:yes, Proc.new { |r| true }, 'no'], :unless => [:no, 'no'] - - def yes; true; end - def other_yes; true; end - def no; false; end - def other_no; false; end - - def save - run_callbacks(:before_save) - run_callbacks(:after_save) + class ConditionalPerson < Record + # proc + before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } + before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } + before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } + before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } + # symbol + before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes + before_save Proc.new { |r| r.history << "b00m" }, :if => :no + before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no + before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes + # string + before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes' + before_save Proc.new { |r| r.history << "b00m" }, :if => 'no' + before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no' + before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes' + # Combined if and unless + before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no + before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes + + def yes; true; end + def other_yes; true; end + def no; false; end + def other_no; false; end + + def save + run_callbacks :save + end end -end -class CallbacksTest < Test::Unit::TestCase - def test_save_person - person = Person.new - assert_equal [], person.history - person.save - assert_equal [ - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :proc], - [:before_save, :object], - [:before_save, :block], - [:after_save, :symbol], - [:after_save, :string], - [:after_save, :proc], - [:after_save, :object], - [:after_save, :block] - ], person.history + class CleanPerson < ConditionalPerson + reset_callbacks :save end -end -class ConditionalCallbackTest < Test::Unit::TestCase - def test_save_conditional_person - person = ConditionalPerson.new - person.save - assert_equal [ - [:before_save, :proc], - [:before_save, :proc], - [:before_save, :symbol], - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :string], - [:before_save, :symbol_array], - [:before_save, :symbol_array], - [:before_save, :combined_symbol], - [:before_save, :symbol_proc_string_array], - [:before_save, :combined_symbol_proc_string_array] - ], person.history + class MySuper + include ActiveSupport::Callbacks + define_callbacks :save end -end -class CallbackTest < Test::Unit::TestCase - include ActiveSupport::Callbacks + class AroundPerson < MySuper + attr_reader :history + + set_callback :save, :before, :nope, :if => :no + set_callback :save, :before, :nope, :unless => :yes + set_callback :save, :after, :tweedle + set_callback :save, :before, "tweedle_dee" + set_callback :save, :before, proc {|m| m.history << "yup" } + set_callback :save, :before, :nope, :if => proc { false } + set_callback :save, :before, :nope, :unless => proc { true } + set_callback :save, :before, :yup, :if => proc { true } + set_callback :save, :before, :yup, :unless => proc { false } + set_callback :save, :around, :tweedle_dum + set_callback :save, :around, :w0tyes, :if => :yes + set_callback :save, :around, :w0tno, :if => :no + set_callback :save, :around, :tweedle_deedle + + def no; false; end + def yes; true; end + + def nope + @history << "boom" + end + + def yup + @history << "yup" + end + + def w0tyes + @history << "w0tyes before" + yield + @history << "w0tyes after" + end + + def w0tno + @history << "boom" + yield + end + + def tweedle_dee + @history << "tweedle dee" + end + + def tweedle_dum + @history << "tweedle dum pre" + yield + @history << "tweedle dum post" + end + + def tweedle + @history << "tweedle" + end + + def tweedle_deedle + @history << "tweedle deedle pre" + yield + @history << "tweedle deedle post" + end + + def initialize + @history = [] + end - def test_eql - callback = Callback.new(:before, :save, :identifier => :lifesaver) - assert callback.eql?(Callback.new(:before, :save, :identifier => :lifesaver)) - assert callback.eql?(Callback.new(:before, :save)) - assert callback.eql?(:lifesaver) - assert callback.eql?(:save) - assert !callback.eql?(Callback.new(:before, :destroy)) - assert !callback.eql?(:destroy) + def save + run_callbacks :save do + @history << "running" + end + end end - def test_dup - a = Callback.new(:before, :save) - assert_equal({}, a.options) - b = a.dup - b.options[:unless] = :pigs_fly - assert_equal({:unless => :pigs_fly}, b.options) - assert_equal({}, a.options) + class HyphenatedCallbacks + include ActiveSupport::Callbacks + define_callbacks :save + attr_reader :stuff + + set_callback :save, :before, :omg, :per_key => {:if => :yes} + + def yes() true end + + def omg + @stuff = "OMG" + end + + def save + run_callbacks :save, "hyphen-ated" do + @stuff + end + end + end + + class AroundCallbacksTest < Test::Unit::TestCase + def test_save_around + around = AroundPerson.new + around.save + assert_equal [ + "tweedle dee", + "yup", "yup", + "tweedle dum pre", + "w0tyes before", + "tweedle deedle pre", + "running", + "tweedle deedle post", + "w0tyes after", + "tweedle dum post", + "tweedle" + ], around.history + end + end + + class SkipCallbacksTest < Test::Unit::TestCase + def test_skip_person + person = PersonSkipper.new + assert_equal [], person.history + person.save + assert_equal [ + [:before_save, :string], + [:before_save, :proc], + [:before_save, :object], + [:before_save, :block], + [:after_save, :block], + [:after_save, :object], + [:after_save, :proc], + [:after_save, :string], + [:after_save, :symbol] + ], person.history + end + end + + class CallbacksTest < Test::Unit::TestCase + def test_save_person + person = Person.new + assert_equal [], person.history + person.save + assert_equal [ + [:before_save, :symbol], + [:before_save, :string], + [:before_save, :proc], + [:before_save, :object], + [:before_save, :block], + [:after_save, :block], + [:after_save, :object], + [:after_save, :proc], + [:after_save, :string], + [:after_save, :symbol] + ], person.history + end + end + + class ConditionalCallbackTest < Test::Unit::TestCase + def test_save_conditional_person + person = ConditionalPerson.new + person.save + assert_equal [ + [:before_save, :proc], + [:before_save, :proc], + [:before_save, :symbol], + [:before_save, :symbol], + [:before_save, :string], + [:before_save, :string], + [:before_save, :combined_symbol], + ], person.history + end + end + + class ResetCallbackTest < Test::Unit::TestCase + def test_save_conditional_person + person = CleanPerson.new + person.save + assert_equal [], person.history + end + end + + class CallbackTerminator + include ActiveSupport::Callbacks + + define_callbacks :save, :terminator => "result == :halt" + + set_callback :save, :before, :first + set_callback :save, :before, :second + set_callback :save, :around, :around_it + set_callback :save, :before, :third + set_callback :save, :after, :first + set_callback :save, :around, :around_it + set_callback :save, :after, :second + set_callback :save, :around, :around_it + set_callback :save, :after, :third + + + attr_reader :history, :saved + def initialize + @history = [] + end + + def around_it + @history << "around1" + yield + @history << "around2" + end + + def first + @history << "first" + end + + def second + @history << "second" + :halt + end + + def third + @history << "third" + end + + def save + run_callbacks :save do + @saved = true + end + end + end + + class CallbackObject + def before(caller) + caller.record << "before" + end + + def before_save(caller) + caller.record << "before save" + end + + def around(caller) + caller.record << "around before" + yield + caller.record << "around after" + end + end + + class UsingObjectBefore + include ActiveSupport::Callbacks + + define_callbacks :save + set_callback :save, :before, CallbackObject.new + + attr_accessor :record + def initialize + @record = [] + end + + def save + run_callbacks :save do + @record << "yielded" + end + end end -end -class CallbackChainTest < Test::Unit::TestCase - include ActiveSupport::Callbacks + class UsingObjectAround + include ActiveSupport::Callbacks - def setup - @chain = CallbackChain.build(:make, :bacon, :lettuce, :tomato) + define_callbacks :save + set_callback :save, :around, CallbackObject.new + + attr_accessor :record + def initialize + @record = [] + end + + def save + run_callbacks :save do + @record << "yielded" + end + end end - def test_build - assert_equal 3, @chain.size - assert_equal [:bacon, :lettuce, :tomato], @chain.map(&:method) + class CustomScopeObject + include ActiveSupport::Callbacks + + define_callbacks :save, :scope => [:kind, :name] + set_callback :save, :before, CallbackObject.new + + attr_accessor :record + def initialize + @record = [] + end + + def save + run_callbacks :save do + @record << "yielded" + "CallbackResult" + end + end end - def test_find - assert_equal :bacon, @chain.find(:bacon).method + class UsingObjectTest < Test::Unit::TestCase + def test_before_object + u = UsingObjectBefore.new + u.save + assert_equal ["before", "yielded"], u.record + end + + def test_around_object + u = UsingObjectAround.new + u.save + assert_equal ["around before", "yielded", "around after"], u.record + end + + def test_customized_object + u = CustomScopeObject.new + u.save + assert_equal ["before save", "yielded"], u.record + end + + def test_block_result_is_returned + u = CustomScopeObject.new + assert_equal "CallbackResult", u.save + end end - def test_replace_or_append - assert_equal [:bacon, :lettuce, :tomato], (@chain.replace_or_append!(Callback.new(:make, :bacon))).map(&:method) - assert_equal [:bacon, :lettuce, :tomato, :turkey], (@chain.replace_or_append!(Callback.new(:make, :turkey))).map(&:method) - assert_equal [:bacon, :lettuce, :tomato, :turkey, :mayo], (@chain.replace_or_append!(Callback.new(:make, :mayo))).map(&:method) + class CallbackTerminatorTest < Test::Unit::TestCase + def test_termination + terminator = CallbackTerminator.new + terminator.save + assert_equal ["first", "second", "third", "second", "first"], terminator.history + end + + def test_block_never_called_if_terminated + obj = CallbackTerminator.new + obj.save + assert !obj.saved + end end - def test_delete - assert_equal [:bacon, :lettuce, :tomato], @chain.map(&:method) - @chain.delete(:bacon) - assert_equal [:lettuce, :tomato], @chain.map(&:method) + class HyphenatedKeyTest < Test::Unit::TestCase + def test_save + obj = HyphenatedCallbacks.new + obj.save + assert_equal obj.stuff, "OMG" + end end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 8198b9bd2c..f5f91ddd80 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -339,6 +339,11 @@ class ArrayWrapperTests < Test::Unit::TestCase end end + class Proxy + def initialize(target) @target = target end + def method_missing(*a) @target.send(*a) end + end + def test_array ary = %w(foo bar) assert_same ary, Array.wrap(ary) @@ -364,4 +369,19 @@ class ArrayWrapperTests < Test::Unit::TestCase def test_object_with_to_ary assert_equal ["foo", "bar"], Array.wrap(FakeCollection.new) end + + def test_proxy_object + p = Proxy.new(Object.new) + assert_equal [p], Array.wrap(p) + end + + def test_proxy_to_object_with_to_ary + p = Proxy.new(FakeCollection.new) + assert_equal [p], Array.wrap(p) + end + + def test_struct + o = Struct.new(:foo).new(123) + assert_equal [o], Array.wrap(o) + end end diff --git a/activesupport/test/core_ext/boolean_ext_test.rb b/activesupport/test/core_ext/boolean_ext_test.rb deleted file mode 100644 index 9439716efb..0000000000 --- a/activesupport/test/core_ext/boolean_ext_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/boolean/conversions' - -class BooleanExtAccessTests < Test::Unit::TestCase - def test_to_param_on_true - assert_equal true, true.to_param - end - - def test_to_param_on_false - assert_equal false, false.to_param - end -end diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index b51d68551d..beb55ba17e 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -52,6 +52,13 @@ class DelegatingAttributesTest < Test::Unit::TestCase assert !single_class.public_instance_methods.map(&:to_s).include?("both=") end + def test_simple_accessor_declaration_with_instance_reader_false + single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false + assert single_class.respond_to?(:no_instance_reader) + assert single_class.respond_to?(:no_instance_reader=) + assert !single_class.public_instance_methods.map(&:to_s).include?("no_instance_reader") + end + def test_working_with_simple_attributes single_class.superclass_delegating_accessor :both diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 18422d68bc..23c9bc7fb1 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -1,5 +1,5 @@ require 'abstract_unit' -require 'active_support/core_ext/date' +require 'active_support/time' class DateExtCalculationsTest < Test::Unit::TestCase def test_to_s diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index a7b179b2be..4341ead488 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -1,5 +1,5 @@ require 'abstract_unit' -require 'active_support/core_ext/date_time' +require 'active_support/time' class DateTimeExtCalculationsTest < Test::Unit::TestCase def test_to_s diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 4170de3dce..66f5f9fbde 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'active_support/core_ext/array' -require 'active_support/core_ext/symbol' require 'active_support/core_ext/enumerable' Payment = Struct.new(:price) @@ -90,15 +89,4 @@ class EnumerableTests < Test::Unit::TestCase assert ![ 1, 2 ].many? {|x| x > 1 } assert [ 1, 2, 2 ].many? {|x| x > 1 } end - - def test_none - assert [].none? - assert [nil, false].none? - assert ![1].none? - - assert [].none? {|x| x > 1 } - assert ![ 2 ].none? {|x| x > 1 } - assert ![ 1, 2 ].none? {|x| x > 1 } - assert [ 1, 1 ].none? {|x| x > 1 } - end end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index eb4c37aaf0..4642bb1330 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -899,42 +899,6 @@ class HashToXmlTest < Test::Unit::TestCase # :builder, etc, shouldn't be added to options assert_equal({:skip_instruct => true}, options) end -end - -class QueryTest < Test::Unit::TestCase - def test_simple_conversion - assert_query_equal 'a=10', :a => 10 - end - - def test_cgi_escaping - assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d' - end - - def test_nil_parameter_value - empty = Object.new - def empty.to_param; nil end - assert_query_equal 'a=', 'a' => empty - end - - def test_nested_conversion - assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', - :person => {:name => 'Nicholas', :login => 'seckar'} - end - - def test_multiple_nested - assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', - :person => {:id => 10}, :account => {:person => {:id => 20}} - end - - def test_array_values - assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20', - :person => {:id => [10, 20]} - end - - def test_array_values_are_not_sorted - assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10', - :person => {:id => [20, 10]} - end def test_expansion_count_is_limited expected = { @@ -962,9 +926,4 @@ class QueryTest < Test::Unit::TestCase Hash.from_xml(attack_xml) end end - - private - def assert_query_equal(expected, actual, message = nil) - assert_equal expected.split('&'), actual.to_query.split('&') - end end diff --git a/activesupport/test/core_ext/integer_ext_test.rb b/activesupport/test/core_ext/integer_ext_test.rb index 956ae5189d..e1591089f5 100644 --- a/activesupport/test/core_ext/integer_ext_test.rb +++ b/activesupport/test/core_ext/integer_ext_test.rb @@ -2,22 +2,6 @@ require 'abstract_unit' require 'active_support/core_ext/integer' class IntegerExtTest < Test::Unit::TestCase - def test_even - assert [ -2, 0, 2, 4 ].all? { |i| i.even? } - assert ![ -1, 1, 3 ].all? { |i| i.even? } - - assert 22953686867719691230002707821868552601124472329079.odd? - assert !22953686867719691230002707821868552601124472329079.even? - assert 22953686867719691230002707821868552601124472329080.even? - assert !22953686867719691230002707821868552601124472329080.odd? - end - - def test_odd - assert ![ -2, 0, 2, 4 ].all? { |i| i.odd? } - assert [ -1, 1, 3 ].all? { |i| i.odd? } - assert 1000000000000000000000000000000000000000000000000000000001.odd? - end - def test_multiple_of [ -7, 0, 7, 14 ].each { |i| assert i.multiple_of?(7) } [ -7, 7, 14 ].each { |i| assert ! i.multiple_of?(6) } diff --git a/activesupport/test/core_ext/name_error_test.rb b/activesupport/test/core_ext/name_error_test.rb index 10913e2ade..6352484d04 100644 --- a/activesupport/test/core_ext/name_error_test.rb +++ b/activesupport/test/core_ext/name_error_test.rb @@ -3,23 +3,19 @@ require 'active_support/core_ext/name_error' class NameErrorTest < Test::Unit::TestCase def test_name_error_should_set_missing_name - begin - SomeNameThatNobodyWillUse____Really ? 1 : 0 - flunk "?!?!" - rescue NameError => exc - assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name - assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really) - assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really") - end + SomeNameThatNobodyWillUse____Really ? 1 : 0 + flunk "?!?!" + rescue NameError => exc + assert_equal "NameErrorTest::SomeNameThatNobodyWillUse____Really", exc.missing_name + assert exc.missing_name?(:SomeNameThatNobodyWillUse____Really) + assert exc.missing_name?("NameErrorTest::SomeNameThatNobodyWillUse____Really") end def test_missing_method_should_ignore_missing_name - begin - some_method_that_does_not_exist - flunk "?!?!" - rescue NameError => exc - assert_equal nil, exc.missing_name - assert ! exc.missing_name?(:Foo) - end + some_method_that_does_not_exist + flunk "?!?!" + rescue NameError => exc + assert !exc.missing_name?(:Foo) + assert_nil exc.missing_name end end diff --git a/activesupport/test/core_ext/nil_ext_test.rb b/activesupport/test/core_ext/nil_ext_test.rb deleted file mode 100644 index 1062676d65..0000000000 --- a/activesupport/test/core_ext/nil_ext_test.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/nil/conversions' - -class NilExtAccessTests < Test::Unit::TestCase - def test_to_param - assert_nil nil.to_param - end -end diff --git a/activesupport/test/core_ext/object/to_param_test.rb b/activesupport/test/core_ext/object/to_param_test.rb new file mode 100644 index 0000000000..c3efefddb5 --- /dev/null +++ b/activesupport/test/core_ext/object/to_param_test.rb @@ -0,0 +1,19 @@ +require 'abstract_unit' +require 'active_support/core_ext/object/to_param' + +class ToParamTest < Test::Unit::TestCase + def test_object + foo = Object.new + def foo.to_s; 'foo' end + assert_equal 'foo', foo.to_param + end + + def test_nil + assert_nil nil.to_param + end + + def test_boolean + assert_equal true, true.to_param + assert_equal false, false.to_param + end +end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb new file mode 100644 index 0000000000..0fb15be654 --- /dev/null +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -0,0 +1,43 @@ +require 'abstract_unit' +require 'active_support/core_ext/object/to_query' + +class ToQueryTest < Test::Unit::TestCase + def test_simple_conversion + assert_query_equal 'a=10', :a => 10 + end + + def test_cgi_escaping + assert_query_equal 'a%3Ab=c+d', 'a:b' => 'c d' + end + + def test_nil_parameter_value + empty = Object.new + def empty.to_param; nil end + assert_query_equal 'a=', 'a' => empty + end + + def test_nested_conversion + assert_query_equal 'person%5Blogin%5D=seckar&person%5Bname%5D=Nicholas', + :person => {:name => 'Nicholas', :login => 'seckar'} + end + + def test_multiple_nested + assert_query_equal 'account%5Bperson%5D%5Bid%5D=20&person%5Bid%5D=10', + :person => {:id => 10}, :account => {:person => {:id => 20}} + end + + def test_array_values + assert_query_equal 'person%5Bid%5D%5B%5D=10&person%5Bid%5D%5B%5D=20', + :person => {:id => [10, 20]} + end + + def test_array_values_are_not_sorted + assert_query_equal 'person%5Bid%5D%5B%5D=20&person%5Bid%5D%5B%5D=10', + :person => {:id => [20, 10]} + end + + private + def assert_query_equal(expected, actual, message = nil) + assert_equal expected.split('&'), actual.to_query.split('&') + end +end diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index f0121b862d..e6fbdb637b 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -182,13 +182,6 @@ class ObjectInstanceVariableTest < Test::Unit::TestCase assert_equal %w(@bar @baz), @source.instance_variable_names.sort end - def test_instance_variable_defined - assert @source.instance_variable_defined?('@bar') - assert @source.instance_variable_defined?(:@bar) - assert !@source.instance_variable_defined?(:@foo) - assert !@source.instance_variable_defined?('@foo') - end - def test_copy_instance_variables_from_without_explicit_excludes assert_equal [], @dest.instance_variables @dest.copy_instance_variables_from(@source) diff --git a/activesupport/test/core_ext/object_ext_test.rb b/activesupport/test/core_ext/object_ext_test.rb deleted file mode 100644 index 484eecaab6..0000000000 --- a/activesupport/test/core_ext/object_ext_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object/metaclass' -require 'active_support/core_ext/object/conversions' - -class ObjectExtTest < Test::Unit::TestCase - def test_tap_yields_and_returns_self - foo = Object.new - assert_equal foo, foo.tap { |x| assert_equal foo, x; :bar } - end - - def test_to_param - foo = Object.new - foo.class_eval("def to_s; 'foo'; end") - assert_equal 'foo', foo.to_param - end -end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 2565c56b8a..5701eeef28 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' +require 'active_support/time' require 'active_support/core_ext/range' -require 'active_support/core_ext/date/conversions' class RangeTest < Test::Unit::TestCase def test_to_s_from_dates diff --git a/activesupport/test/core_ext/regexp_ext_test.rb b/activesupport/test/core_ext/regexp_ext_test.rb index cc3f07d5c5..68b089d5b4 100644 --- a/activesupport/test/core_ext/regexp_ext_test.rb +++ b/activesupport/test/core_ext/regexp_ext_test.rb @@ -2,28 +2,9 @@ require 'abstract_unit' require 'active_support/core_ext/regexp' class RegexpExtAccessTests < Test::Unit::TestCase - def test_number_of_captures - assert_equal 0, //.number_of_captures - assert_equal 1, /.(.)./.number_of_captures - assert_equal 2, /.(.).(?:.).(.)/.number_of_captures - assert_equal 3, /.((.).(?:.).(.))/.number_of_captures - end - def test_multiline assert_equal true, //m.multiline? assert_equal false, //.multiline? assert_equal false, /(?m:)/.multiline? end - - def test_optionalize - assert_equal "a?", Regexp.optionalize("a") - assert_equal "(?:foo)?", Regexp.optionalize("foo") - assert_equal "", Regexp.optionalize("") - end - - def test_unoptionalize - assert_equal "a", Regexp.unoptionalize("a?") - assert_equal "foo", Regexp.unoptionalize("(?:foo)?") - assert_equal "", Regexp.unoptionalize("") - end end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 584a41b631..56ed296dac 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -4,7 +4,8 @@ require 'abstract_unit' require 'inflector_test_cases' require 'active_support/core_ext/string' -require 'active_support/core_ext/time' +require 'active_support/time' +require 'active_support/core_ext/kernel/reporting' class StringInflectionsTest < Test::Unit::TestCase include InflectorTestCases @@ -185,17 +186,9 @@ class StringInflectionsTest < Test::Unit::TestCase assert s.starts_with?('hel') assert !s.starts_with?('el') - assert s.start_with?('h') - assert s.start_with?('hel') - assert !s.start_with?('el') - assert s.ends_with?('o') assert s.ends_with?('lo') assert !s.ends_with?('el') - - assert s.end_with?('o') - assert s.end_with?('lo') - assert !s.end_with?('el') end def test_string_squish @@ -214,17 +207,6 @@ class StringInflectionsTest < Test::Unit::TestCase # And changes the original string: assert_equal original, expected end - - if RUBY_VERSION < '1.9' - def test_each_char_with_utf8_string_when_kcode_is_utf8 - with_kcode('UTF8') do - '€2.99'.each_char do |char| - assert_not_equal 1, char.length - break - end - end - end - end end class StringBehaviourTest < Test::Unit::TestCase @@ -350,13 +332,6 @@ class TestGetTextString < Test::Unit::TestCase end end -class StringBytesizeTest < Test::Unit::TestCase - def test_bytesize - assert_respond_to 'foo', :bytesize - assert_equal 3, 'foo'.bytesize - end -end - class OutputSafetyTest < ActiveSupport::TestCase def setup @string = "hello" diff --git a/activesupport/test/core_ext/symbol_test.rb b/activesupport/test/core_ext/symbol_test.rb deleted file mode 100644 index 1eaccb9965..0000000000 --- a/activesupport/test/core_ext/symbol_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'abstract_unit' - -class SymbolTests < Test::Unit::TestCase - def test_to_proc - assert_equal %w(one two three), [:one, :two, :three].map(&:to_s) - assert_equal(%w(one two three), - {1 => "one", 2 => "two", 3 => "three"}.sort_by(&:first).map(&:last)) - end -end diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb index 2d4f38d095..e4a242abc4 100644 --- a/activesupport/test/core_ext/uri_ext_test.rb +++ b/activesupport/test/core_ext/uri_ext_test.rb @@ -7,7 +7,11 @@ class URIExtTest < Test::Unit::TestCase str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. str.force_encoding(Encoding::UTF_8) if str.respond_to?(:force_encoding) - assert_equal str, URI.unescape(URI.escape(str)) - assert_equal str, URI.decode(URI.escape(str)) + if URI.const_defined?(:Parser) + parser = URI::Parser.new + assert_equal str, parser.unescape(parser.escape(str)) + else + assert_equal str, URI.unescape(URI.escape(str)) + end end end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 97d70cf8c4..0fcf1eaf00 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -3,7 +3,6 @@ require 'pp' require 'active_support/dependencies' require 'active_support/core_ext/module/loading' require 'active_support/core_ext/kernel/reporting' -require 'active_support/core_ext/symbol/to_proc' module ModuleWithMissing mattr_accessor :missing_count diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index 9868f1e87d..dfcd4f822d 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -9,8 +9,8 @@ class I18nTest < Test::Unit::TestCase end def test_time_zone_localization_with_default_format - Time.zone.stubs(:now).returns Time.local(2000) - assert_equal Time.zone.now.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(Time.zone.now) + now = Time.local(2000) + assert_equal now.strftime("%a, %d %b %Y %H:%M:%S %z"), I18n.localize(now) end def test_date_localization_should_use_default_format diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 7b5a4d0416..8fcb16abfb 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -1,6 +1,7 @@ # encoding: UTF-8 require 'abstract_unit' require 'active_support/json' +require 'active_support/time' require 'active_support/core_ext/kernel/reporting' class TestJSONDecoding < ActiveSupport::TestCase diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index ed3461571a..5c2b44f188 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/time' class MessageEncryptorTest < Test::Unit::TestCase def setup diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index ef300e4e26..714a3b3a39 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -7,6 +7,8 @@ rescue LoadError, NameError $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install" else +require 'active_support/time' + class MessageVerifierTest < Test::Unit::TestCase def setup @verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!") diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 680936ded5..0e489c10e1 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -100,9 +100,14 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase def setup @chars = UNICODE_STRING.dup.mb_chars - # NEWLINE, SPACE, EM SPACE - @whitespace = "\n#{[32, 8195].pack('U*')}" - @whitespace.force_encoding(Encoding::UTF_8) if @whitespace.respond_to?(:force_encoding) + if RUBY_VERSION < '1.9' + # Multibyte support all kinds of whitespace (ie. NEWLINE, SPACE, EM SPACE) + @whitespace = "\n\t#{[32, 8195].pack('U*')}" + else + # Ruby 1.9 only supports basic whitespace + @whitespace = "\n\t ".force_encoding(Encoding::UTF_8) + end + @byte_order_mark = [65279].pack('U') end @@ -163,6 +168,7 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert chars('').strip.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars('').reverse.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars(' ').slice(0).kind_of?(ActiveSupport::Multibyte.proxy_class) + assert chars('').limit(0).kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars('').upcase.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars('').downcase.kind_of?(ActiveSupport::Multibyte.proxy_class) assert chars('').capitalize.kind_of?(ActiveSupport::Multibyte.proxy_class) @@ -190,7 +196,9 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase def test_should_return_character_offset_for_regexp_matches assert_nil(@chars =~ /wrong/u) assert_equal 0, (@chars =~ /こ/u) + assert_equal 0, (@chars =~ /こに/u) assert_equal 1, (@chars =~ /に/u) + assert_equal 2, (@chars =~ /ち/u) assert_equal 3, (@chars =~ /わ/u) end @@ -220,10 +228,10 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert !@chars.include?('a') end - def test_include_raises_type_error_when_nil_is_passed - assert_raise(TypeError) do - @chars.include?(nil) - end + def test_include_raises_when_nil_is_passed + @chars.include?(nil) + flunk "Expected chars.include?(nil) to raise TypeError or NoMethodError" + rescue Exception => e end def test_index_should_return_character_offset @@ -378,6 +386,17 @@ class MultibyteCharsUTF8BehaviourTest < Test::Unit::TestCase assert_equal 'わちにこ', @chars.reverse end + def test_reverse_should_work_with_normalized_strings + str = 'bös' + reversed_str = 'söb' + assert_equal chars(reversed_str).normalize(:kc), chars(str).normalize(:kc).reverse + assert_equal chars(reversed_str).normalize(:c), chars(str).normalize(:c).reverse + assert_equal chars(reversed_str).normalize(:d), chars(str).normalize(:d).reverse + assert_equal chars(reversed_str).normalize(:kd), chars(str).normalize(:kd).reverse + assert_equal chars(reversed_str).decompose, chars(str).decompose.reverse + assert_equal chars(reversed_str).compose, chars(str).compose.reverse + end + def test_slice_should_take_character_offsets assert_equal nil, ''.mb_chars.slice(0) assert_equal 'こ', @chars.slice(0) @@ -476,6 +495,43 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase end end + def test_limit_should_not_break_on_blank_strings + example = chars('') + assert_equal example, example.limit(0) + assert_equal example, example.limit(1) + end + + def test_limit_should_work_on_a_multibyte_string + example = chars(UNICODE_STRING) + bytesize = UNICODE_STRING.respond_to?(:bytesize) ? UNICODE_STRING.bytesize : UNICODE_STRING.size + + assert_equal UNICODE_STRING, example.limit(bytesize) + assert_equal '', example.limit(0) + assert_equal '', example.limit(1) + assert_equal 'こ', example.limit(3) + assert_equal 'こに', example.limit(6) + assert_equal 'こに', example.limit(8) + assert_equal 'こにち', example.limit(9) + assert_equal 'こにちわ', example.limit(50) + end + + def test_limit_should_work_on_an_ascii_string + ascii = chars(ASCII_STRING) + assert_equal ASCII_STRING, ascii.limit(ASCII_STRING.length) + assert_equal '', ascii.limit(0) + assert_equal 'o', ascii.limit(1) + assert_equal 'oh', ascii.limit(2) + assert_equal 'ohay', ascii.limit(4) + assert_equal 'ohayo', ascii.limit(50) + end + + def test_limit_should_keep_under_the_specified_byte_limit + example = chars(UNICODE_STRING) + (1..UNICODE_STRING.length).each do |limit| + assert example.limit(limit).to_s.length <= limit + end + end + def test_composition_exclusion_is_set_up_properly # Normalization of DEVANAGARI LETTER QA breaks when composition exclusion isn't used correctly qa = [0x915, 0x93c].pack('U*') @@ -586,3 +642,21 @@ class MultibyteCharsExtrasTest < Test::Unit::TestCase end.pack('U*') end end + +class MultibyteInternalsTest < ActiveSupport::TestCase + include MultibyteTestHelpers + + test "Chars translates a character offset to a byte offset" do + example = chars("Puisque c'était son erreur, il m'a aidé") + [ + [0, 0], + [3, 3], + [12, 11], + [14, 13], + [41, 39] + ].each do |byte_offset, character_offset| + assert_equal character_offset, example.send(:translate_offset, byte_offset), + "Expected byte offset #{byte_offset} to translate to #{character_offset}" + end + end +end diff --git a/activesupport/test/multibyte_utils_test.rb b/activesupport/test/multibyte_utils_test.rb index 0a2f20d282..1dff944922 100644 --- a/activesupport/test/multibyte_utils_test.rb +++ b/activesupport/test/multibyte_utils_test.rb @@ -2,6 +2,7 @@ require 'abstract_unit' require 'multibyte_test_helpers' +require 'active_support/core_ext/kernel/reporting' class MultibyteUtilsTest < ActiveSupport::TestCase include MultibyteTestHelpers diff --git a/activesupport/test/new_callbacks_test.rb b/activesupport/test/new_callbacks_test.rb deleted file mode 100644 index 04db376fc6..0000000000 --- a/activesupport/test/new_callbacks_test.rb +++ /dev/null @@ -1,528 +0,0 @@ -# require 'abstract_unit' -require 'test/unit' -$:.unshift "#{File.dirname(__FILE__)}/../lib" -require 'active_support' - -module NewCallbacksTest - class Record - include ActiveSupport::NewCallbacks - - define_callbacks :save - - def self.before_save(*filters, &blk) - set_callback(:save, :before, *filters, &blk) - end - - def self.after_save(*filters, &blk) - set_callback(:save, :after, *filters, &blk) - end - - class << self - def callback_symbol(callback_method) - method_name = :"#{callback_method}_method" - define_method(method_name) do - history << [callback_method, :symbol] - end - method_name - end - - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - - def callback_proc(callback_method) - Proc.new { |model| model.history << [callback_method, :proc] } - end - - def callback_object(callback_method) - klass = Class.new - klass.send(:define_method, callback_method) do |model| - model.history << [:"#{callback_method}_save", :object] - end - klass.new - end - end - - def history - @history ||= [] - end - end - - class Person < Record - [:before_save, :after_save].each do |callback_method| - callback_method_sym = callback_method.to_sym - send(callback_method, callback_symbol(callback_method_sym)) - send(callback_method, callback_string(callback_method_sym)) - send(callback_method, callback_proc(callback_method_sym)) - send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ''))) - send(callback_method) { |model| model.history << [callback_method_sym, :block] } - end - - def save - _run_save_callbacks {} - end - end - - class PersonSkipper < Person - skip_callback :save, :before, :before_save_method, :if => :yes - skip_callback :save, :after, :before_save_method, :unless => :yes - skip_callback :save, :after, :before_save_method, :if => :no - skip_callback :save, :before, :before_save_method, :unless => :no - def yes; true; end - def no; false; end - end - - class ParentController - include ActiveSupport::NewCallbacks - - define_callbacks :dispatch - - set_callback :dispatch, :before, :log, :per_key => {:unless => proc {|c| c.action_name == :index || c.action_name == :show }} - set_callback :dispatch, :after, :log2 - - attr_reader :action_name, :logger - def initialize(action_name) - @action_name, @logger = action_name, [] - end - - def log - @logger << action_name - end - - def log2 - @logger << action_name - end - - def dispatch - _run_dispatch_callbacks(action_name) { - @logger << "Done" - } - self - end - end - - class Child < ParentController - skip_callback :dispatch, :before, :log, :per_key => {:if => proc {|c| c.action_name == :update} } - skip_callback :dispatch, :after, :log2 - end - - class OneTimeCompile < Record - @@starts_true, @@starts_false = true, false - - def initialize - super - end - - before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :per_key => {:if => :starts_true} - before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :per_key => {:if => :starts_false} - before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :per_key => {:unless => :starts_true} - before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :per_key => {:unless => :starts_false} - - def starts_true - if @@starts_true - @@starts_true = false - return true - end - @@starts_true - end - - def starts_false - unless @@starts_false - @@starts_false = true - return false - end - @@starts_false - end - - def save - _run_save_callbacks(:action) {} - end - end - - class OneTimeCompileTest < Test::Unit::TestCase - def test_optimized_first_compile - around = OneTimeCompile.new - around.save - assert_equal [ - [:before_save, :starts_true, :if], - [:before_save, :starts_true, :unless] - ], around.history - end - end - - class ConditionalPerson < Record - # proc - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true } - before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false } - before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false } - before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true } - # symbol - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes - before_save Proc.new { |r| r.history << "b00m" }, :if => :no - before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes - # string - before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes' - before_save Proc.new { |r| r.history << "b00m" }, :if => 'no' - before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no' - before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes' - # Combined if and unless - before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no - before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes - - def yes; true; end - def other_yes; true; end - def no; false; end - def other_no; false; end - - def save - _run_save_callbacks {} - end - end - - class CleanPerson < ConditionalPerson - reset_callbacks :save - end - - class MySuper - include ActiveSupport::NewCallbacks - define_callbacks :save - end - - class AroundPerson < MySuper - attr_reader :history - - set_callback :save, :before, :nope, :if => :no - set_callback :save, :before, :nope, :unless => :yes - set_callback :save, :after, :tweedle - set_callback :save, :before, "tweedle_dee" - set_callback :save, :before, proc {|m| m.history << "yup" } - set_callback :save, :before, :nope, :if => proc { false } - set_callback :save, :before, :nope, :unless => proc { true } - set_callback :save, :before, :yup, :if => proc { true } - set_callback :save, :before, :yup, :unless => proc { false } - set_callback :save, :around, :tweedle_dum - set_callback :save, :around, :w0tyes, :if => :yes - set_callback :save, :around, :w0tno, :if => :no - set_callback :save, :around, :tweedle_deedle - - def no; false; end - def yes; true; end - - def nope - @history << "boom" - end - - def yup - @history << "yup" - end - - def w0tyes - @history << "w0tyes before" - yield - @history << "w0tyes after" - end - - def w0tno - @history << "boom" - yield - end - - def tweedle_dee - @history << "tweedle dee" - end - - def tweedle_dum - @history << "tweedle dum pre" - yield - @history << "tweedle dum post" - end - - def tweedle - @history << "tweedle" - end - - def tweedle_deedle - @history << "tweedle deedle pre" - yield - @history << "tweedle deedle post" - end - - def initialize - @history = [] - end - - def save - _run_save_callbacks do - @history << "running" - end - end - end - - class HyphenatedCallbacks - include ActiveSupport::NewCallbacks - define_callbacks :save - attr_reader :stuff - - set_callback :save, :before, :omg, :per_key => {:if => :yes} - - def yes() true end - - def omg - @stuff = "OMG" - end - - def save - _run_save_callbacks("hyphen-ated") do - @stuff - end - end - end - - class AroundCallbacksTest < Test::Unit::TestCase - def test_save_around - around = AroundPerson.new - around.save - assert_equal [ - "tweedle dee", - "yup", "yup", - "tweedle dum pre", - "w0tyes before", - "tweedle deedle pre", - "running", - "tweedle deedle post", - "w0tyes after", - "tweedle dum post", - "tweedle" - ], around.history - end - end - - class SkipCallbacksTest < Test::Unit::TestCase - def test_skip_person - person = PersonSkipper.new - assert_equal [], person.history - person.save - assert_equal [ - [:before_save, :string], - [:before_save, :proc], - [:before_save, :object], - [:before_save, :block], - [:after_save, :block], - [:after_save, :object], - [:after_save, :proc], - [:after_save, :string], - [:after_save, :symbol] - ], person.history - end - end - - class CallbacksTest < Test::Unit::TestCase - def test_save_person - person = Person.new - assert_equal [], person.history - person.save - assert_equal [ - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :proc], - [:before_save, :object], - [:before_save, :block], - [:after_save, :block], - [:after_save, :object], - [:after_save, :proc], - [:after_save, :string], - [:after_save, :symbol] - ], person.history - end - end - - class ConditionalCallbackTest < Test::Unit::TestCase - def test_save_conditional_person - person = ConditionalPerson.new - person.save - assert_equal [ - [:before_save, :proc], - [:before_save, :proc], - [:before_save, :symbol], - [:before_save, :symbol], - [:before_save, :string], - [:before_save, :string], - [:before_save, :combined_symbol], - ], person.history - end - end - - class ResetCallbackTest < Test::Unit::TestCase - def test_save_conditional_person - person = CleanPerson.new - person.save - assert_equal [], person.history - end - end - - class CallbackTerminator - include ActiveSupport::NewCallbacks - - define_callbacks :save, :terminator => "result == :halt" - - set_callback :save, :before, :first - set_callback :save, :before, :second - set_callback :save, :around, :around_it - set_callback :save, :before, :third - set_callback :save, :after, :first - set_callback :save, :around, :around_it - set_callback :save, :after, :second - set_callback :save, :around, :around_it - set_callback :save, :after, :third - - - attr_reader :history, :saved - def initialize - @history = [] - end - - def around_it - @history << "around1" - yield - @history << "around2" - end - - def first - @history << "first" - end - - def second - @history << "second" - :halt - end - - def third - @history << "third" - end - - def save - _run_save_callbacks do - @saved = true - end - end - end - - class CallbackObject - def before(caller) - caller.record << "before" - end - - def before_save(caller) - caller.record << "before save" - end - - def around(caller) - caller.record << "around before" - yield - caller.record << "around after" - end - end - - class UsingObjectBefore - include ActiveSupport::NewCallbacks - - define_callbacks :save - set_callback :save, :before, CallbackObject.new - - attr_accessor :record - def initialize - @record = [] - end - - def save - _run_save_callbacks do - @record << "yielded" - end - end - end - - class UsingObjectAround - include ActiveSupport::NewCallbacks - - define_callbacks :save - set_callback :save, :around, CallbackObject.new - - attr_accessor :record - def initialize - @record = [] - end - - def save - _run_save_callbacks do - @record << "yielded" - end - end - end - - class CustomScopeObject - include ActiveSupport::NewCallbacks - - define_callbacks :save, :scope => [:kind, :name] - set_callback :save, :before, CallbackObject.new - - attr_accessor :record - def initialize - @record = [] - end - - def save - _run_save_callbacks do - @record << "yielded" - "CallbackResult" - end - end - end - - class UsingObjectTest < Test::Unit::TestCase - def test_before_object - u = UsingObjectBefore.new - u.save - assert_equal ["before", "yielded"], u.record - end - - def test_around_object - u = UsingObjectAround.new - u.save - assert_equal ["around before", "yielded", "around after"], u.record - end - - def test_customized_object - u = CustomScopeObject.new - u.save - assert_equal ["before save", "yielded"], u.record - end - - def test_block_result_is_returned - u = CustomScopeObject.new - assert_equal "CallbackResult", u.save - end - end - - class CallbackTerminatorTest < Test::Unit::TestCase - def test_termination - terminator = CallbackTerminator.new - terminator.save - assert_equal ["first", "second", "third", "second", "first"], terminator.history - end - - def test_block_never_called_if_terminated - obj = CallbackTerminator.new - obj.save - assert !obj.saved - end - end - - class HyphenatedKeyTest < Test::Unit::TestCase - def test_save - obj = HyphenatedCallbacks.new - obj.save - assert_equal obj.stuff, "OMG" - end - end -end diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb new file mode 100644 index 0000000000..01106e83e9 --- /dev/null +++ b/activesupport/test/notifications_test.rb @@ -0,0 +1,206 @@ +require 'abstract_unit' + +# Allow LittleFanout to be cleaned. +class ActiveSupport::Notifications::LittleFanout + def clear + @listeners.clear + end +end + +class NotificationsEventTest < Test::Unit::TestCase + def test_events_are_initialized_with_details + event = event(:foo, Time.now, Time.now + 1, 1, random_id, :payload => :bar) + assert_equal :foo, event.name + assert_equal Hash[:payload => :bar], event.payload + end + + def test_events_consumes_information_given_as_payload + time = Time.now + event = event(:foo, time, time + 0.01, 1, random_id, {}) + + assert_equal Hash.new, event.payload + assert_equal time, event.time + assert_equal 1, event.result + assert_equal 10.0, event.duration + end + + def test_event_is_parent_based_on_time_frame + time = Time.utc(2009, 01, 01, 0, 0, 1) + + parent = event(:foo, Time.utc(2009), Time.utc(2009) + 100, nil, random_id, {}) + child = event(:foo, time, time + 10, nil, random_id, {}) + not_child = event(:foo, time, time + 100, nil, random_id, {}) + + assert parent.parent_of?(child) + assert !child.parent_of?(parent) + assert !parent.parent_of?(not_child) + assert !not_child.parent_of?(parent) + end + +protected + + def random_id + @random_id ||= ActiveSupport::SecureRandom.hex(10) + end + + def event(*args) + ActiveSupport::Notifications::Event.new(*args) + end +end + +class NotificationsMainTest < Test::Unit::TestCase + def setup + @events = [] + Thread.abort_on_exception = true + ActiveSupport::Notifications.subscribe do |*args| + @events << ActiveSupport::Notifications::Event.new(*args) + end + end + + def teardown + Thread.abort_on_exception = false + ActiveSupport::Notifications.queue.clear + end + + def test_notifications_returns_action_result + result = ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + 1 + 1 + end + + assert_equal 2, result + end + + def test_events_are_published_to_a_listener + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + 1 + 1 + end + + drain + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_nested_events_can_be_instrumented + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + ActiveSupport::Notifications.instrument(:wot, :payload => "child") do + 1 + 1 + end + + drain + + assert_equal 1, @events.size + assert_equal :wot, @events.first.name + assert_equal Hash[:payload => "child"], @events.first.payload + end + + drain + + assert_equal 2, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_event_is_pushed_even_if_block_fails + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") do + raise "OMG" + end rescue RuntimeError + + drain + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_event_is_pushed_even_without_block + ActiveSupport::Notifications.instrument(:awesome, :payload => "notifications") + drain + + assert_equal 1, @events.size + assert_equal :awesome, @events.last.name + assert_equal Hash[:payload => "notifications"], @events.last.payload + end + + def test_subscribed_in_a_transaction + @another = [] + + ActiveSupport::Notifications.subscribe("cache") do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.instrument(:cache){ 1 } + ActiveSupport::Notifications.transaction do + ActiveSupport::Notifications.instrument(:cache){ 1 } + end + ActiveSupport::Notifications.instrument(:cache){ 1 } + + drain + + assert_equal 3, @another.size + before, during, after = @another.map {|e| e.transaction_id } + assert_equal before, after + assert_not_equal before, during + end + + def test_subscriber_with_pattern + @another = [] + + ActiveSupport::Notifications.subscribe("cache") do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.instrument(:cache){ 1 } + + drain + + assert_equal 1, @another.size + assert_equal :cache, @another.first.name + assert_equal 1, @another.first.result + end + + def test_subscriber_with_pattern_as_regexp + @another = [] + ActiveSupport::Notifications.subscribe(/cache/) do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + ActiveSupport::Notifications.instrument(:something){ 0 } + ActiveSupport::Notifications.instrument(:cache){ 1 } + + drain + + assert_equal 1, @another.size + assert_equal :cache, @another.first.name + assert_equal 1, @another.first.result + end + + def test_with_several_consumers_and_several_events + @another = [] + ActiveSupport::Notifications.subscribe do |*args| + @another << ActiveSupport::Notifications::Event.new(*args) + end + + 1.upto(100) do |i| + ActiveSupport::Notifications.instrument(:value){ i } + end + + drain + + assert_equal 100, @events.size + assert_equal :value, @events.first.name + assert_equal 1, @events.first.result + assert_equal 100, @events.last.result + + assert_equal 100, @another.size + assert_equal :value, @another.first.name + assert_equal 1, @another.first.result + assert_equal 100, @another.last.result + end + + private + def drain + sleep(0.1) until ActiveSupport::Notifications.queue.drained? + end +end diff --git a/activesupport/test/orchestra_test.rb b/activesupport/test/orchestra_test.rb deleted file mode 100644 index 683cc36f6a..0000000000 --- a/activesupport/test/orchestra_test.rb +++ /dev/null @@ -1,161 +0,0 @@ -require 'abstract_unit' - -class OrchestraEventTest < Test::Unit::TestCase - def setup - @parent = ActiveSupport::Orchestra::Event.new(:parent) - end - - def test_initialization_with_name_and_parent_and_payload - event = ActiveSupport::Orchestra::Event.new(:awesome, @parent, :payload => "orchestra") - assert_equal(:awesome, event.name) - assert_equal(@parent, event.parent) - assert_equal({ :payload => "orchestra" }, event.payload) - end - - def test_thread_id_is_set_on_initialization - event = ActiveSupport::Orchestra::Event.new(:awesome) - assert_equal Thread.current.object_id, event.thread_id - end - - def test_current_time_is_set_on_initialization - previous_time = Time.now.utc - event = ActiveSupport::Orchestra::Event.new(:awesome) - assert_kind_of Time, event.time - assert event.time.to_f >= previous_time.to_f - end - - def test_duration_is_set_when_event_finishes - event = ActiveSupport::Orchestra::Event.new(:awesome) - sleep(0.1) - event.finish! - assert_in_delta 100, event.duration, 30 - end -end - -class OrchestraMainTest < Test::Unit::TestCase - def setup - @listener = [] - ActiveSupport::Orchestra.register @listener - end - - def teardown - ActiveSupport::Orchestra.unregister @listener - end - - def test_orchestra_allows_any_action_to_be_instrumented - event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - sleep(0.1) - end - - assert_equal :awesome, event.name - assert_equal "orchestra", event.payload - assert_in_delta 100, event.duration, 30 - end - - def test_block_result_is_stored - event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - 1 + 1 - end - - assert_equal 2, event.result - end - - def test_events_are_published_to_a_listener - event = ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - 1 + 1 - end - - assert_equal 1, @listener.size - assert_equal :awesome, @listener.last.name - assert_equal "orchestra", @listener.last.payload - end - - def test_nested_events_can_be_instrumented - ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - ActiveSupport::Orchestra.instrument(:wot, "child") do - sleep(0.1) - end - - assert_equal 1, @listener.size - assert_equal :wot, @listener.first.name - assert_equal "child", @listener.first.payload - - assert_nil @listener.first.parent.duration - assert_in_delta 100, @listener.first.duration, 30 - end - - assert_equal 2, @listener.size - assert_equal :awesome, @listener.last.name - assert_equal "orchestra", @listener.last.payload - assert_in_delta 100, @listener.first.parent.duration, 30 - end - - def test_event_is_pushed_even_if_block_fails - ActiveSupport::Orchestra.instrument(:awesome, "orchestra") do - raise "OMG" - end rescue RuntimeError - - assert_equal 1, @listener.size - assert_equal :awesome, @listener.last.name - assert_equal "orchestra", @listener.last.payload - end -end - -class OrchestraListenerTest < Test::Unit::TestCase - class MyListener < ActiveSupport::Orchestra::Listener - attr_reader :consumed - - def consume(event) - @consumed ||= [] - @consumed << event - end - end - - def setup - @listener = MyListener.new - ActiveSupport::Orchestra.register @listener - end - - def teardown - ActiveSupport::Orchestra.unregister @listener - end - - def test_thread_is_exposed_by_listener - assert_kind_of Thread, @listener.thread - end - - def test_event_is_consumed_when_an_action_is_instrumented - ActiveSupport::Orchestra.instrument(:sum) do - 1 + 1 - end - sleep 0.1 - assert_equal 1, @listener.consumed.size - assert_equal :sum, @listener.consumed.first.name - assert_equal 2, @listener.consumed.first.result - end - - def test_with_sevaral_consumers_and_several_events - @another = MyListener.new - ActiveSupport::Orchestra.register @another - - 1.upto(100) do |i| - ActiveSupport::Orchestra.instrument(:value) do - i - end - end - - sleep 0.1 - - assert_equal 100, @listener.consumed.size - assert_equal :value, @listener.consumed.first.name - assert_equal 1, @listener.consumed.first.result - assert_equal 100, @listener.consumed.last.result - - assert_equal 100, @another.consumed.size - assert_equal :value, @another.consumed.first.name - assert_equal 1, @another.consumed.first.result - assert_equal 100, @another.consumed.last.result - ensure - ActiveSupport::Orchestra.unregister @another - end -end diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index 15bd57181f..1521279437 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -191,4 +191,11 @@ class OrderedHashTest < Test::Unit::TestCase assert_equal "odd number of arguments for Hash", $!.message end end + + def test_replace_updates_keys + @other_ordered_hash = ActiveSupport::OrderedHash[:black, '000000', :white, '000000'] + original = @ordered_hash.replace(@other_ordered_hash) + assert_same original, @ordered_hash + assert_equal @other_ordered_hash.keys, @ordered_hash.keys + end end diff --git a/activesupport/test/test_test.rb b/activesupport/test/test_test.rb index 40d3d612e7..5cbffb81fc 100644 --- a/activesupport/test/test_test.rb +++ b/activesupport/test/test_test.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/kernel/reporting' class AssertDifferenceTest < ActiveSupport::TestCase def setup @object = Class.new do - attr_accessor :num + attr_accessor :num def increment self.num += 1 end @@ -12,7 +12,7 @@ class AssertDifferenceTest < ActiveSupport::TestCase def decrement self.num -= 1 end - end.new + end.new @object.num = 0 end diff --git a/activesupport/test/ts_isolated.rb b/activesupport/test/ts_isolated.rb index 9378a13766..cbab61a523 100644 --- a/activesupport/test/ts_isolated.rb +++ b/activesupport/test/ts_isolated.rb @@ -8,8 +8,8 @@ class TestIsolated < Test::Unit::TestCase Dir["#{File.dirname(__FILE__)}/**/*_test.rb"].each do |file| define_method("test #{file}") do command = "#{ruby} -Ilib:test #{file}" - silence_stderr { `#{command}` } - assert_equal 0, $?.to_i, command + result = silence_stderr { `#{command}` } + assert_block("#{command}\n#{result}") { $?.to_i.zero? } end end end diff --git a/activesupport/test/whiny_nil_test.rb b/activesupport/test/whiny_nil_test.rb index 4cb22c41b2..009d97940f 100644 --- a/activesupport/test/whiny_nil_test.rb +++ b/activesupport/test/whiny_nil_test.rb @@ -13,7 +13,7 @@ class WhinyNilTest < Test::Unit::TestCase def test_unchanged nil.method_thats_not_in_whiners rescue NoMethodError => nme - assert_match(/nil.method_thats_not_in_whiners/, nme.message) + assert(nme.message =~ /nil:NilClass/) end def test_active_record @@ -35,4 +35,16 @@ class WhinyNilTest < Test::Unit::TestCase rescue RuntimeError => nme assert(!(nme.message =~ /nil:NilClass/)) end + + def test_no_to_ary_coercion + nil.to_ary + rescue NoMethodError => nme + assert(nme.message =~ /nil:NilClass/) + end + + def test_no_to_str_coercion + nil.to_str + rescue NoMethodError => nme + assert(nme.message =~ /nil:NilClass/) + end end diff --git a/activesupport/test/xml_mini/libxml_engine_test.rb b/activesupport/test/xml_mini/libxml_engine_test.rb new file mode 100644 index 0000000000..900c8052d6 --- /dev/null +++ b/activesupport/test/xml_mini/libxml_engine_test.rb @@ -0,0 +1,194 @@ +require 'abstract_unit' +require 'active_support/xml_mini' +require 'active_support/core_ext/hash/conversions' + +begin + require 'libxml' +rescue LoadError + # Skip libxml tests +else + +class LibxmlEngineTest < Test::Unit::TestCase + include ActiveSupport + + def setup + @default_backend = XmlMini.backend + XmlMini.backend = 'LibXML' + + LibXML::XML::Error.set_handler(&lambda { |error| }) #silence libxml, exceptions will do + end + + def teardown + XmlMini.backend = @default_backend + end + + def test_exception_thrown_on_expansion_attack + assert_raise LibXML::XML::Error do + attack_xml = %{<?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + } + Hash.from_xml(attack_xml) + end + end + + def test_setting_libxml_as_backend + XmlMini.backend = 'LibXML' + assert_equal XmlMini_LibXML, XmlMini.backend + end + + def test_blank_returns_empty_hash + assert_equal({}, XmlMini.parse(nil)) + assert_equal({}, XmlMini.parse('')) + end + + def test_array_type_makes_an_array + assert_equal_rexml(<<-eoxml) + <blog> + <posts type="array"> + <post>a post</post> + <post>another post</post> + </posts> + </blog> + eoxml + end + + def test_one_node_document_as_hash + assert_equal_rexml(<<-eoxml) + <products/> + eoxml + end + + def test_one_node_with_attributes_document_as_hash + assert_equal_rexml(<<-eoxml) + <products foo="bar"/> + eoxml + end + + def test_products_node_with_book_node_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + </products> + eoxml + end + + def test_products_node_with_two_book_nodes_as_hash + assert_equal_rexml(<<-eoxml) + <products> + <book name="awesome" id="12345" /> + <book name="america" id="67890" /> + </products> + eoxml + end + + def test_single_node_with_content_as_hash + assert_equal_rexml(<<-eoxml) + <products> + hello world + </products> + eoxml + end + + def test_children_with_children + assert_equal_rexml(<<-eoxml) + <root> + <products> + <book name="america" id="67890" /> + </products> + </root> + eoxml + end + + def test_children_with_text + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello everyone + </products> + </root> + eoxml + end + + def test_children_with_non_adjacent_text + assert_equal_rexml(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + end + + def test_parse_from_io + io = StringIO.new(<<-eoxml) + <root> + good + <products> + hello everyone + </products> + morning + </root> + eoxml + XmlMini.parse(io) + end + + def test_children_with_simple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock]]> + </products> + </root> + eoxml + end + + def test_children_with_multiple_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + <![CDATA[cdatablock1]]><![CDATA[cdatablock2]]> + </products> + </root> + eoxml + end + + def test_children_with_text_and_cdata + assert_equal_rexml(<<-eoxml) + <root> + <products> + hello <![CDATA[cdatablock]]> + morning + </products> + </root> + eoxml + end + + def test_children_with_blank_text + assert_equal_rexml(<<-eoxml) + <root> + <products> </products> + </root> + eoxml + end + + private + def assert_equal_rexml(xml) + hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + assert_equal(hash, XmlMini.parse(xml)) + end +end + +end diff --git a/arel b/arel new file mode 160000 +Subproject 0faeb5047407348533db952d9cf93ea59d2526d diff --git a/ci/ci_build.rb b/ci/ci_build.rb index b9c321efef..7ae660fb7d 100755 --- a/ci/ci_build.rb +++ b/ci/ci_build.rb @@ -1,12 +1,18 @@ #!/usr/bin/env ruby require 'fileutils' - include FileUtils -puts "[CruiseControl] Rails build" +def root_dir + @root_dir ||= File.expand_path('../..', __FILE__) +end + +def rake(*tasks) + tasks.each { |task| return false unless system("#{root_dir}/bin/rake", task) } + true +end +puts "[CruiseControl] Rails build" build_results = {} -root_dir = File.expand_path(File.dirname(__FILE__) + "/..") # Requires gem home and path to be writeable and/or overridden to be ~/.gem, # Will enable when RubyGems supports this properly (in a coming release) @@ -16,41 +22,48 @@ root_dir = File.expand_path(File.dirname(__FILE__) + "/..") # A security hole, but there is nothing valuable on rails CI box anyway. build_results[:geminstaller] = system "sudo geminstaller --config=#{root_dir}/ci/geminstaller.yml --exceptions" +cd root_dir do + puts + puts "[CruiseControl] Bundling RubyGems" + puts + build_results[:bundle] = system 'rm -rf vendor && env CI=1 gem bundle --update && chmod 755 bin vendor vendor/gems' +end + cd "#{root_dir}/activesupport" do puts puts "[CruiseControl] Building ActiveSupport" puts - build_results[:activesupport] = system 'rake' - build_results[:activesupport_isolated] = system 'rake test:isolated' + build_results[:activesupport] = rake 'test' + build_results[:activesupport_isolated] = rake 'test:isolated' end -rm_f "#{root_dir}/activerecord/debug.log" -cd "#{root_dir}/activerecord" do +cd "#{root_dir}/railties" do puts - puts "[CruiseControl] Building ActiveRecord with MySQL" + puts "[CruiseControl] Building RailTies" puts - build_results[:activerecord_mysql] = system 'rake mysql:rebuild_databases && rake test_mysql' + build_results[:railties] = rake 'test' end -cd "#{root_dir}/activerecord" do +cd "#{root_dir}/actionpack" do puts - puts "[CruiseControl] Building ActiveRecord with PostgreSQL" + puts "[CruiseControl] Building ActionPack" puts - build_results[:activerecord_postgresql8] = system 'rake postgresql:rebuild_databases && rake test_postgresql' + build_results[:actionpack] = rake 'test' + build_results[:actionpack_isolated] = rake 'test:isolated' end -cd "#{root_dir}/activerecord" do +cd "#{root_dir}/actionmailer" do puts - puts "[CruiseControl] Building ActiveRecord with SQLite 3" + puts "[CruiseControl] Building ActionMailer" puts - build_results[:activerecord_sqlite3] = system 'rake test_sqlite3' + build_results[:actionmailer] = rake 'test' end cd "#{root_dir}/activemodel" do puts puts "[CruiseControl] Building ActiveModel" puts - build_results[:activemodel] = system 'rake' + build_results[:activemodel] = rake 'test' end rm_f "#{root_dir}/activeresource/debug.log" @@ -58,29 +71,29 @@ cd "#{root_dir}/activeresource" do puts puts "[CruiseControl] Building ActiveResource" puts - build_results[:activeresource] = system 'rake' + build_results[:activeresource] = rake 'test' end -cd "#{root_dir}/actionpack" do +rm_f "#{root_dir}/activerecord/debug.log" +cd "#{root_dir}/activerecord" do puts - puts "[CruiseControl] Building ActionPack" + puts "[CruiseControl] Building ActiveRecord with MySQL" puts - build_results[:actionpack] = system 'gem bundle && rake' - build_results[:actionpack_isolated] = system 'rake test:isolated' + build_results[:activerecord_mysql] = rake 'mysql:rebuild_databases', 'test_mysql' end -cd "#{root_dir}/actionmailer" do +cd "#{root_dir}/activerecord" do puts - puts "[CruiseControl] Building ActionMailer" + puts "[CruiseControl] Building ActiveRecord with PostgreSQL" puts - build_results[:actionmailer] = system 'rake' + build_results[:activerecord_postgresql8] = rake 'postgresql:rebuild_databases', 'test_postgresql' end -cd "#{root_dir}/railties" do +cd "#{root_dir}/activerecord" do puts - puts "[CruiseControl] Building RailTies" + puts "[CruiseControl] Building ActiveRecord with SQLite 3" puts - build_results[:railties] = system 'rake' + build_results[:activerecord_sqlite3] = rake 'test_sqlite3' end @@ -93,6 +106,8 @@ puts "[CruiseControl] #{`mysql --version`}" puts "[CruiseControl] #{`pg_config --version`}" puts "[CruiseControl] SQLite3: #{`sqlite3 -version`}" `gem env`.each_line {|line| print "[CruiseControl] #{line}"} +puts "[CruiseControl] Bundled gems:" +`gem bundle --list`.each_line {|line| print "[CruiseControl] #{line}"} puts "[CruiseControl] Local gems:" `gem list`.each_line {|line| print "[CruiseControl] #{line}"} diff --git a/ci/geminstaller.yml b/ci/geminstaller.yml index 8dde704e79..776ae8d98d 100644 --- a/ci/geminstaller.yml +++ b/ci/geminstaller.yml @@ -2,26 +2,7 @@ gems: - name: geminstaller version: >= 0.4.3 -- name: fcgi - version: >= 0.8.7 -- name: memcache-client - version: >= 1.5.0 -- name: mocha - version: >= 0.9.7 -- name: mysql - #version: >= 2.7 - version: = 2.7 -- name: nokogiri - version: >= 1.3.3 -- name: pg - version: >= 0.8.0 -- name: rack - version: '~> 1.0.0' -- name: rack-test - version: >= 0.5.0 -- name: rake - version: >= 0.8.1 -- name: sqlite3-ruby - version: >= 1.2.2 - name: rubygems-update - version: >= 1.3.3 + version: >= 1.3.5 +- name: bundler + version: >= 0.6.0 diff --git a/rack-mount b/rack-mount new file mode 160000 +Subproject 3784e633b42f43a4131e02519be60080d179da2 diff --git a/railties/.gitignore b/railties/.gitignore new file mode 100644 index 0000000000..80dd262d2f --- /dev/null +++ b/railties/.gitignore @@ -0,0 +1 @@ +log/ diff --git a/railties/Rakefile b/railties/Rakefile index e36930af4f..e6f698fc74 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -6,7 +6,8 @@ require 'rake/gempackagetask' require 'date' require 'rbconfig' -require File.join(File.dirname(__FILE__), 'lib/rails', 'version') +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" +require 'rails/version' PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : '' PKG_NAME = ENV['PKG_NAME'] || 'rails' @@ -21,20 +22,23 @@ RUBY_FORGE_USER = "webster132" task :default => :test +task :test => 'test:isolated' ## This is required until the regular test task ## below passes. It's not ideal, but at least ## we can see the failures -task :test do - dir = ENV["TEST_DIR"] || "**" - Dir["test/#{dir}/*_test.rb"].all? do |file| - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) - system(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file) - end or raise "Failures" +namespace :test do + task :isolated do + dir = ENV["TEST_DIR"] || "**" + Dir["test/#{dir}/*_test.rb"].all? do |file| + next true if file.include?("fixtures") + ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) + system(ruby, '-Itest', "-I#{File.dirname(__FILE__)}/../activesupport/lib", file) + end or raise "Failures" + end end -task :isolated_test => :test -Rake::TestTask.new("regular_test") do |t| +Rake::TestTask.new('test:regular') do |t| t.libs << 'test' << "#{File.dirname(__FILE__)}/../activesupport/lib" t.pattern = 'test/**/*_test.rb' t.warning = true @@ -80,7 +84,7 @@ end # Run application generator ------------------------------------------------------------- task :create_rails do - require File.join(File.dirname(__FILE__), 'lib', 'generators') + require 'rails/generators' require 'rails/generators/rails/app/app_generator' Rails::Generators::AppGenerator.start [ File.basename(PKG_DESTINATION), "--quiet" ], :destination_root => File.expand_path(File.dirname(PKG_DESTINATION)) @@ -146,7 +150,7 @@ end # Publishing ------------------------------------------------------- desc "Publish the rails gem" -task :pgem => [:gem] do +task :pgem => [:gem] do require 'rake/contrib/sshpublisher' Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload `ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'` diff --git a/railties/bin/rails b/railties/bin/rails index e743aa83f1..808df97429 100755 --- a/railties/bin/rails +++ b/railties/bin/rails @@ -19,6 +19,7 @@ end ARGV << "--help" if ARGV.empty? +require 'rails' require 'rails/generators' require 'rails/generators/rails/app/app_generator' diff --git a/railties/builtin/rails_info/rails/info.rb b/railties/builtin/rails_info/rails/info.rb index 48d89ad06a..c3784cff32 100644 --- a/railties/builtin/rails_info/rails/info.rb +++ b/railties/builtin/rails_info/rails/info.rb @@ -75,7 +75,7 @@ module Rails protected def rails_vendor_root - @rails_vendor_root ||= "#{RAILS_ROOT}/vendor/rails" + @rails_vendor_root ||= "#{Rails.root}/vendor/rails" end def git_info @@ -124,7 +124,7 @@ module Rails # The application's location on the filesystem. property 'Application root' do - File.expand_path(RAILS_ROOT) + File.expand_path(Rails.root) end # The current Rails environment (development, test, or production). diff --git a/railties/guides/source/2_2_release_notes.textile b/railties/guides/source/2_2_release_notes.textile index f60af01050..15a7bdbd44 100644 --- a/railties/guides/source/2_2_release_notes.textile +++ b/railties/guides/source/2_2_release_notes.textile @@ -51,7 +51,7 @@ If you want to generate these guides locally, inside your application: rake doc:guides </ruby> -This will put the guides inside +RAILS_ROOT/doc/guides+ and you may start surfing straight away by opening +RAILS_ROOT/doc/guides/index.html+ in your favourite browser. +This will put the guides inside +Rails.root/doc/guides+ and you may start surfing straight away by opening +Rails.root/doc/guides/index.html+ in your favourite browser. * Lead Contributors: "Rails Documentation Team":http://guides.rails.info/credits.html * Major contributions from "Xavier Noria":http://advogato.org/person/fxn/diary.html and "Hongli Lai":http://izumi.plan99.net/blog/. diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile index 756caea5fe..46a28da8c4 100644 --- a/railties/guides/source/action_controller_overview.textile +++ b/railties/guides/source/action_controller_overview.textile @@ -653,7 +653,7 @@ class ClientsController < ApplicationController # Stream a file that has already been generated and stored on disk. def download_pdf client = Client.find(params[:id]) - send_data("#{RAILS_ROOT}/files/clients/#{client.id}.pdf", + send_data("#{Rails.root}/files/clients/#{client.id}.pdf", :filename => "#{client.name}.pdf", :type => "application/pdf") end diff --git a/railties/guides/source/command_line.textile b/railties/guides/source/command_line.textile index d042458419..1a571358a1 100644 --- a/railties/guides/source/command_line.textile +++ b/railties/guides/source/command_line.textile @@ -424,10 +424,10 @@ INFO: For a good rundown on generators, see "Understanding Generators":http://wi Generators are code that generates code. Let's experiment by building one. Our generator will generate a text file. -The Rails generator by default looks in these places for available generators, where RAILS_ROOT is the root of your Rails application, like /home/foobar/commandsapp: +The Rails generator by default looks in these places for available generators, where Rails.root is the root of your Rails application, like /home/foobar/commandsapp: -* RAILS_ROOT/lib/generators -* RAILS_ROOT/vendor/generators +* Rails.root/lib/generators +* Rails.root/vendor/generators * Inside any plugin with a directory like "generators" or "rails_generators" * ~/.rails/generators * Inside any Gem you have installed with a name ending in "_generator" @@ -465,7 +465,7 @@ We take whatever args are supplied, save them to an instance variable, and liter * Check there's a *public* directory. You bet there is. * Run the ERb template called "tutorial.erb". -* Save it into "RAILS_ROOT/public/tutorial.txt". +* Save it into "Rails.root/public/tutorial.txt". * Pass in the arguments we saved through the +:assign+ parameter. Next we'll build the template: diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index 43ece14a49..c23b67e321 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -1 +1,13 @@ +require "pathname" + +require 'rails/initializable' +require 'rails/application' +require 'rails/railties_path' +require 'rails/version' +require 'rails/rack' +require 'rails/paths' +require 'rails/core' +require 'rails/configuration' +require 'rails/deprecation' require 'rails/initializer' +require 'rails/plugin'
\ No newline at end of file diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d54120f850..7c2d8eab67 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -1,8 +1,22 @@ module Rails class Application - extend Initializable + include Initializable class << self + def inherited(klass) + Rails.application ||= klass unless klass.name =~ /Rails/ + super + end + + # Stub out App initialize + def initialize! + new + end + + def new + @instance ||= super + end + def config @config ||= Configuration.new end @@ -14,31 +28,52 @@ module Rails @config = config end - def plugin_loader - @plugin_loader ||= config.plugin_loader.new(self) + def root + config.root end - def routes - ActionController::Routing::Routes + def call(env) + new.call(env) end + end - def middleware - config.middleware - end + def initialize + run_initializers(self) + end - def call(env) - @app ||= middleware.build(routes) - @app.call(env) - end + def config + self.class.config + end - def new - initializers.run - self + alias configuration config + + def middleware + config.middleware + end + + def routes + ActionController::Routing::Routes + end + + def initializers + initializers = super + plugins.each { |p| initializers += p.initializers } + initializers + end + + def plugins + @plugins ||= begin + Plugin::Vendored.all(config.plugins || [:all], config.paths.vendor.plugins) end end + def call(env) + @app ||= middleware.build(routes) + @app.call(env) + end + initializer :initialize_rails do - Rails.initializers.run + Rails.run_initializers end # Set the <tt>$LOAD_PATH</tt> based on the value of @@ -48,13 +83,6 @@ module Rails $LOAD_PATH.uniq! end - # Bail if boot.rb is outdated - initializer :freak_out_if_boot_rb_is_outdated do - unless defined?(Rails::BOOTSTRAP_VERSION) - abort %{Your config/boot.rb is outdated: Run "rake rails:update".} - end - end - # Requires all frameworks specified by the Configuration#frameworks # list. By default, all frameworks (Active Record, Active Support, # Action Pack, Action Mailer, and Active Resource) are loaded. @@ -92,17 +120,10 @@ module Rails config.load_once_paths.freeze end - # Adds all load paths from plugins to the global set of load paths, so that - # code from plugins can be required (explicitly or automatically via ActiveSupport::Dependencies). - initializer :add_plugin_load_paths do - require 'active_support/dependencies' - plugin_loader.add_plugin_load_paths - end - # Create tmp directories initializer :ensure_tmp_directories_exist do %w(cache pids sessions sockets).each do |dir_to_make| - FileUtils.mkdir_p(File.join(config.root_path, 'tmp', dir_to_make)) + FileUtils.mkdir_p(File.join(config.root, 'tmp', dir_to_make)) end end @@ -124,15 +145,6 @@ module Rails end end - initializer :add_gem_load_paths do - require 'rails/gem_dependency' - Rails::GemDependency.add_frozen_gem_path - unless config.gems.empty? - require "rubygems" - config.gems.each { |gem| gem.add_load_paths } - end - end - # Preload all frameworks specified by the Configuration#frameworks. # Used by Passenger to ensure everything's loaded before forking and # to avoid autoload race conditions in JRuby. @@ -249,6 +261,7 @@ module Rails # If assigned value cannot be matched to a TimeZone, an exception will be raised. initializer :initialize_time_zone do if config.time_zone + require 'active_support/core_ext/time/zones' zone_default = Time.__send__(:get_zone, config.time_zone) unless zone_default @@ -310,7 +323,6 @@ module Rails # TODO: Make Rails and metal work without ActionController if config.frameworks.include?(:action_controller) Rails::Rack::Metal.requested_metals = config.metals - Rails::Rack::Metal.metal_paths += plugin_loader.engine_metal_paths config.middleware.insert_before( :"ActionDispatch::ParamsParser", @@ -318,94 +330,19 @@ module Rails end end - initializer :check_for_unbuilt_gems do - unbuilt_gems = config.gems.select {|gem| gem.frozen? && !gem.built? } - if unbuilt_gems.size > 0 - # don't print if the gems:build rake tasks are being run - unless $gems_build_rake_task - abort <<-end_error - The following gems have native components that need to be built - #{unbuilt_gems.map { |gemm| "#{gemm.name} #{gemm.requirement}" } * "\n "} - - You're running: - ruby #{Gem.ruby_version} at #{Gem.ruby} - rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '} - - Run `rake gems:build` to build the unbuilt gems. - end_error - end - end - end - - initializer :load_gems do - unless $gems_rake_task - config.gems.each { |gem| gem.load } - end - end - - # Loads all plugins in <tt>config.plugin_paths</tt>. <tt>plugin_paths</tt> - # defaults to <tt>vendor/plugins</tt> but may also be set to a list of - # paths, such as - # config.plugin_paths = ["#{RAILS_ROOT}/lib/plugins", "#{RAILS_ROOT}/vendor/plugins"] - # - # In the default implementation, as each plugin discovered in <tt>plugin_paths</tt> is initialized: - # * its +lib+ directory, if present, is added to the load path (immediately after the applications lib directory) - # * <tt>init.rb</tt> is evaluated, if present - # - # After all plugins are loaded, duplicates are removed from the load path. - # If an array of plugin names is specified in config.plugins, only those plugins will be loaded - # and they plugins will be loaded in that order. Otherwise, plugins are loaded in alphabetical - # order. - # - # if config.plugins ends contains :all then the named plugins will be loaded in the given order and all other - # plugins will be loaded in alphabetical order - initializer :load_plugins do - plugin_loader.load_plugins - end - - # TODO: Figure out if this needs to run a second time - # load_gems - - initializer :check_gem_dependencies do - unloaded_gems = config.gems.reject { |g| g.loaded? } - if unloaded_gems.size > 0 - configuration.gems_dependencies_loaded = false - # don't print if the gems rake tasks are being run - unless $gems_rake_task - abort <<-end_error - Missing these required gems: - #{unloaded_gems.map { |gemm| "#{gemm.name} #{gemm.requirement}" } * "\n "} - - You're running: - ruby #{Gem.ruby_version} at #{Gem.ruby} - rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '} - - Run `rake gems:install` to install the missing gems. - end_error - end - else - configuration.gems_dependencies_loaded = true - end - end - # # bail out if gems are missing - note that check_gem_dependencies will have # # already called abort() unless $gems_rake_task is set # return unless gems_dependencies_loaded - initializer :load_application_initializers do - if config.gems_dependencies_loaded - Dir["#{configuration.root_path}/config/initializers/**/*.rb"].sort.each do |initializer| - load(initializer) - end + Dir["#{configuration.root}/config/initializers/**/*.rb"].sort.each do |initializer| + load(initializer) end end # Fires the user-supplied after_initialize block (Configuration#after_initialize) initializer :after_initialize do - if config.gems_dependencies_loaded - configuration.after_initialize_blocks.each do |block| - block.call - end + configuration.after_initialize_blocks.each do |block| + block.call end end @@ -447,7 +384,7 @@ module Rails # # # Observers are loaded after plugins in case Observers or observed models are modified by plugins. initializer :load_observers do - if config.gems_dependencies_loaded && configuration.frameworks.include?(:active_record) + if configuration.frameworks.include?(:active_record) ActiveRecord::Base.instantiate_observers end end @@ -473,14 +410,14 @@ module Rails end end - # Configure generators if they were already loaded - # === - # TODO: Does this need to be an initializer here? - initializer :initialize_generators do - if defined?(Rails::Generators) - Rails::Generators.no_color! unless config.generators.colorize_logging - Rails::Generators.aliases.deep_merge! config.generators.aliases - Rails::Generators.options.deep_merge! config.generators.options + # For each framework, search for instrument file with Notifications hooks. + # + initializer :load_notifications_hooks do + config.frameworks.each do |framework| + begin + require "#{framework}/notifications" + rescue LoadError => e + end end end end diff --git a/railties/lib/rails/backtrace_cleaner.rb b/railties/lib/rails/backtrace_cleaner.rb index 9ff8367807..cd7dd0f80a 100644 --- a/railties/lib/rails/backtrace_cleaner.rb +++ b/railties/lib/rails/backtrace_cleaner.rb @@ -1,5 +1,4 @@ require 'active_support/backtrace_cleaner' -require 'rails/gem_dependency' module Rails class BacktraceCleaner < ActiveSupport::BacktraceCleaner @@ -18,7 +17,7 @@ module Rails def initialize super - add_filter { |line| line.sub("#{RAILS_ROOT}/", '') } + add_filter { |line| line.sub("#{Rails.root}/", '') } add_filter { |line| line.sub(ERB_METHOD_SIG, '') } add_filter { |line| line.sub('./', '/') } # for tests @@ -28,17 +27,14 @@ module Rails add_silencer { |line| RAILS_GEMS.any? { |gem| line =~ /^#{gem} / } } add_silencer { |line| line =~ %r(vendor/plugins/[^\/]+/lib) } end - - + private def add_gem_filters + return unless defined? Gem (Gem.path + [Gem.default_dir]).uniq.each do |path| # http://gist.github.com/30430 add_filter { |line| line.sub(/(#{path})\/gems\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) \4')} end - - vendor_gems_path = Rails::GemDependency.unpacked_path.sub("#{RAILS_ROOT}/",'') - add_filter { |line| line.sub(/(#{vendor_gems_path})\/([a-z]+)-([0-9.]+)\/(.*)/, '\2 (\3) [v] \4')} end end diff --git a/railties/lib/rails/commands/about.rb b/railties/lib/rails/commands/about.rb index bc2cfcb948..d4c30bbeb2 100644 --- a/railties/lib/rails/commands/about.rb +++ b/railties/lib/rails/commands/about.rb @@ -1,3 +1,2 @@ -require "#{RAILS_ROOT}/config/environment" require 'rails/info' puts Rails::Info diff --git a/railties/lib/rails/commands/console.rb b/railties/lib/rails/commands/console.rb index 31448bdf1a..b977b7162f 100644 --- a/railties/lib/rails/commands/console.rb +++ b/railties/lib/rails/commands/console.rb @@ -12,7 +12,7 @@ OptionParser.new do |opt| end libs = " -r irb/completion" -libs << %( -r "#{RAILS_ROOT}/config/environment") +libs << %( -r "#{Rails.root}/config/environment") libs << " -r rails/console_app" libs << " -r rails/console_sandbox" if options[:sandbox] libs << " -r rails/console_with_helpers" diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index e6f11a45db..4e699acf6b 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -25,7 +25,7 @@ OptionParser.new do |opt| end env = ARGV.first || ENV['RAILS_ENV'] || 'development' -unless config = YAML::load(ERB.new(IO.read(RAILS_ROOT + "/config/database.yml")).result)[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/rails/commands/destroy.rb b/railties/lib/rails/commands/destroy.rb index 5013d30b83..f85c17bb94 100644 --- a/railties/lib/rails/commands/destroy.rb +++ b/railties/lib/rails/commands/destroy.rb @@ -1,5 +1,5 @@ -require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators')) -require "#{RAILS_ROOT}/config/environment" +require 'rails/generators' +Rails::Generators.configure! if ARGV.size == 0 Rails::Generators.help diff --git a/railties/lib/rails/commands/generate.rb b/railties/lib/rails/commands/generate.rb index 32cabcab10..c5e3ae3529 100755 --- a/railties/lib/rails/commands/generate.rb +++ b/railties/lib/rails/commands/generate.rb @@ -1,5 +1,5 @@ -require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators')) -require "#{RAILS_ROOT}/config/environment" +require 'rails/generators' +Rails::Generators.configure! if ARGV.size == 0 Rails::Generators.help diff --git a/railties/lib/rails/commands/performance/benchmarker.rb b/railties/lib/rails/commands/performance/benchmarker.rb index e8804fe1bf..dfba4bf034 100644 --- a/railties/lib/rails/commands/performance/benchmarker.rb +++ b/railties/lib/rails/commands/performance/benchmarker.rb @@ -10,7 +10,6 @@ rescue ArgumentError N = 1 end -require RAILS_ROOT + '/config/environment' require 'benchmark' include Benchmark @@ -21,4 +20,4 @@ bm(6) do |x| ARGV.each_with_index do |expression, idx| x.report("##{idx + 1}") { N.times { eval(expression) } } end -end +end diff --git a/railties/lib/rails/commands/performance/profiler.rb b/railties/lib/rails/commands/performance/profiler.rb index 7df840f197..aaa075018c 100644 --- a/railties/lib/rails/commands/performance/profiler.rb +++ b/railties/lib/rails/commands/performance/profiler.rb @@ -3,10 +3,6 @@ if ARGV.empty? exit(1) end -# Keep the expensive require out of the profile. -$stderr.puts 'Loading Rails...' -require RAILS_ROOT + '/config/environment' - # Define a method to profile. if ARGV[1] and ARGV[1].to_i > 1 eval "def profile_me() #{ARGV[1]}.times { #{ARGV[0]} } end" diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index 510128318a..0246348c77 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -36,8 +36,6 @@ ARGV.delete(code_or_file) ENV["RAILS_ENV"] = options[:environment] RAILS_ENV.replace(options[:environment]) if defined?(RAILS_ENV) -require RAILS_ROOT + '/config/environment' - begin if code_or_file.nil? $stderr.puts "Run '#{$0} -h' for help." diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index c138cbc9bf..2c90851fb2 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -1,4 +1,4 @@ -require 'action_controller' +require 'action_dispatch' require 'fileutils' require 'optparse' @@ -7,7 +7,7 @@ options = { :Port => 3000, :Host => "0.0.0.0", :environment => (ENV['RAILS_ENV'] || "development").dup, - :config => RAILS_ROOT + "/config.ru", + :config => "#{Rails.root}/config.ru", :detach => false, :debugger => false } @@ -42,11 +42,11 @@ unless server end puts "=> Booting #{ActiveSupport::Inflector.demodulize(server)}" -puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}}" +puts "=> Rails #{Rails.version} application starting on http://#{options[:Host]}:#{options[:Port]}" if options[:detach] Process.daemon - pid = "#{RAILS_ROOT}/tmp/pids/server.pid" + pid = "#{Rails.root}/tmp/pids/server.pid" File.open(pid, 'w'){ |f| f.write(Process.pid) } at_exit { File.delete(pid) if File.exist?(pid) } end diff --git a/railties/lib/rails/commands/update.rb b/railties/lib/rails/commands/update.rb index f3b3ad0775..85a81cddf0 100644 --- a/railties/lib/rails/commands/update.rb +++ b/railties/lib/rails/commands/update.rb @@ -1,5 +1,4 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'generators')) -require "#{RAILS_ROOT}/config/environment" if ARGV.size == 0 Rails::Generators.help diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 4a70a4800e..102a0836dc 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -1,84 +1,75 @@ +require 'active_support/ordered_options' + module Rails class Configuration - attr_accessor :cache_classes, :load_paths, :eager_load_paths, :framework_paths, - :load_once_paths, :gems_dependencies_loaded, :after_initialize_blocks, - :frameworks, :framework_root_path, :root_path, :plugin_paths, :plugins, - :plugin_loader, :plugin_locators, :gems, :loaded_plugins, :reload_plugins, + attr_accessor :cache_classes, :load_paths, :load_once_paths, :after_initialize_blocks, + :frameworks, :framework_root_path, :root, :gems, :plugins, :i18n, :gems, :whiny_nils, :consider_all_requests_local, :action_controller, :active_record, :action_view, :active_support, :action_mailer, :active_resource, - :log_path, :log_level, :logger, :preload_frameworks, + :reload_plugins, :log_path, :log_level, :logger, :preload_frameworks, :database_configuration_file, :cache_store, :time_zone, :view_path, :metals, :controller_paths, :routes_configuration_file, :eager_load_paths, :dependency_loading, :paths, :serve_static_assets def initialize - set_root_path! - - @framework_paths = [] @load_once_paths = [] @after_initialize_blocks = [] - @loaded_plugins = [] @dependency_loading = true - @eager_load_paths = default_eager_load_paths - @load_paths = default_load_paths - @plugin_paths = default_plugin_paths - @frameworks = default_frameworks - @plugin_loader = default_plugin_loader - @plugin_locators = default_plugin_locators - @gems = default_gems - @i18n = default_i18n - @log_path = default_log_path - @log_level = default_log_level - @cache_store = default_cache_store - @view_path = default_view_path - @controller_paths = default_controller_paths - @routes_configuration_file = default_routes_configuration_file - @database_configuration_file = default_database_configuration_file - @serve_static_assets = default_serve_static_assets - - for framework in default_frameworks - self.send("#{framework}=", Rails::OrderedOptions.new) + @serve_static_assets = true + + for framework in frameworks + self.send("#{framework}=", ActiveSupport::OrderedOptions.new) end - self.active_support = Rails::OrderedOptions.new + self.active_support = ActiveSupport::OrderedOptions.new end def after_initialize(&blk) @after_initialize_blocks << blk if blk end - def set_root_path! - raise 'RAILS_ROOT is not set' unless defined?(RAILS_ROOT) - raise 'RAILS_ROOT is not a directory' unless File.directory?(RAILS_ROOT) - - self.root_path = - # Pathname is incompatible with Windows, but Windows doesn't have - # real symlinks so File.expand_path is safe. - if RUBY_PLATFORM =~ /(:?mswin|mingw)/ - File.expand_path(RAILS_ROOT) + def root + @root ||= begin + call_stack = caller.map { |p| p.split(':').first } + root_path = call_stack.detect { |p| p !~ %r[railties/lib/rails] } + root_path = File.dirname(root_path) - # Otherwise use Pathname#realpath which respects symlinks. - else - Pathname.new(RAILS_ROOT).realpath.to_s + while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/config.ru") + parent = File.dirname(root_path) + root_path = parent != root_path && parent end - @paths = Rails::Application::Root.new(root_path) - @paths.app "app", :load_path => true - @paths.app.metals "app/metal", :eager_load => true - @paths.app.models "app/models", :eager_load => true - @paths.app.controllers "app/controllers", builtin_directories, :eager_load => true - @paths.app.helpers "app/helpers", :eager_load => true - @paths.app.services "app/services", :load_path => true - @paths.lib "lib", :load_path => true - @paths.vendor "vendor", :load_path => true - @paths.vendor.plugins "vendor/plugins" - @paths.tmp "tmp" - @paths.tmp.cache "tmp/cache" - @paths.config "config" - @paths.config.locales "config/locales" - @paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb" - - RAILS_ROOT.replace root_path + root = File.exist?("#{root_path}/config.ru") ? root_path : Dir.pwd + + RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? + Pathname.new(root).expand_path : + Pathname.new(root).realpath + end + end + + def root=(root) + @root = Pathname.new(root).expand_path + end + + def paths + @paths ||= begin + paths = Rails::Application::Root.new(root) + paths.app "app", :load_path => true + paths.app.metals "app/metal", :eager_load => true + paths.app.models "app/models", :eager_load => true + paths.app.controllers "app/controllers", builtin_directories, :eager_load => true + paths.app.helpers "app/helpers", :eager_load => true + paths.app.services "app/services", :load_path => true + paths.lib "lib", :load_path => true + paths.vendor "vendor", :load_path => true + paths.vendor.plugins "vendor/plugins" + paths.tmp "tmp" + paths.tmp.cache "tmp/cache" + paths.config "config" + paths.config.locales "config/locales" + paths.config.environments "config/environments", :glob => "#{RAILS_ENV}.rb" + paths + end end # Enable threaded mode. Allows concurrent requests to controller actions and @@ -105,7 +96,7 @@ module Rails end def framework_root_path - defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root_path}/vendor/rails" + defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{root}/vendor/rails" end def middleware @@ -121,63 +112,69 @@ module Rails YAML::load(ERB.new(IO.read(database_configuration_file)).result) end - def default_routes_configuration_file - File.join(root_path, 'config', 'routes.rb') + def routes_configuration_file + @routes_configuration_file ||= File.join(root, 'config', 'routes.rb') end - def default_controller_paths - paths = [File.join(root_path, 'app', 'controllers')] - paths.concat builtin_directories - paths + def controller_paths + @controller_paths ||= begin + paths = [File.join(root, 'app', 'controllers')] + paths.concat builtin_directories + paths + end end - def default_cache_store - if File.exist?("#{root_path}/tmp/cache/") - [ :file_store, "#{root_path}/tmp/cache/" ] - else - :memory_store + def cache_store + @cache_store ||= begin + if File.exist?("#{root}/tmp/cache/") + [ :file_store, "#{root}/tmp/cache/" ] + else + :memory_store + end end end - def default_database_configuration_file - File.join(root_path, 'config', 'database.yml') + def database_configuration_file + @database_configuration_file ||= File.join(root, 'config', 'database.yml') end - def default_view_path - File.join(root_path, 'app', 'views') + def view_path + @view_path ||= File.join(root, 'app', 'views') end - def default_eager_load_paths - %w( + def eager_load_paths + @eager_load_paths ||= %w( app/metal app/models app/controllers app/helpers - ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) } + ).map { |dir| "#{root}/#{dir}" }.select { |dir| File.directory?(dir) } end - def default_load_paths - paths = [] - - # Add the old mock paths only if the directories exists - paths.concat(Dir["#{root_path}/test/mocks/#{RAILS_ENV}"]) if File.exists?("#{root_path}/test/mocks/#{RAILS_ENV}") - - # Add the app's controller directory - paths.concat(Dir["#{root_path}/app/controllers/"]) - - # Followed by the standard includes. - paths.concat %w( - app - app/metal - app/models - app/controllers - app/helpers - app/services - lib - vendor - ).map { |dir| "#{root_path}/#{dir}" }.select { |dir| File.directory?(dir) } - - paths.concat builtin_directories + def load_paths + @load_paths ||= begin + paths = [] + + # Add the old mock paths only if the directories exists + paths.concat(Dir["#{root}/test/mocks/#{RAILS_ENV}"]) if File.exists?("#{root}/test/mocks/#{RAILS_ENV}") + + # Add the app's controller directory + paths.concat(Dir["#{root}/app/controllers/"]) + + # Followed by the standard includes. + paths.concat %w( + app + app/metal + app/models + app/controllers + app/helpers + app/services + lib + vendor + ).map { |dir| "#{root}/#{dir}" }.select { |dir| File.directory?(dir) } + + paths.concat builtin_directories + end end def builtin_directories @@ -185,75 +182,34 @@ module Rails (RAILS_ENV == 'development') ? Dir["#{RAILTIES_PATH}/builtin/*/"] : [] end - def default_log_path - File.join(root_path, 'log', "#{RAILS_ENV}.log") + def log_path + @log_path ||= File.join(root, 'log', "#{RAILS_ENV}.log") end - def default_log_level - RAILS_ENV == 'production' ? :info : :debug + def log_level + @log_level ||= RAILS_ENV == 'production' ? :info : :debug end - def default_frameworks - [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ] + def frameworks + @frameworks ||= [ :active_record, :action_controller, :action_view, :action_mailer, :active_resource ] end - def default_plugin_paths - ["#{root_path}/vendor/plugins"] - end - - def default_plugin_loader - require 'rails/plugin/loader' - Plugin::Loader - end + def i18n + @i18n ||= begin + i18n = ActiveSupport::OrderedOptions.new + i18n.load_path = [] - def default_plugin_locators - require 'rails/plugin/locator' - locators = [] - locators << Plugin::GemLocator if defined? Gem - locators << Plugin::FileSystemLocator - end - - def default_i18n - i18n = Rails::OrderedOptions.new - i18n.load_path = [] + if File.exist?(File.join(root, 'config', 'locales')) + i18n.load_path << Dir[File.join(root, 'config', 'locales', '*.{rb,yml}')] + i18n.load_path.flatten! + end - if File.exist?(File.join(RAILS_ROOT, 'config', 'locales')) - i18n.load_path << Dir[File.join(RAILS_ROOT, 'config', 'locales', '*.{rb,yml}')] - i18n.load_path.flatten! + i18n end - - i18n - end - - def default_serve_static_assets - true - end - - # Adds a single Gem dependency to the rails application. By default, it will require - # the library with the same name as the gem. Use :lib to specify a different name. - # - # # gem 'aws-s3', '>= 0.4.0' - # # require 'aws/s3' - # config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0', \ - # :source => "http://code.whytheluckystiff.net" - # - # To require a library be installed, but not attempt to load it, pass :lib => false - # - # config.gem 'qrp', :version => '0.4.1', :lib => false - def gem(name, options = {}) - @gems << Rails::GemDependency.new(name, options) - end - - def default_gems - [] end def environment_path - "#{root_path}/config/environments/#{RAILS_ENV}.rb" - end - - def reload_plugins? - @reload_plugins + "#{root}/config/environments/#{RAILS_ENV}.rb" end # Holds generators configuration: @@ -277,6 +233,14 @@ module Rails end end + # Allows Notifications queue to be modified. + # + # config.notifications.queue = MyNewQueue.new + # + def notifications + ActiveSupport::Notifications + end + class Generators #:nodoc: attr_accessor :aliases, :options, :colorize_logging @@ -287,12 +251,16 @@ module Rails end def method_missing(method, *args) - method = method.to_s.sub(/=$/, '').to_sym - namespace = args.first.is_a?(Symbol) ? args.shift : nil - configuration = args.first.is_a?(Hash) ? args.shift : nil + method = method.to_s.sub(/=$/, '').to_sym - @options[:rails][method] = namespace if namespace - namespace ||= method + if method == :rails + namespace, configuration = :rails, args.shift + elsif args.first.is_a?(Hash) + namespace, configuration = method, args.shift + else + namespace, configuration = args.shift, args.shift + @options[:rails][method] = namespace + end if configuration aliases = configuration.delete(:aliases) diff --git a/railties/lib/rails/core.rb b/railties/lib/rails/core.rb index 929c38bd22..a5e51ad04a 100644 --- a/railties/lib/rails/core.rb +++ b/railties/lib/rails/core.rb @@ -6,7 +6,7 @@ module Rails # TODO: w0t? class << self def application - @@application + @@application ||= nil end def application=(application) @@ -18,6 +18,10 @@ module Rails application.configuration end + def initialize! + application.initialize! + end + def initialized? @initialized || false end @@ -43,7 +47,7 @@ module Rails end def root - Pathname.new(RAILS_ROOT) if defined?(RAILS_ROOT) + application && application.config.root end def env @@ -66,36 +70,4 @@ module Rails @@public_path = path end end - - class OrderedOptions < Array #:nodoc: - def []=(key, value) - key = key.to_sym - - if pair = find_pair(key) - pair.pop - pair << value - else - self << [key, value] - end - end - - def [](key) - pair = find_pair(key.to_sym) - pair ? pair.last : nil - end - - def method_missing(name, *args) - if name.to_s =~ /(.*)=$/ - self[$1.to_sym] = args.first - else - self[name] - end - end - - private - def find_pair(key) - self.each { |i| return i if i.first == key } - return false - end - end end
\ No newline at end of file diff --git a/railties/lib/rails/deprecation.rb b/railties/lib/rails/deprecation.rb new file mode 100644 index 0000000000..3c5b8bdec7 --- /dev/null +++ b/railties/lib/rails/deprecation.rb @@ -0,0 +1,25 @@ +require "active_support/string_inquirer" +require "active_support/deprecation" + +RAILS_ROOT = (Class.new(ActiveSupport::Deprecation::DeprecationProxy) do + def target + Rails.root + end + + def replace(val) + puts OMG + end + + def warn(callstack, called, args) + msg = "RAILS_ROOT is deprecated! Use Rails.root instead." + ActiveSupport::Deprecation.warn(msg, callstack) + end +end).new + +module Rails + class Configuration + def gem(*args) + ActiveSupport::Deprecation.warn("config.gem has been deprecated in favor of the Gemfile.") + end + end +end
\ No newline at end of file diff --git a/railties/lib/rails/gem_builder.rb b/railties/lib/rails/gem_builder.rb deleted file mode 100644 index 79c61cc034..0000000000 --- a/railties/lib/rails/gem_builder.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rubygems' -require 'rubygems/installer' - -module Rails - - # this class hijacks the functionality of Gem::Installer by overloading its - # initializer to only provide the information needed by - # Gem::Installer#build_extensions (which happens to be what we have) - class GemBuilder < Gem::Installer - - def initialize(spec, gem_dir) - @spec = spec - @gem_dir = gem_dir - end - - # silence the underlying builder - def say(message) - end - - end -end diff --git a/railties/lib/rails/gem_dependency.rb b/railties/lib/rails/gem_dependency.rb deleted file mode 100644 index 06d830ba24..0000000000 --- a/railties/lib/rails/gem_dependency.rb +++ /dev/null @@ -1,311 +0,0 @@ -require 'rails/vendor_gem_source_index' - -module Gem - def self.source_index=(index) - @@source_index = index - end -end - -module Rails - class GemDependency < Gem::Dependency - attr_accessor :lib, :source, :dep - - def self.unpacked_path - @unpacked_path ||= File.join(RAILS_ROOT, 'vendor', 'gems') - end - - @@framework_gems = {} - - def self.add_frozen_gem_path - @@paths_loaded ||= begin - source_index = Rails::VendorGemSourceIndex.new(Gem.source_index) - Gem.clear_paths - Gem.source_index = source_index - # loaded before us - we can't change them, so mark them - Gem.loaded_specs.each do |name, spec| - @@framework_gems[name] = spec - end - true - end - end - - def self.from_directory_name(directory_name, load_spec=true) - directory_name_parts = File.basename(directory_name).split('-') - name = directory_name_parts[0..-2].join('-') - version = directory_name_parts.last - result = self.new(name, :version => version) - spec_filename = File.join(directory_name, '.specification') - if load_spec - raise "Missing specification file in #{File.dirname(spec_filename)}. Perhaps you need to do a 'rake gems:refresh_specs'?" unless File.exists?(spec_filename) - spec = YAML::load_file(spec_filename) - result.specification = spec - end - result - rescue ArgumentError => e - raise "Unable to determine gem name and version from '#{directory_name}'" - end - - def initialize(name, options = {}) - require 'rubygems' unless Object.const_defined?(:Gem) - - if options[:requirement] - req = options[:requirement] - elsif options[:version] - req = Gem::Requirement.create(options[:version]) - else - req = Gem::Requirement.default - end - - @lib = options[:lib] - @source = options[:source] - @loaded = @frozen = @load_paths_added = false - - super(name, req) - end - - def add_load_paths - self.class.add_frozen_gem_path - return if @loaded || @load_paths_added - if framework_gem? - @load_paths_added = @loaded = @frozen = true - return - end - gem self - @spec = Gem.loaded_specs[name] - @frozen = @spec.loaded_from.include?(self.class.unpacked_path) if @spec - @load_paths_added = true - rescue Gem::LoadError - end - - def dependencies - return [] if framework_gem? - return [] unless installed? - specification.dependencies.reject do |dependency| - dependency.type == :development - end.map do |dependency| - GemDependency.new(dependency.name, :requirement => dependency.version_requirements) - end - end - - def specification - # code repeated from Gem.activate. Find a matching spec, or the currently loaded version. - # error out if loaded version and requested version are incompatible. - @spec ||= begin - matches = Gem.source_index.search(self) - matches << @@framework_gems[name] if framework_gem? - if Gem.loaded_specs[name] then - # This gem is already loaded. If the currently loaded gem is not in the - # list of candidate gems, then we have a version conflict. - existing_spec = Gem.loaded_specs[name] - unless matches.any? { |spec| spec.version == existing_spec.version } then - raise Gem::Exception, - "can't activate #{@dep}, already activated #{existing_spec.full_name}" - end - # we're stuck with it, so change to match - version_requirements = Gem::Requirement.create("=#{existing_spec.version}") - existing_spec - else - # new load - matches.last - end - end - end - - def specification=(s) - @spec = s - end - - def requirement - r = version_requirements - (r == Gem::Requirement.default) ? nil : r - end - - def built? - return false unless frozen? - - if vendor_gem? - specification.extensions.each do |ext| - makefile = File.join(unpacked_gem_directory, File.dirname(ext), 'Makefile') - return false unless File.exists?(makefile) - end - end - - true - end - - def framework_gem? - @@framework_gems.has_key?(name) - end - - def frozen? - @frozen ||= vendor_rails? || vendor_gem? - end - - def installed? - Gem.loaded_specs.keys.include?(name) - end - - def load_paths_added? - # always try to add load paths - even if a gem is loaded, it may not - # be a compatible version (ie random_gem 0.4 is loaded and a later spec - # needs >= 0.5 - gem 'random_gem' will catch this and error out) - @load_paths_added - end - - def loaded? - @loaded ||= begin - if vendor_rails? - true - elsif specification.nil? - false - else - # check if the gem is loaded by inspecting $" - # specification.files lists all the files contained in the gem - gem_files = specification.files - # select only the files contained in require_paths - typically in bin and lib - require_paths_regexp = Regexp.new("^(#{specification.require_paths*'|'})/") - gem_lib_files = gem_files.select { |f| require_paths_regexp.match(f) } - # chop the leading directory off - a typical file might be in - # lib/gem_name/file_name.rb, but it will be 'require'd as gem_name/file_name.rb - gem_lib_files.map! { |f| f.split('/', 2)[1] } - # if any of the files from the above list appear in $", the gem is assumed to - # have been loaded - !(gem_lib_files & $").empty? - end - end - end - - def vendor_rails? - Gem.loaded_specs.has_key?(name) && Gem.loaded_specs[name].loaded_from.empty? - end - - def vendor_gem? - specification && File.exists?(unpacked_gem_directory) - end - - def build(options={}) - require 'rails/gem_builder' - return if specification.nil? - if options[:force] || !built? - return unless File.exists?(unpacked_specification_filename) - spec = YAML::load_file(unpacked_specification_filename) - Rails::GemBuilder.new(spec, unpacked_gem_directory).build_extensions - puts "Built gem: '#{unpacked_gem_directory}'" - end - dependencies.each { |dep| dep.build(options) } - end - - def install - unless installed? - cmd = "#{gem_command} #{install_command.join(' ')}" - puts cmd - puts %x(#{cmd}) - end - end - - def load - return if @loaded || @load_paths_added == false - require(@lib || name) unless @lib == false - @loaded = true - rescue LoadError - puts $!.to_s - $!.backtrace.each { |b| puts b } - end - - def refresh - Rails::VendorGemSourceIndex.silence_spec_warnings = true - real_gems = Gem.source_index.installed_source_index - exact_dep = Gem::Dependency.new(name, "= #{specification.version}") - matches = real_gems.search(exact_dep) - installed_spec = matches.first - if frozen? - if installed_spec - # we have a real copy - # get a fresh spec - matches should only have one element - # note that there is no reliable method to check that the loaded - # spec is the same as the copy from real_gems - Gem.activate changes - # some of the fields - real_spec = Gem::Specification.load(matches.first.loaded_from) - write_specification(real_spec) - puts "Reloaded specification for #{name} from installed gems." - else - # the gem isn't installed locally - write out our current specs - write_specification(specification) - puts "Gem #{name} not loaded locally - writing out current spec." - end - else - if framework_gem? - puts "Gem directory for #{name} not found - check if it's loading before rails." - else - puts "Something bad is going on - gem directory not found for #{name}." - end - end - end - - def unpack(options={}) - unless frozen? || framework_gem? - FileUtils.mkdir_p unpack_base - Dir.chdir unpack_base do - Gem::GemRunner.new.run(unpack_command) - end - # Gem.activate changes the spec - get the original - real_spec = Gem::Specification.load(specification.loaded_from) - write_specification(real_spec) - end - dependencies.each { |dep| dep.unpack(options) } if options[:recursive] - end - - def write_specification(spec) - # copy the gem's specification into GEMDIR/.specification so that - # we can access information about the gem on deployment systems - # without having the gem installed - File.open(unpacked_specification_filename, 'w') do |file| - file.puts spec.to_yaml - end - end - - def ==(other) - self.name == other.name && self.requirement == other.requirement - end - alias_method :"eql?", :"==" - - private - - def gem_command - case RUBY_PLATFORM - when /win32/ - 'gem.bat' - when /java/ - 'jruby -S gem' - else - 'gem' - end - end - - def install_command - cmd = %w(install) << name - cmd << "--version" << %("#{requirement.to_s}") if requirement - cmd << "--source" << @source if @source - cmd - end - - def unpack_command - cmd = %w(unpack) << name - cmd << "--version" << "= "+specification.version.to_s if requirement - cmd - end - - def unpack_base - Rails::GemDependency.unpacked_path - end - - def unpacked_gem_directory - File.join(unpack_base, specification.full_name) - end - - def unpacked_specification_filename - File.join(unpacked_gem_directory, '.specification') - end - - end -end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 0419a4e36c..19412c259e 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -1,4 +1,4 @@ -activesupport_path = "#{File.dirname(__FILE__)}/../../activesupport/lib" +activesupport_path = "#{File.dirname(__FILE__)}/../../../activesupport/lib" $LOAD_PATH.unshift(activesupport_path) if File.directory?(activesupport_path) require 'active_support' require 'active_support/core_ext/object/blank' @@ -9,7 +9,7 @@ require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/string/inflections' # TODO: Do not always push on vendored thor -$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.11.6/lib") +$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.0/lib") require 'rails/generators/base' require 'rails/generators/named_base' @@ -72,6 +72,12 @@ module Rails } } + def self.configure!(config = Rails.application.config.generators) #:nodoc: + no_color! unless config.colorize_logging + aliases.deep_merge! config.aliases + options.deep_merge! config.options + end + def self.aliases #:nodoc: @aliases ||= DEFAULT_ALIASES.dup end @@ -92,8 +98,6 @@ module Rails generator_path = File.join(spec.full_gem_path, "lib/generators") paths << generator_path if File.exist?(generator_path) end - elsif defined?(RAILS_ROOT) - paths += Dir[File.join(RAILS_ROOT, "vendor", "gems", "gems", "*", "lib", "generators")] end paths @@ -102,8 +106,8 @@ module Rails # Load paths from plugin. # def self.plugins_generators_paths - return [] unless defined?(RAILS_ROOT) - Dir[File.join(RAILS_ROOT, "vendor", "plugins", "*", "lib", "generators")] + return [] unless Rails.root + Dir[File.join(Rails.root, "vendor", "plugins", "*", "lib", "generators")] end # Hold configured generators fallbacks. If a plugin developer wants a @@ -143,7 +147,7 @@ module Rails def self.load_paths @load_paths ||= begin paths = [] - paths << File.join(RAILS_ROOT, "lib", "generators") if defined?(RAILS_ROOT) + paths << File.join(Rails.root, "lib", "generators") if Rails.root paths << File.join(Thor::Util.user_home, ".rails", "generators") paths += self.plugins_generators_paths paths += self.gems_generators_paths @@ -154,7 +158,18 @@ module Rails end load_paths # Cache load paths. Needed to avoid __FILE__ pointing to wrong paths. - # Receives a namespace and tries different combinations to find a generator. + # Rails finds namespaces exactly as thor, with three conveniences: + # + # 1) If your generator name ends with generator, as WebratGenerator, it sets + # its namespace to "webrat", so it can be invoked as "webrat" and not + # "webrat_generator"; + # + # 2) If your generator has a generators namespace, as Rails::Generators::WebratGenerator, + # the namespace is set to "rails:generators:webrat", but Rails allows it + # to be invoked simply as "rails:webrat". The "generators" is added + # automatically when doing the lookup; + # + # 3) Rails looks in load paths and loads the generator just before it's going to be used. # # ==== Examples # @@ -164,30 +179,29 @@ module Rails # # "rails:generators:webrat", "webrat:generators:integration", "webrat" # - # If the namespace has ":" included we consider that a absolute namespace - # was given and the lookup above does not happen. Just the name is searched. + # On the other hand, if "rails:webrat" is given, it will search for: # - # Finally, it deals with one kind of shortcut: + # "rails:generators:webrat", "rails:webrat" # - # find_by_namespace "test_unit:model" - # - # It will search for generators at: - # - # "test_unit:generators:model", "test_unit:model" + # Notice that the "generators" namespace is handled automatically by Rails, + # so you don't need to type it when you want to invoke a generator in specific. # def self.find_by_namespace(name, base=nil, context=nil) #:nodoc: - name, attempts = name.to_s, [] + name, attempts = name.to_s, [ ] case name.count(':') when 1 base, name = name.split(':') return find_by_namespace(name, base) when 0 - attempts << "#{base}:generators:#{name}" if base - attempts << "#{name}:generators:#{context}" if context + attempts += generator_names(base, name) if base + attempts += generator_names(name, context) if context end attempts << name + attempts += generator_names(name, name) unless name.include?(?:) + attempts.uniq! + unloaded = attempts - namespaces lookup(unloaded) @@ -233,7 +247,10 @@ module Rails until tail.empty? others += Dir[File.join(path, *tail)].collect do |file| - file.split('/')[-tail.size, 2].join(':').sub(/_generator\.rb$/, '') + name = file.split('/')[-tail.size, 2] + name.last.sub!(/_generator\.rb$/, '') + name.uniq! + name.join(':') end tail.shift end @@ -248,7 +265,7 @@ module Rails # Return all defined namespaces. # def self.namespaces #:nodoc: - Thor::Base.subclasses.map{ |klass| klass.namespace } + Thor::Base.subclasses.map { |klass| klass.namespace } end # Keep builtin generators in an Array[Array[group, name]]. @@ -259,6 +276,12 @@ module Rails end end + # By default, Rails strips the generator namespace to make invocations + # easier. This method generaters the both possibilities names. + def self.generator_names(first, second) + [ "#{first}:generators:#{second}", "#{first}:#{second}" ] + end + # Try callbacks for the given base. # def self.invoke_fallbacks_for(name, base) @@ -287,6 +310,9 @@ module Rails Dir[File.join(path, '**', attempts)].each do |file| begin require file + rescue NameError => e + raise unless e.message =~ /Rails::Generator/ + warn "[WARNING] Could not load generator at #{file.inspect} because it's a Rails 2.x generator, which is not supported anymore" rescue Exception => e warn "[WARNING] Could not load generator at #{file.inspect}. Error: #{e.message}" end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index c4552dd399..8677bf283b 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -1,4 +1,5 @@ require 'open-uri' +require 'active_support/deprecation' module Rails module Generators @@ -45,19 +46,56 @@ module Rails # # gem "rspec", :env => :test # gem "technoweenie-restful-authentication", :lib => "restful-authentication", :source => "http://gems.github.com/" + # gem "rails", "3.0", :git => "git://github.com/rails/rails" # - def gem(name, options={}) - log :gem, name - env = options.delete(:env) + def gem(*args) + options = args.extract_options! + name, version = args - gems_code = "config.gem '#{name}'" + # Deal with deprecated options + { :env => :only, :lib => :require_as }.each do |old, new| + next unless options[old] + options[new] = options.delete(old) + ActiveSupport::Deprecation.warn "#{old.inspect} option in gem is deprecated, use #{new.inspect} instead" + end + + # Deal with deprecated source + if source = options.delete(:source) + ActiveSupport::Deprecation.warn ":source option in gem is deprecated, use add_source method instead" + add_source(source) + end + + # Set the message to be shown in logs. Uses the git repo if one is given, + # otherwise use name (version). + parts, message = [ name.inspect ], name + if version ||= options.delete(:version) + parts << version + message << " (#{version})" + end + message = options[:git] if options[:git] + + log :gemfile, message + + options.each do |option, value| + parts << ":#{option} => #{value.inspect}" + end - if options.any? - opts = options.inject([]) {|result, h| result << [":#{h[0]} => #{h[1].inspect.gsub('"',"'")}"] }.sort.join(", ") - gems_code << ", #{opts}" + in_root do + append_file "Gemfile", "gem #{parts.join(", ")}", :verbose => false end + end + + # Add the given source to Gemfile + # + # ==== Example + # + # source "http://gems.github.com/" + def add_source(source, options={}) + log :source, source - environment gems_code, :env => env + in_root do + prepend_file "Gemfile", "source #{source.inspect}", :verbose => false + end end # Adds a line inside the Initializer block for config/environment.rb. @@ -71,7 +109,7 @@ module Rails in_root do if options[:env].nil? - inject_into_file 'config/environment.rb', "\n #{data}", :after => sentinel, :verbose => false + inject_into_file 'config/application.rb', "\n #{data}", :after => sentinel, :verbose => false else Array.wrap(options[:env]).each do|env| append_file "config/environments/#{env}.rb", "\n#{data}", :verbose => false @@ -79,6 +117,7 @@ module Rails end end end + alias :application :environment # Run a command in git. # @@ -222,9 +261,8 @@ module Rails # # freeze! # - def freeze!(args = {}) - log :vendor, "rails" - in_root { run("#{extify(:rake)} rails:freeze:edge", :verbose => false) } + def freeze!(args={}) + ActiveSupport::Deprecation.warn "freeze! is deprecated since your rails app now comes bundled with Rails by default, please check your Gemfile" end # Make an entry in Rails routing file conifg/routes.rb @@ -251,6 +289,7 @@ module Rails if args.size == 1 say args.first.to_s else + args << (self.behavior == :invoke ? :green : :red) say_status *args end end diff --git a/railties/lib/rails/generators/active_model.rb b/railties/lib/rails/generators/active_model.rb index 1a849a0e02..fe6321af30 100644 --- a/railties/lib/rails/generators/active_model.rb +++ b/railties/lib/rails/generators/active_model.rb @@ -32,7 +32,7 @@ module Rails # GET index def self.all(klass) - raise NotImplementedError + "#{klass}.all" end # GET show @@ -40,34 +40,38 @@ module Rails # PUT update # DELETE destroy def self.find(klass, params=nil) - raise NotImplementedError + "#{klass}.find(#{params})" end # GET new # POST create def self.build(klass, params=nil) - raise NotImplementedError + if params + "#{klass}.new(#{params})" + else + "#{klass}.new" + end end # POST create def save - raise NotImplementedError + "#{name}.save" end # PUT update def update_attributes(params=nil) - raise NotImplementedError + "#{name}.update_attributes(#{params})" end # POST create # PUT update def errors - raise NotImplementedError + "#{name}.errors" end # DELETE destroy def destroy - raise NotImplementedError + "#{name}.destroy" end end end diff --git a/railties/lib/rails/generators/active_record.rb b/railties/lib/rails/generators/active_record.rb index c03ea59c1b..c62f75c384 100644 --- a/railties/lib/rails/generators/active_record.rb +++ b/railties/lib/rails/generators/active_record.rb @@ -1,6 +1,7 @@ require 'rails/generators/named_base' require 'rails/generators/migration' require 'rails/generators/active_model' +require 'active_record' module ActiveRecord module Generators @@ -18,39 +19,5 @@ module ActiveRecord end end end - - class ActiveModel < Rails::Generators::ActiveModel #:nodoc: - def self.all(klass) - "#{klass}.all" - end - - def self.find(klass, params=nil) - "#{klass}.find(#{params})" - end - - def self.build(klass, params=nil) - if params - "#{klass}.new(#{params})" - else - "#{klass}.new" - end - end - - def save - "#{name}.save" - end - - def update_attributes(params=nil) - "#{name}.update_attributes(#{params})" - end - - def errors - "#{name}.errors" - end - - def destroy - "#{name}.destroy" - end - end end end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index 720caa5b3f..226ae63963 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -12,11 +12,24 @@ module Rails add_runtime_options! + # Always move to rails source root. + # + def initialize(*args) #:nodoc: + if !invoked?(args) && defined?(Rails.root) && Rails.root + self.destination_root = Rails.root + FileUtils.cd(destination_root) + end + super + end + # Automatically sets the source root based on the class name. # def self.source_root - @_rails_source_root ||= File.expand_path(File.join(File.dirname(__FILE__), - base_name, generator_name, 'templates')) + @_rails_source_root ||= begin + if base_name && generator_name + File.expand_path(File.join(File.dirname(__FILE__), base_name, generator_name, 'templates')) + end + end end # Tries to get the description from a USAGE file one folder above the source @@ -201,10 +214,13 @@ module Rails # def self.inherited(base) #:nodoc: super - base.source_root # Cache source root - if defined?(RAILS_ROOT) && base.name !~ /Base$/ - path = File.expand_path(File.join(RAILS_ROOT, 'lib', 'templates')) + # Cache source root, we need to do this, since __FILE__ is a relative value + # and can point to wrong directions when inside an specified directory. + base.source_root + + if base.name && base.name !~ /Base$/ && base.base_name && base.generator_name && defined?(Rails.root) && Rails.root + path = File.expand_path(File.join(Rails.root, 'lib', 'templates')) if base.name.include?('::') base.source_paths << File.join(path, base.base_name, base.generator_name) else @@ -247,6 +263,13 @@ module Rails end end + # Check if this generator was invoked from another one by inspecting + # parameters. + # + def invoked?(args) + args.last.is_a?(Hash) && args.last.key?(:invocations) + end + # Use Rails default banner. # def self.banner @@ -256,17 +279,24 @@ module Rails # Sets the base_name taking into account the current class namespace. # def self.base_name - @base_name ||= self.name.split('::').first.underscore + @base_name ||= begin + if base = name.to_s.split('::').first + base.underscore + end + end end # Removes the namespaces and get the generator name. For example, # Rails::Generators::MetalGenerator will return "metal" as generator name. # def self.generator_name - @generator_name ||= begin - klass_name = self.name.split('::').last - klass_name.sub!(/Generator$/, '') - klass_name.underscore + if name + @generator_name ||= begin + if klass_name = name.to_s.split('::').last + klass_name.sub!(/Generator$/, '') + klass_name.underscore + end + end end end @@ -274,35 +304,27 @@ module Rails # Rails::Generators.options. # def self.default_value_for_option(name, options) - config = Rails::Generators.options - generator, base = generator_name.to_sym, base_name.to_sym - - if config[generator] && config[generator].key?(name) - config[generator][name] - elsif config[base] && config[base].key?(name) - config[base][name] - elsif config[:rails].key?(name) - config[:rails][name] - else - options[:default] - end + default_for_option(Rails::Generators.options, name, options, options[:default]) end # Return default aliases for the option name given doing a lookup in # Rails::Generators.aliases. # def self.default_aliases_for_option(name, options) - config = Rails::Generators.aliases - generator, base = generator_name.to_sym, base_name.to_sym + default_for_option(Rails::Generators.aliases, name, options, options[:aliases]) + end - if config[generator] && config[generator].key?(name) - config[generator][name] - elsif config[base] && config[base].key?(name) - config[base][name] + # Return default for the option name given doing a lookup in config. + # + def self.default_for_option(config, name, options, default) + if generator_name and c = config[generator_name.to_sym] and c.key?(name) + c[name] + elsif base_name and c = config[base_name.to_sym] and c.key?(name) + c[name] elsif config[:rails].key?(name) config[:rails][name] else - options[:aliases] + default end end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 0e5976f915..1d4f52286e 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -11,7 +11,10 @@ module Rails alias :file_name :singular_name - def initialize(*args) #:nodoc: + def initialize(args, *options) #:nodoc: + # Unfreeze name in case it's given as a frozen string + args[0] = args[0].dup if args[0].is_a?(String) && args[0].frozen? + super assign_names!(self.name) parse_attributes! if respond_to?(:attributes) diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 78b4b057ae..2bcea4bc8f 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -1,4 +1,4 @@ -require 'digest/md5' +require 'digest/md5' require 'active_support/secure_random' require 'rails/version' unless defined?(Rails::VERSION) @@ -12,9 +12,6 @@ module Rails::Generators class_option :database, :type => :string, :aliases => "-d", :default => "sqlite3", :desc => "Preconfigure for selected database (options: #{DATABASES.join('/')})" - class_option :freeze, :type => :boolean, :aliases => "-F", :default => false, - :desc => "Freeze Rails in vendor/rails from the gems" - class_option :template, :type => :string, :aliases => "-m", :desc => "Path to an application template (can be a filesystem path or URL)." @@ -54,6 +51,7 @@ module Rails::Generators copy_file "Rakefile" copy_file "README" copy_file "config.ru" + template "Gemfile" end def create_app_files @@ -65,6 +63,7 @@ module Rails::Generators inside "config" do copy_file "routes.rb" + template "application.rb" template "environment.rb" directory "environments" @@ -124,8 +123,10 @@ module Rails::Generators end def create_script_files - directory "script" - chmod "script", 0755, :verbose => false + directory "script" do |file| + prepend_file file, "#{shebang}\n", :verbose => false + chmod file, 0755, :verbose => false + end end def create_test_files @@ -153,10 +154,6 @@ module Rails::Generators raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" end - def freeze? - freeze! if options[:freeze] - end - protected attr_accessor :rails_template diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile new file mode 100644 index 0000000000..8e851a64e7 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -0,0 +1,18 @@ +# Edit this Gemfile to bundle your application's dependencies. + +gem "rails", "<%= Rails::VERSION::STRING %>" + +## Bundle edge rails: +# gem "rails", :git => "git://github.com/rails/rails.git" + +## Bundle the gems you use: +# gem "bj" +# gem "hpricot", "0.6" +# gem "sqlite3-ruby", :require_as => "sqlite3" +# gem "aws-s3", :require_as => "aws/s3" + +## Bundle gems used only in certain environments: +# gem "rspec", :only => :test +# only :test do +# gem "webrat" +# end diff --git a/railties/lib/rails/generators/rails/app/templates/Rakefile b/railties/lib/rails/generators/rails/app/templates/Rakefile index bd4dec5389..6b6d07e8cc 100755 --- a/railties/lib/rails/generators/rails/app/templates/Rakefile +++ b/railties/lib/rails/generators/rails/app/templates/Rakefile @@ -1,7 +1,7 @@ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require(File.join(File.dirname(__FILE__), 'config', 'boot')) +require File.expand_path('../config/application', __FILE__) require 'rake' require 'rake/testtask' diff --git a/railties/lib/rails/generators/rails/app/templates/config.ru b/railties/lib/rails/generators/rails/app/templates/config.ru index 50ee033d44..509a0da5b7 100644 --- a/railties/lib/rails/generators/rails/app/templates/config.ru +++ b/railties/lib/rails/generators/rails/app/templates/config.ru @@ -1,5 +1,5 @@ # Require your environment file to bootstrap Rails -require ::File.dirname(__FILE__) + '/config/environment' +require ::File.expand_path('../config/environment', __FILE__) # Dispatch the request -run Rails.application.new +run Rails.application diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb new file mode 100644 index 0000000000..8008c6ba07 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -0,0 +1,41 @@ +require File.expand_path('../boot', __FILE__) + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{root}/extras ) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Skip frameworks you're not going to use. To use Rails without a database, + # you must remove the Active Record framework. +<% if options[:skip_activerecord] -%> + config.frameworks -= [ :active_record ] +<% else -%> + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer +<% end -%> + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. + config.time_zone = 'UTC' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] + # config.i18n.default_locale = :de + + # Configure generators values. Many other options are available, be sure to + # check the documentation. + # config.generators do |g| + # g.orm :active_record + # g.template_engine :erb + # g.test_framework :test_unit, :fixture => true + # end +end diff --git a/railties/lib/rails/generators/rails/app/templates/config/boot.rb b/railties/lib/rails/generators/rails/app/templates/config/boot.rb index 6e0e2279cd..5aa49ca5e6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/boot.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/boot.rb @@ -1,148 +1,16 @@ -# Don't change this file! -# Configure your app in config/environment.rb and config/environments/*.rb - -RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) - -module Rails - # Mark the version of Rails that generated the boot.rb file. This is - # a temporary solution and will most likely be removed as Rails 3.0 - # comes closer. - BOOTSTRAP_VERSION = "3.0" - - class << self - def boot! - unless booted? - preinitialize - pick_boot.run - end - end - - def booted? - defined? Rails::Initializer - end - - def pick_boot - (vendor_rails? ? VendorBoot : GemBoot).new - end - - def vendor_rails? - File.exist?("#{RAILS_ROOT}/vendor/rails") - end - - def preinitialize - load(preinitializer_path) if File.exist?(preinitializer_path) - end - - def preinitializer_path - "#{RAILS_ROOT}/config/preinitializer.rb" - end - end - - class Boot - def run - set_load_paths - load_initializer - end - - def set_load_paths - %w( - actionmailer/lib - actionpack/lib - activemodel/lib - activerecord/lib - activeresource/lib - activesupport/lib - railties/lib - railties - ).reverse_each do |path| - path = "#{framework_root_path}/#{path}" - $LOAD_PATH.unshift(path) if File.directory?(path) - $LOAD_PATH.uniq! - end - end - - def framework_root_path - defined?(::RAILS_FRAMEWORK_ROOT) ? ::RAILS_FRAMEWORK_ROOT : "#{RAILS_ROOT}/vendor/rails" - end +# Use Bundler (preferred) +environment = File.expand_path('../../vendor/gems/environment', __FILE__) +if File.exist?("#{environment}.rb") + require environment + +# Use 2.x style vendor/rails and RubyGems +else + vendor_rails = File.expand_path('../../vendor/rails', __FILE__) + if File.exist?(vendor_rails) + Dir["#{vendor_rails}/*/lib"].each { |path| $:.unshift(path) } end - class VendorBoot < Boot - def load_initializer - require "rails" - install_gem_spec_stubs - Rails::GemDependency.add_frozen_gem_path - end - - def install_gem_spec_stubs - begin; require "rubygems"; rescue LoadError; return; end - - %w(rails activesupport activerecord actionpack actionmailer activeresource).each do |stub| - Gem.loaded_specs[stub] ||= Gem::Specification.new do |s| - s.name = stub - s.version = Rails::VERSION::STRING - s.loaded_from = "" - end - end - end - end - - class GemBoot < Boot - def load_initializer - self.class.load_rubygems - load_rails_gem - require 'rails' - end - - def load_rails_gem - if version = self.class.gem_version - gem 'rails', version - else - gem 'rails' - end - rescue Gem::LoadError => load_error - $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) - exit 1 - end - - class << self - def rubygems_version - Gem::RubyGemsVersion rescue nil - end - - def gem_version - if defined? RAILS_GEM_VERSION - RAILS_GEM_VERSION - elsif ENV.include?('RAILS_GEM_VERSION') - ENV['RAILS_GEM_VERSION'] - else - parse_gem_version(read_environment_rb) - end - end - - def load_rubygems - min_version = '1.3.2' - require 'rubygems' - unless rubygems_version >= min_version - $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) - exit 1 - end - - rescue LoadError - $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) - exit 1 - end - - def parse_gem_version(text) - $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ - end - - private - def read_environment_rb - File.read("#{RAILS_ROOT}/config/environment.rb") - end - end - end + require 'rubygems' end -# All that for this: -Rails.boot! +require 'rails' diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb index adb3a3060a..0bb191f205 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -1,53 +1,5 @@ -# Be sure to restart your server when you modify this file +# Load the rails application +require File.expand_path('../application', __FILE__) -# Specifies gem version of Rails to use when vendor/rails is not present -<%= '# ' if options[:freeze] %>RAILS_GEM_VERSION = '<%= Rails::VERSION::STRING %>' unless defined? RAILS_GEM_VERSION - -# Bootstrap the Rails environment, frameworks, and default configuration -require File.join(File.dirname(__FILE__), 'boot') - -Rails::Initializer.run do |config| - # Settings in config/environments/* take precedence over those specified here. - # Application configuration should go into files in config/initializers - # -- all .rb files in that directory are automatically loaded. - - # Add additional load paths for your own custom dirs - # config.load_paths += %W( #{RAILS_ROOT}/extras ) - - # Specify gems that this application depends on and have them installed with rake gems:install - # config.gem "bj" - # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net" - # config.gem "sqlite3-ruby", :lib => "sqlite3" - # config.gem "aws-s3", :lib => "aws/s3" - - # Only load the plugins named here, in the order given (default is alphabetical). - # :all can be used as a placeholder for all plugins not explicitly named - # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - - # Skip frameworks you're not going to use. To use Rails without a database, - # you must remove the Active Record framework. -<% if options[:skip_activerecord] -%> - config.frameworks -= [ :active_record ] -<% else -%> - # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] - - # Activate observers that should always be running - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer -<% end -%> - - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. - config.time_zone = 'UTC' - - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. - # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}')] - # config.i18n.default_locale = :de - - # Configure generators values. Many other options are available, be sure to - # check the documentation. - # config.generators do |g| - # g.orm :active_record - # g.template_engine :erb - # g.test_framework :test_unit, :fixture => true - # end -end +# Initialize the rails application +Rails.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/script/about.tt b/railties/lib/rails/generators/rails/app/templates/script/about index 1220676c08..93fd007649 100755 --- a/railties/lib/rails/generators/rails/app/templates/script/about.tt +++ b/railties/lib/rails/generators/rails/app/templates/script/about @@ -1,4 +1,3 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) +require File.expand_path('../../config/environment', __FILE__) $LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info" require 'rails/commands/about' diff --git a/railties/lib/rails/generators/rails/app/templates/script/console b/railties/lib/rails/generators/rails/app/templates/script/console new file mode 100755 index 0000000000..20aa799d2f --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/console @@ -0,0 +1,2 @@ +require File.expand_path('../../config/application', __FILE__) +require 'rails/commands/console' diff --git a/railties/lib/rails/generators/rails/app/templates/script/console.tt b/railties/lib/rails/generators/rails/app/templates/script/console.tt deleted file mode 100755 index 5aec193853..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/console.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/console' diff --git a/railties/lib/rails/generators/rails/app/templates/script/dbconsole b/railties/lib/rails/generators/rails/app/templates/script/dbconsole new file mode 100755 index 0000000000..e6a1c59394 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/dbconsole @@ -0,0 +1,2 @@ +require File.expand_path('../../config/application', __FILE__) +require 'rails/commands/dbconsole' diff --git a/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt b/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt deleted file mode 100755 index 632563f470..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/dbconsole.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/dbconsole' diff --git a/railties/lib/rails/generators/rails/app/templates/script/destroy b/railties/lib/rails/generators/rails/app/templates/script/destroy new file mode 100755 index 0000000000..adfa8e8426 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/destroy @@ -0,0 +1,2 @@ +require File.expand_path('../../config/environment', __FILE__) +require 'rails/commands/destroy' diff --git a/railties/lib/rails/generators/rails/app/templates/script/destroy.tt b/railties/lib/rails/generators/rails/app/templates/script/destroy.tt deleted file mode 100755 index c5f94862c3..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/destroy.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/destroy' diff --git a/railties/lib/rails/generators/rails/app/templates/script/generate b/railties/lib/rails/generators/rails/app/templates/script/generate new file mode 100755 index 0000000000..6fb8ad0395 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/generate @@ -0,0 +1,2 @@ +require File.expand_path('../../config/environment', __FILE__) +require 'rails/commands/generate' diff --git a/railties/lib/rails/generators/rails/app/templates/script/generate.tt b/railties/lib/rails/generators/rails/app/templates/script/generate.tt deleted file mode 100755 index d466c94767..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/generate.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/generate' diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker new file mode 100755 index 0000000000..9647d8f10a --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker @@ -0,0 +1,2 @@ +require File.expand_path('../../../config/environment', __FILE__) +require 'rails/commands/performance/benchmarker' diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt b/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt deleted file mode 100755 index 3e03aaa767..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/performance/benchmarker.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../../config/boot', __FILE__) -require 'rails/commands/performance/benchmarker' diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler new file mode 100755 index 0000000000..a5822042d2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler @@ -0,0 +1,2 @@ +require File.expand_path('../../../config/environment', __FILE__) +require 'rails/commands/performance/profiler' diff --git a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt b/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt deleted file mode 100755 index deada2f561..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/performance/profiler.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../../config/boot', __FILE__) -require 'rails/commands/performance/profiler' diff --git a/railties/lib/rails/generators/rails/app/templates/script/plugin b/railties/lib/rails/generators/rails/app/templates/script/plugin new file mode 100755 index 0000000000..1f1af6c880 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/plugin @@ -0,0 +1,2 @@ +require File.expand_path('../../config/application', __FILE__) +require 'rails/commands/plugin' diff --git a/railties/lib/rails/generators/rails/app/templates/script/plugin.tt b/railties/lib/rails/generators/rails/app/templates/script/plugin.tt deleted file mode 100755 index f4081f56b6..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/plugin.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/plugin' diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner b/railties/lib/rails/generators/rails/app/templates/script/runner new file mode 100755 index 0000000000..7a70828e90 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/runner @@ -0,0 +1,2 @@ +require File.expand_path('../../config/environment', __FILE__) +require 'rails/commands/runner' diff --git a/railties/lib/rails/generators/rails/app/templates/script/runner.tt b/railties/lib/rails/generators/rails/app/templates/script/runner.tt deleted file mode 100755 index 60c8c0bba1..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/runner.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/runner' diff --git a/railties/lib/rails/generators/rails/app/templates/script/server b/railties/lib/rails/generators/rails/app/templates/script/server new file mode 100755 index 0000000000..a7aaee2953 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/script/server @@ -0,0 +1,2 @@ +require File.expand_path('../../config/application', __FILE__) +require 'rails/commands/server' diff --git a/railties/lib/rails/generators/rails/app/templates/script/server.tt b/railties/lib/rails/generators/rails/app/templates/script/server.tt deleted file mode 100755 index 54e3346bec..0000000000 --- a/railties/lib/rails/generators/rails/app/templates/script/server.tt +++ /dev/null @@ -1,3 +0,0 @@ -<%= shebang %> -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands/server' diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb index 4b60558b43..a3dc38d9e4 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb @@ -1,5 +1,5 @@ require 'test_helper' -require 'performance_test_help' +require 'rails/performance_test_help' # Profiling results for each test method are written to tmp/performance. class BrowsingTest < ActionController::PerformanceTest diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index d4b0d4b945..99954e2292 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -1,9 +1,13 @@ +require 'rails/generators/active_model' + module Rails module Generators # Deal with controller names on scaffold and add some helpers to deal with # ActiveModel. # module ResourceHelpers + mattr_accessor :skip_warn + def self.included(base) #:nodoc: base.send :attr_reader, :controller_name, :controller_class_name, :controller_file_name, :controller_class_path, :controller_file_path @@ -17,7 +21,11 @@ module Rails super if name == name.pluralize && !options[:force_plural] - say "Plural version of the model detected, using singularized version. Override with --force-plural." + unless ResourceHelpers.skip_warn + say "Plural version of the model detected, using singularized version. Override with --force-plural." + ResourceHelpers.skip_warn = true + end + name.replace name.singularize assign_names!(self.name) end @@ -47,20 +55,11 @@ module Rails raise "You need to have :orm as class option to invoke orm_class and orm_instance" end - active_model = "#{options[:orm].to_s.classify}::Generators::ActiveModel" - - # If the orm was not loaded, try to load it at "generators/orm", - # for example "generators/active_record" or "generators/sequel". begin - klass = active_model.constantize - rescue NameError - require "rails/generators/#{options[:orm]}" + "#{options[:orm].to_s.classify}::Generators::ActiveModel".constantize + rescue NameError => e + Rails::Generators::ActiveModel end - - # Try once again after loading the file with success. - klass ||= active_model.constantize - rescue Exception => e - raise Error, "Could not load #{active_model}, skipping controller. Error: #{e.message}." end end diff --git a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb index 27c91b0fca..362e3dc09f 100644 --- a/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb +++ b/railties/lib/rails/generators/test_unit/performance/templates/performance_test.rb @@ -1,5 +1,5 @@ require 'test_helper' -require 'performance_test_help' +require 'rails/performance_test_help' class <%= class_name %>Test < ActionController::PerformanceTest # Replace this with your real tests. diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index 4bd5088207..3866b856b2 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -1,85 +1,117 @@ module Rails module Initializable + def self.included(base) + base.extend ClassMethods + end - # A collection of initializers - class Collection - def initialize(context) - @context = context - @keys = [] - @values = {} - @ran = false - end + class Initializer + attr_reader :name, :before, :after, :global, :block - def run - return self if @ran - each do |key, initializer| - @context.class_eval(&initializer.block) - end - @ran = true - self + def initialize(name, context, options, &block) + @name, @context, @options, @block = name, context, options, block end - def [](key) - keys, values = merge_with_parent - values[key.to_sym] + def before + @options[:before] end - def []=(key, value) - key = key.to_sym - @keys |= [key] - @values[key] = value + def after + @options[:after] end - def each - keys, values = merge_with_parent - keys.each { |k| yield k, values[k] } - self + def global + @options[:global] end - protected - - attr_reader :keys, :values + alias global? global - private + def run(*args) + @context.instance_exec(*args, &block) + end - def merge_with_parent - keys, values = [], {} + def bind(context) + return self if @context + Initializer.new(@name, context, @options, &block) + end + end - if @context.is_a?(Class) && @context.superclass.is_a?(Initializable) - parent = @context.superclass.initializers - keys, values = parent.keys, parent.values + class Collection < Array + def initialize(initializers = []) + super() + initializers.each do |initializer| + if initializer.before + index = index_for(initializer.before) + elsif initializer.after + index = index_for(initializer.after) + index += 1 if index + else + index = length + end + insert(index || -1, initializer) end - - values = values.merge(@values) - return keys | @keys, values end - end - - class Initializer - attr_reader :name, :options, :block + def +(other) + Collection.new(to_a + other.to_a) + end - def initialize(name, options = {}, &block) - @name, @options, @block = name, options, block + def index_for(name) + initializer = find { |i| i.name == name } + initializer && index(initializer) end end - def initializer(name, options = {}, &block) - @initializers ||= Collection.new(self) - @initializers[name] = Initializer.new(name, options, &block) + def run_initializers(*args) + return if @ran + initializers.each do |initializer| + initializer.run(*args) + end + @ran = true end def initializers - @initializers ||= Collection.new(self) + @initializers ||= begin + initializers = self.class.initializers_for(:instance) + Collection.new(initializers.map { |i| i.bind(self) }) + end end + module ClassMethods + def initializers + @initializers ||= [] + end + + def initializers_for(scope = :global) + initializers = Collection.new + ancestors.reverse_each do |klass| + next unless klass.respond_to?(:initializers) + initializers = initializers + klass.initializers.select { |i| + (scope == :global) == !!i.global? + } + end + initializers + end + + def initializer(name, opts = {}, &blk) + @initializers ||= [] + @initializers << Initializer.new(name, nil, opts, &blk) + end + + def run_initializers(*args) + return if @ran + initializers_for(:global).each do |initializer| + instance_exec(*args, &initializer.block) + end + @ran = true + end + end end - extend Initializable + include Initializable # Check for valid Ruby version (1.8.2 or 1.8.4 or higher). This is done in an # external file, so we can use it from the `rails` program as well without duplication. - initializer :check_ruby_version do + initializer :check_ruby_version, :global => true do require 'rails/ruby_version_check' end @@ -89,7 +121,7 @@ module Rails # on ActionController::Base. # # For Ruby 1.9, UTF-8 is the default internal and external encoding. - initializer :initialize_encoding do + initializer :initialize_encoding, :global => true do if RUBY_VERSION < '1.9' $KCODE='u' else diff --git a/railties/lib/rails/initializer.rb b/railties/lib/rails/initializer.rb index f7c3774450..44d04688c8 100644 --- a/railties/lib/rails/initializer.rb +++ b/railties/lib/rails/initializer.rb @@ -1,14 +1,4 @@ -require "pathname" - -require 'rails/initializable' -require 'rails/application' -require 'rails/railties_path' -require 'rails/version' -require 'rails/gem_dependency' -require 'rails/rack' -require 'rails/paths' -require 'rails/core' -require 'rails/configuration' +require "rails" # In case people require this file directly RAILS_ENV = (ENV['RAILS_ENV'] || 'development').dup unless defined?(RAILS_ENV) @@ -21,8 +11,7 @@ module Rails else Rails.application = Class.new(Application) yield Rails.application.config if block_given? - Rails.application.new end end end -end +end
\ No newline at end of file diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 3899b744b0..b3d105d8c7 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -19,14 +19,14 @@ module Rails class Root include PathParent - attr_reader :path + attr_accessor :path + def initialize(path) - raise unless path.is_a?(String) + raise if path.is_a?(Array) @children = {} - # TODO: Move logic from set_root_path initializer - @path = File.expand_path(path) + @path = path @root = self @all_paths = [] end @@ -64,7 +64,7 @@ module Rails end class Path - include PathParent + include PathParent, Enumerable attr_reader :path attr_accessor :glob @@ -83,6 +83,10 @@ module Rails @root.all_paths << self end + def each + to_a.each { |p| yield p } + end + def push(path) @paths.push path end @@ -123,8 +127,10 @@ module Rails end def paths + raise "You need to set a path root" unless @root.path + @paths.map do |path| - path.index('/') == 0 ? path : File.join(@root.path, path) + path.index('/') == 0 ? path : File.expand_path(File.join(@root.path, path)) end end diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb index 1c0af6411a..86bf032641 100644 --- a/railties/lib/rails/plugin.rb +++ b/railties/lib/rails/plugin.rb @@ -1,180 +1,64 @@ module Rails - # The Plugin class should be an object which provides the following methods: - # - # * +name+ - Used during initialisation to order the plugin (based on name and - # the contents of <tt>config.plugins</tt>). - # * +valid?+ - Returns true if this plugin can be loaded. - # * +load_paths+ - Each path within the returned array will be added to the <tt>$LOAD_PATH</tt>. - # * +load+ - Finally 'load' the plugin. - # - # These methods are expected by the Rails::Plugin::Locator and Rails::Plugin::Loader classes. - # The default implementation returns the <tt>lib</tt> directory as its <tt>load_paths</tt>, - # and evaluates <tt>init.rb</tt> when <tt>load</tt> is called. - # - # You can also inspect the about.yml data programmatically: - # - # plugin = Rails::Plugin.new(path_to_my_plugin) - # plugin.about["author"] # => "James Adam" - # plugin.about["url"] # => "http://interblah.net" class Plugin - include Comparable - - attr_reader :directory, :name - - def initialize(directory) - @directory = directory - @name = File.basename(@directory) rescue nil - @loaded = false - end - - def valid? - File.directory?(directory) && (has_app_directory? || has_lib_directory? || has_init_file?) - end - - # Returns a list of paths this plugin wishes to make available in <tt>$LOAD_PATH</tt>. - def load_paths - report_nonexistant_or_empty_plugin! unless valid? - - load_paths = [] - load_paths << lib_path if has_lib_directory? - load_paths << app_paths if has_app_directory? - load_paths.flatten - end - - # Evaluates a plugin's init.rb file. - def load(initializer) - return if loaded? - report_nonexistant_or_empty_plugin! unless valid? - evaluate_init_rb(initializer) - @loaded = true - end - - def loaded? - @loaded - end - - def <=>(other_plugin) - name <=> other_plugin.name - end - - def about - @about ||= load_about_information - end - - # Engines are plugins with an app/ directory. - def engine? - has_app_directory? - end - - # Returns true if the engine ships with a routing file - def routed? - File.exist?(routing_file) - end - - # Returns true if there is any localization file in locale_path - def localized? - locale_files.any? - end - - def view_path - File.join(directory, 'app', 'views') - end - - def controller_path - File.join(directory, 'app', 'controllers') - end - - def metal_path - File.join(directory, 'app', 'metal') - end - - def routing_file - File.join(directory, 'config', 'routes.rb') - end - - def locale_path - File.join(directory, 'config', 'locales') - end - - def locale_files - Dir[ File.join(locale_path, '*.{rb,yml}') ] - end - + include Initializable + + class Vendored < Plugin + def self.all(list, paths) + plugins = [] + paths.each do |path| + Dir["#{path}/*"].each do |plugin_path| + plugin = new(plugin_path) + next unless list.include?(plugin.name) || list.include?(:all) + plugins << plugin + end + end - private - def load_about_information - about_yml_path = File.join(@directory, "about.yml") - parsed_yml = File.exist?(about_yml_path) ? YAML.load(File.read(about_yml_path)) : {} - parsed_yml || {} - rescue Exception - {} + plugins.sort_by do |p| + [list.index(p.name) || list.index(:all), p.name.to_s] + end end - def report_nonexistant_or_empty_plugin! - raise LoadError, "Can not find the plugin named: #{name}" - end + attr_reader :name, :path - - def app_paths - [ File.join(directory, 'app', 'models'), File.join(directory, 'app', 'helpers'), controller_path, metal_path ] - end - - def lib_path - File.join(directory, 'lib') + def initialize(path) + @name = File.basename(path).to_sym + @path = path end - def classic_init_path - File.join(directory, 'init.rb') + def load_paths + Dir["#{path}/{lib}", "#{path}/app/{models,controllers,helpers}"] end - def gem_init_path - File.join(directory, 'rails', 'init.rb') - end + initializer :add_to_load_path, :after => :set_autoload_paths do |app| + load_paths.each do |path| + $LOAD_PATH << path + require "active_support/dependencies" - def init_path - File.file?(gem_init_path) ? gem_init_path : classic_init_path - end + ActiveSupport::Dependencies.load_paths << path - - def has_app_directory? - File.directory?(File.join(directory, 'app')) + unless app.config.reload_plugins + ActiveSupport::Dependencies.load_once_paths << path + end + end end - def has_lib_directory? - File.directory?(lib_path) + initializer :load_init_rb, :before => :load_application_initializers do |app| + file = "#{@path}/init.rb" + config = app.config + eval File.read(file), binding, file if File.file?(file) end - def has_init_file? - File.file?(init_path) + initializer :add_view_paths, :after => :initialize_framework_views do + ActionController::Base.view_paths.concat ["#{path}/app/views"] if File.directory?("#{path}/app/views") end - - def evaluate_init_rb(initializer) - if has_init_file? - require 'active_support/core_ext/kernel/reporting' - silence_warnings do - # Allow plugins to reference the current configuration object - config = initializer.configuration - - eval(IO.read(init_path), binding, init_path) - end + initializer :add_routing_file, :after => :initialize_routing do |app| + routing_file = "#{path}/config/routes.rb" + if File.exist?(routing_file) + app.routes.add_configuration_file(routing_file) + app.routes.reload! end - end - end - - # This Plugin subclass represents a Gem plugin. Although RubyGems has already - # taken care of $LOAD_PATHs, it exposes its load_paths to add them - # to Dependencies.load_paths. - class GemPlugin < Plugin - # Initialize this plugin from a Gem::Specification. - def initialize(spec, gem) - directory = spec.full_gem_path - super(directory) - @name = spec.name - end - - def init_path - File.join(directory, 'rails', 'init.rb') + end end end -end +end
\ No newline at end of file diff --git a/railties/lib/rails/plugin/loader.rb b/railties/lib/rails/plugin/loader.rb deleted file mode 100644 index 0d16cbd7c3..0000000000 --- a/railties/lib/rails/plugin/loader.rb +++ /dev/null @@ -1,200 +0,0 @@ -require "rails/plugin" - -module Rails - class Plugin - class Loader - attr_reader :initializer - - # Creates a new Plugin::Loader instance, associated with the given - # Rails::Initializer. This default implementation automatically locates - # all plugins, and adds all plugin load paths, when it is created. The plugins - # are then fully loaded (init.rb is evaluated) when load_plugins is called. - # - # It is the loader's responsibility to ensure that only the plugins specified - # in the configuration are actually loaded, and that the order defined - # is respected. - def initialize(initializer) - @initializer = initializer - end - - # Returns the plugins to be loaded, in the order they should be loaded. - def plugins - @plugins ||= all_plugins.select { |plugin| should_load?(plugin) }.sort { |p1, p2| order_plugins(p1, p2) } - end - - # Returns the plugins that are in engine-form (have an app/ directory) - def engines - @engines ||= plugins.select {|plugin| plugin.engine? } - end - - # Returns all the plugins that could be found by the current locators. - def all_plugins - @all_plugins ||= locate_plugins - @all_plugins - end - - def load_plugins - plugins.each do |plugin| - plugin.load(initializer) - register_plugin_as_loaded(plugin) - end - - configure_engines - - ensure_all_registered_plugins_are_loaded! - end - - # Adds the load paths for every plugin into the $LOAD_PATH. Plugin load paths are - # added *after* the application's <tt>lib</tt> directory, to ensure that an application - # can always override code within a plugin. - # - # Plugin load paths are also added to Dependencies.load_paths, and Dependencies.load_once_paths. - def add_plugin_load_paths - plugins.each do |plugin| - plugin.load_paths.each do |path| - $LOAD_PATH.insert(application_lib_index + 1, path) - - ActiveSupport::Dependencies.load_paths << path - - unless configuration.reload_plugins? - ActiveSupport::Dependencies.load_once_paths << path - end - end - end - - $LOAD_PATH.uniq! - end - - def engine_metal_paths - engines.collect {|engine| engine.metal_path } - end - - protected - def configure_engines - if engines.any? - add_engine_routing_configurations - add_engine_locales - add_engine_controller_paths - add_engine_view_paths - end - end - - def add_engine_routing_configurations - engines.select {|engine| engine.routed? }.map {|engine| engine.routing_file }.each do |routing_file| - ActionController::Routing::Routes.add_configuration_file(routing_file) - end - end - - def add_engine_locales - localized_engines = engines.select { |engine| engine.localized? } - - # reverse it such that the last engine can overwrite translations from the first, like with routes - locale_files = localized_engines.collect { |engine| engine.locale_files }.reverse.flatten - I18n.load_path += locale_files - I18n.load_path - end - - def add_engine_controller_paths - ActionController::Routing.controller_paths += engines.collect {|engine| engine.controller_path } - end - - def add_engine_view_paths - # reverse it such that the last engine can overwrite view paths from the first, like with routes - paths = ActionView::PathSet.new(engines.collect {|engine| engine.view_path }.reverse) - ActionController::Base.view_paths.concat(paths) - ActionMailer::Base.view_paths.concat(paths) if configuration.frameworks.include?(:action_mailer) - end - - # The locate_plugins method uses each class in config.plugin_locators to - # find the set of all plugins available to this Rails application. - def locate_plugins - configuration.plugin_locators.map do |locator| - locator.new(initializer).plugins - end.flatten - # TODO: sorting based on config.plugins - end - - def register_plugin_as_loaded(plugin) - initializer.config.loaded_plugins << plugin - end - - def configuration - initializer.configuration - end - - def should_load?(plugin) - # uses Plugin#name and Plugin#valid? - enabled?(plugin) && plugin.valid? - end - - def order_plugins(plugin_a, plugin_b) - if !explicit_plugin_loading_order? - plugin_a <=> plugin_b - else - if !explicitly_enabled?(plugin_a) && !explicitly_enabled?(plugin_b) - plugin_a <=> plugin_b - else - effective_order_of(plugin_a) <=> effective_order_of(plugin_b) - end - end - end - - def effective_order_of(plugin) - if explicitly_enabled?(plugin) - registered_plugin_names.index(plugin.name) - else - registered_plugin_names.index('all') - end - end - - def application_lib_index - $LOAD_PATH.index(File.join(RAILS_ROOT, 'lib')) || 0 - end - - def enabled?(plugin) - !explicit_plugin_loading_order? || registered?(plugin) - end - - def explicit_plugin_loading_order? - !registered_plugin_names.nil? - end - - def registered?(plugin) - explicit_plugin_loading_order? && registered_plugins_names_plugin?(plugin) - end - - def explicitly_enabled?(plugin) - !explicit_plugin_loading_order? || explicitly_registered?(plugin) - end - - def explicitly_registered?(plugin) - explicit_plugin_loading_order? && registered_plugin_names.include?(plugin.name) - end - - def registered_plugins_names_plugin?(plugin) - registered_plugin_names.include?(plugin.name) || registered_plugin_names.include?('all') - end - - # The plugins that have been explicitly listed with config.plugins. If this list is nil - # then it means the client does not care which plugins or in what order they are loaded, - # so we load all in alphabetical order. If it is an empty array, we load no plugins, if it is - # non empty, we load the named plugins in the order specified. - def registered_plugin_names - configuration.plugins ? configuration.plugins.map {|plugin| plugin.to_s } : nil - end - - def loaded?(plugin_name) - initializer.config.loaded_plugins.detect { |plugin| plugin.name == plugin_name.to_s } - end - - def ensure_all_registered_plugins_are_loaded! - if explicit_plugin_loading_order? - if configuration.plugins.detect {|plugin| plugin != :all && !loaded?(plugin) } - missing_plugins = configuration.plugins - (plugins.map{|p| p.name.to_sym} + [:all]) - raise LoadError, "Could not locate the following plugins: #{missing_plugins.to_sentence(:locale => :en)}" - end - end - end - - end - end -end diff --git a/railties/lib/rails/plugin/locator.rb b/railties/lib/rails/plugin/locator.rb deleted file mode 100644 index 1057c004e0..0000000000 --- a/railties/lib/rails/plugin/locator.rb +++ /dev/null @@ -1,100 +0,0 @@ -module Rails - class Plugin - - # The Plugin::Locator class should be subclasses to provide custom plugin-finding - # abilities to Rails (i.e. loading plugins from Gems, etc). Each subclass should implement - # the <tt>located_plugins</tt> method, which return an array of Plugin objects that have been found. - class Locator - include Enumerable - - attr_reader :initializer - - def initialize(initializer) - @initializer = initializer - end - - # This method should return all the plugins which this Plugin::Locator can find - # These will then be used by the current Plugin::Loader, which is responsible for actually - # loading the plugins themselves - def plugins - raise "The `plugins' method must be defined by concrete subclasses of #{self.class}" - end - - def each(&block) - plugins.each(&block) - end - - def plugin_names - plugins.map {|plugin| plugin.name } - end - end - - # The Rails::Plugin::FileSystemLocator will try to locate plugins by examining the directories - # in the paths given in configuration.plugin_paths. Any plugins that can be found are returned - # in a list. - # - # The criteria for a valid plugin in this case is found in Rails::Plugin#valid?, although - # other subclasses of Rails::Plugin::Locator can of course use different conditions. - class FileSystemLocator < Locator - - # Returns all the plugins which can be loaded in the filesystem, under the paths given - # by configuration.plugin_paths. - def plugins - initializer.configuration.plugin_paths.flatten.inject([]) do |plugins, path| - plugins.concat locate_plugins_under(path) - plugins - end.flatten - end - - private - - # Attempts to create a plugin from the given path. If the created plugin is valid? - # (see Rails::Plugin#valid?) then the plugin instance is returned; otherwise nil. - def create_plugin(path) - plugin = Rails::Plugin.new(path) - plugin.valid? ? plugin : nil - end - - # This starts at the base path looking for valid plugins (see Rails::Plugin#valid?). - # Since plugins can be nested arbitrarily deep within an unspecified number of intermediary - # directories, this method runs recursively until it finds a plugin directory, e.g. - # - # locate_plugins_under('vendor/plugins/acts/acts_as_chunky_bacon') - # => <Rails::Plugin name: 'acts_as_chunky_bacon' ... > - # - def locate_plugins_under(base_path) - Dir.glob(File.join(base_path, '*')).sort.inject([]) do |plugins, path| - if plugin = create_plugin(path) - plugins << plugin - elsif File.directory?(path) - plugins.concat locate_plugins_under(path) - end - plugins - end - end - end - - # The GemLocator scans all the loaded RubyGems, looking for gems with - # a <tt>rails/init.rb</tt> file. - class GemLocator < Locator - def plugins - gem_index = initializer.configuration.gems.inject({}) { |memo, gem| memo.update gem.specification => gem } - specs = gem_index.keys - 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" - - deps = Gem::DependencyList.new - deps.add(*specs) unless specs.empty? - - deps.dependency_order.collect do |spec| - Rails::GemPlugin.new(spec, gem_index[spec]) - end - end - end - end -end
\ No newline at end of file diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 68d3acc876..62d7804bf3 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,13 +1,6 @@ -min_release = "1.8.2 (2004-12-25)" +min_release = "1.8.7" ruby_release = "#{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" -if ruby_release =~ /1\.8\.3/ - abort <<-end_message - - Rails does not work with Ruby version 1.8.3. - Please upgrade to version 1.8.4 or downgrade to 1.8.2. - - end_message -elsif ruby_release < min_release +if ruby_release < min_release abort <<-end_message Rails requires Ruby version #{min_release} or later. diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index aad965306c..82113a297c 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -6,7 +6,6 @@ $VERBOSE = nil databases documentation framework - gems log middleware misc @@ -20,5 +19,5 @@ end # Load any custom rakefile extensions # TODO: Don't hardcode these paths. -Dir["#{RAILS_ROOT}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext } -Dir["#{RAILS_ROOT}/lib/tasks/**/*.rake"].sort.each { |ext| load ext } +Dir["#{Rails.root}/vendor/plugins/*/**/tasks/**/*.rake"].sort.each { |ext| load ext } +Dir["#{Rails.root}/lib/tasks/**/*.rake"].sort.each { |ext| load ext } diff --git a/railties/lib/rails/tasks/databases.rake b/railties/lib/rails/tasks/databases.rake index ed015e7a67..a35a6c156b 100644 --- a/railties/lib/rails/tasks/databases.rake +++ b/railties/lib/rails/tasks/databases.rake @@ -283,7 +283,7 @@ namespace :db do desc "Create a db/schema.rb file that can be portably used against any DB supported by AR" task :dump => :environment do require 'active_record/schema_dumper' - File.open(ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb", "w") do |file| + File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) end Rake::Task["db:schema:dump"].reenable @@ -291,11 +291,11 @@ namespace :db do desc "Load a schema.rb file into the database" task :load => :environment do - file = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb" + file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" if File.exists?(file) load(file) else - abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{RAILS_ROOT}/config/environment.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} + abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to prevent active_record from loading: config.frameworks -= [ :active_record ]} end end end @@ -307,7 +307,7 @@ namespace :db do case abcs[RAILS_ENV]["adapter"] when "mysql", "oci", "oracle" ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) - File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } + File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } when "postgresql" ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] @@ -327,13 +327,13 @@ namespace :db do when "firebird" set_firebird_env(abcs[RAILS_ENV]) db_string = firebird_db_string(abcs[RAILS_ENV]) - sh "isql -a #{db_string} > #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql" + sh "isql -a #{db_string} > #{Rails.root}/db/#{RAILS_ENV}_structure.sql" else raise "Task not supported by '#{abcs["test"]["adapter"]}'" end if ActiveRecord::Base.connection.supports_migrations? - File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + File.open("#{Rails.root}/db/#{RAILS_ENV}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } end end end @@ -356,28 +356,28 @@ namespace :db do when "mysql" ActiveRecord::Base.establish_connection(:test) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| + IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end when "postgresql" ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` + `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` when "sqlite", "sqlite3" dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"] - `#{abcs["test"]["adapter"]} #{dbfile} < #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql` + `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{RAILS_ENV}_structure.sql` when "sqlserver" `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` when "oci", "oracle" ActiveRecord::Base.establish_connection(:test) - IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| + IO.readlines("#{Rails.root}/db/#{RAILS_ENV}_structure.sql").join.split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end when "firebird" set_firebird_env(abcs["test"]) db_string = firebird_db_string(abcs["test"]) - sh "isql -i #{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql #{db_string}" + sh "isql -i #{Rails.root}/db/#{RAILS_ENV}_structure.sql #{db_string}" else raise "Task not supported by '#{abcs["test"]["adapter"]}'" end @@ -446,7 +446,7 @@ def drop_database(config) when /^sqlite/ require 'pathname' path = Pathname.new(config['database']) - file = path.absolute? ? path.to_s : File.join(RAILS_ROOT, path) + file = path.absolute? ? path.to_s : File.join(Rails.root, path) FileUtils.rm(file) when 'postgresql' diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 16dd0af44e..1611d1d94d 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -86,7 +86,7 @@ namespace :rails do template = File.expand_path(template) if template !~ %r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} require 'generators' - generator = Rails::Generators::App.new [ RAILS_ROOT ], {}, :destination_root => RAILS_ROOT + generator = Rails::Generators::App.new [ Rails.root ], {}, :destination_root => Rails.root generator.apply template, :verbose => false end @@ -96,15 +96,10 @@ namespace :rails do require 'rails/generators/rails/app/app_generator' generator = Rails::Generators::AppGenerator.new ["rails"], { :with_dispatchers => true }, - :destination_root => RAILS_ROOT + :destination_root => Rails.root generator.invoke(method) end - desc "Update config/boot.rb from your current rails install" - task :configs do - invoke_from_app_generator :create_boot_file - end - desc "Update Prototype javascripts from your current rails install" task :javascripts do invoke_from_app_generator :create_prototype_files @@ -117,8 +112,8 @@ namespace :rails do desc "Rename application.rb to application_controller.rb" task :application_controller do - old_style = RAILS_ROOT + '/app/controllers/application.rb' - new_style = RAILS_ROOT + '/app/controllers/application_controller.rb' + old_style = Rails.root + '/app/controllers/application.rb' + new_style = Rails.root + '/app/controllers/application_controller.rb' if File.exists?(old_style) && !File.exists?(new_style) FileUtils.mv(old_style, new_style) puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary" diff --git a/railties/lib/rails/tasks/gems.rake b/railties/lib/rails/tasks/gems.rake deleted file mode 100644 index f1c34c7cca..0000000000 --- a/railties/lib/rails/tasks/gems.rake +++ /dev/null @@ -1,78 +0,0 @@ -desc "List the gems that this rails application depends on" -task :gems => 'gems:base' do - Rails.configuration.gems.each do |gem| - print_gem_status(gem) - end - puts - puts "I = Installed" - puts "F = Frozen" - puts "R = Framework (loaded before rails starts)" -end - -namespace :gems do - task :base do - $gems_rake_task = true - require 'rubygems' - require 'rubygems/gem_runner' - Rake::Task[:environment].invoke - end - - desc "Build any native extensions for unpacked gems" - task :build do - $gems_build_rake_task = true - frozen_gems.each { |gem| gem.build } - end - - namespace :build do - desc "Force the build of all gems" - task :force do - $gems_build_rake_task = true - frozen_gems.each { |gem| gem.build(:force => true) } - end - end - - desc "Installs all required gems." - task :install => :base do - current_gems.each { |gem| gem.install } - end - - desc "Unpacks all required gems into vendor/gems." - task :unpack => :install do - current_gems.each { |gem| gem.unpack } - end - - namespace :unpack do - desc "Unpacks all required gems and their dependencies into vendor/gems." - task :dependencies => :install do - current_gems.each { |gem| gem.unpack(:recursive => true) } - end - end - - desc "Regenerate gem specifications in correct format." - task :refresh_specs do - frozen_gems(false).each { |gem| gem.refresh } - end -end - -def current_gems - gems = Rails.configuration.gems - gems = gems.select { |gem| gem.name == ENV['GEM'] } unless ENV['GEM'].blank? - gems -end - -def frozen_gems(load_specs=true) - Dir[File.join(RAILS_ROOT, 'vendor', 'gems', '*-*')].map do |gem_dir| - Rails::GemDependency.from_directory_name(gem_dir, load_specs) - end -end - -def print_gem_status(gem, indent=1) - code = case - when gem.framework_gem? then 'R' - when gem.frozen? then 'F' - when gem.installed? then 'I' - else ' ' - end - puts " "*(indent-1)+" - [#{code}] #{gem.name} #{gem.requirement.to_s}" - gem.dependencies.each { |g| print_gem_status(g, indent+1) } -end diff --git a/railties/lib/rails/tasks/misc.rake b/railties/lib/rails/tasks/misc.rake index fb2fc31dc1..7f244ebaed 100644 --- a/railties/lib/rails/tasks/misc.rake +++ b/railties/lib/rails/tasks/misc.rake @@ -1,7 +1,7 @@ task :default => :test task :environment do $rails_rake_task = true - require(File.join(RAILS_ROOT, 'config', 'environment')) + require(File.join(Rails.root, 'config', 'environment')) end task :rails_env do diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index abbf3258c1..2395d73b2f 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -3,16 +3,13 @@ task :routes => :environment do all_routes = ENV['CONTROLLER'] ? ActionController::Routing::Routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : ActionController::Routing::Routes.routes routes = all_routes.collect do |route| name = ActionController::Routing::Routes.named_routes.routes.index(route).to_s - verb = route.conditions[:method].to_s.upcase - segs = route.segments.inject("") { |str,s| str << s.to_s } - segs.chop! if segs.length > 1 reqs = route.requirements.empty? ? "" : route.requirements.inspect - {:name => name, :verb => verb, :segs => segs, :reqs => reqs} + {:name => name, :verb => route.verb.to_s, :path => route.path, :reqs => reqs} end name_width = routes.collect {|r| r[:name]}.collect {|n| n.length}.max verb_width = routes.collect {|r| r[:verb]}.collect {|v| v.length}.max - segs_width = routes.collect {|r| r[:segs]}.collect {|s| s.length}.max + path_width = routes.collect {|r| r[:path]}.collect {|s| s.length}.max routes.each do |r| - puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:segs].ljust(segs_width)} #{r[:reqs]}" + puts "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" end end diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 2dcc7bdf9d..40f8c1034a 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -1,14 +1,13 @@ STATS_DIRECTORIES = [ %w(Controllers app/controllers), - %w(Helpers app/helpers), + %w(Helpers app/helpers), %w(Models app/models), %w(Libraries lib/), %w(APIs app/apis), %w(Integration\ tests test/integration), %w(Functional\ tests test/functional), %w(Unit\ tests test/unit) - -].collect { |name, dir| [ name, "#{RAILS_ROOT}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } +].collect { |name, dir| [ name, "#{Rails.root}/#{dir}" ] }.select { |name, dir| File.directory?(dir) } desc "Report code statistics (KLOCs, etc) from the application" task :stats do diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 8bd4475c7b..9f6c42945f 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -17,7 +17,7 @@ if defined?(ActiveRecord) class ActiveSupport::TestCase include ActiveRecord::TestFixtures - self.fixture_path = "#{RAILS_ROOT}/test/fixtures/" + self.fixture_path = "#{Rails.root}/test/fixtures/" self.use_instantiated_fixtures = false self.use_transactional_fixtures = true end diff --git a/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor b/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor deleted file mode 100755 index 50c7410d80..0000000000 --- a/railties/lib/rails/vendor/thor-0.11.6/bin/rake2thor +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env ruby - -require 'rubygems' -require 'ruby2ruby' -require 'parse_tree' -if Ruby2Ruby::VERSION >= "1.2.0" - require 'parse_tree_extensions' -end -require 'rake' - -input = ARGV[0] || 'Rakefile' -output = ARGV[1] || 'Thorfile' - -$requires = [] - -module Kernel - def require_with_record(file) - $requires << file if caller[1] =~ /rake2thor:/ - require_without_record file - end - alias_method :require_without_record, :require - alias_method :require, :require_with_record -end - -load input - -@private_methods = [] - -def file_task_name(name) - "compile_" + name.gsub('/', '_slash_').gsub('.', '_dot_').gsub(/\W/, '_') -end - -def method_for_task(task) - file_task = task.is_a?(Rake::FileTask) - comment = task.instance_variable_get('@comment') - prereqs = task.instance_variable_get('@prerequisites').select(&Rake::Task.method(:task_defined?)) - actions = task.instance_variable_get('@actions') - name = task.name.gsub(/^([^:]+:)+/, '') - name = file_task_name(name) if file_task - meth = '' - - meth << "desc #{name.inspect}, #{comment.inspect}\n" if comment - meth << "def #{name}\n" - - meth << prereqs.map do |pre| - pre = pre.to_s - pre = file_task_name(pre) if Rake::Task[pre].is_a?(Rake::FileTask) - ' ' + pre - end.join("\n") - - meth << "\n\n" unless prereqs.empty? || actions.empty? - - meth << actions.map do |act| - act = act.to_ruby - unless act.gsub!(/^proc \{ \|(\w+)\|\n/, - " \\1 = Struct.new(:name).new(#{name.inspect}) # A crude mock Rake::Task object\n") - act.gsub!(/^proc \{\n/, '') - end - act.gsub(/\n\}$/, '') - end.join("\n") - - meth << "\nend" - - if file_task - @private_methods << meth - return - end - - meth -end - -body = Rake::Task.tasks.map(&method(:method_for_task)).compact.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") - -unless @private_methods.empty? - body << "\n\n private\n\n" - body << @private_methods.map { |meth| meth.gsub(/^/, ' ') }.join("\n\n") -end - -requires = $requires.map { |r| "require #{r.inspect}" }.join("\n") - -File.open(output, 'w') { |f| f.write(<<END.lstrip) } -#{requires} - -class Default < Thor -#{body} -end -END diff --git a/railties/lib/rails/vendor/thor-0.11.6/bin/thor b/railties/lib/rails/vendor/thor-0.11.6/bin/thor deleted file mode 100755 index eaf849fb4a..0000000000 --- a/railties/lib/rails/vendor/thor-0.11.6/bin/thor +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby -# -*- mode: ruby -*- - -require File.join(File.dirname(__FILE__), '..', 'lib', 'thor') -require 'thor/runner' - -Thor::Runner.start diff --git a/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc b/railties/lib/rails/vendor/thor-0.12.0/CHANGELOG.rdoc index dba25b7205..adedfeca9d 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/CHANGELOG.rdoc +++ b/railties/lib/rails/vendor/thor-0.12.0/CHANGELOG.rdoc @@ -2,7 +2,12 @@ * Improve spec coverage for Thor::Runner -== 0.11.x, released 2009-07-01 +== 0.12, released 2009-11-06 + +* [#7] Do not force white color on status +* [#8] Yield a block with the filename on directory + +== 0.11, released 2009-07-01 * Added a rake compatibility layer. It allows you to use spec and rdoc tasks on Thor classes. diff --git a/railties/lib/rails/vendor/thor-0.11.6/LICENSE b/railties/lib/rails/vendor/thor-0.12.0/LICENSE index 98722da459..98722da459 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/LICENSE +++ b/railties/lib/rails/vendor/thor-0.12.0/LICENSE diff --git a/railties/lib/rails/vendor/thor-0.11.6/README.rdoc b/railties/lib/rails/vendor/thor-0.12.0/README.rdoc index f1106f02b6..f1106f02b6 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/README.rdoc +++ b/railties/lib/rails/vendor/thor-0.12.0/README.rdoc diff --git a/railties/lib/rails/vendor/thor-0.12.0/Thorfile b/railties/lib/rails/vendor/thor-0.12.0/Thorfile new file mode 100644 index 0000000000..f71a1e57e2 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.12.0/Thorfile @@ -0,0 +1,63 @@ +# enconding: utf-8 + +require File.join(File.dirname(__FILE__), "lib", "thor", "version") +require 'thor/rake_compat' +require 'spec/rake/spectask' +require 'rdoc/task' + +GEM_NAME = 'thor' +EXTRA_RDOC_FILES = ["README.rdoc", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"] + +class Default < Thor + include Thor::RakeCompat + + Spec::Rake::SpecTask.new(:spec) do |t| + t.libs << 'lib' + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/**/*_spec.rb'] + end + + Spec::Rake::SpecTask.new(:rcov) do |t| + t.libs << 'lib' + t.spec_opts = ['--options', "spec/spec.opts"] + t.spec_files = FileList['spec/**/*_spec.rb'] + t.rcov = true + t.rcov_dir = "rcov" + end + + RDoc::Task.new do |rdoc| + rdoc.main = "README.rdoc" + rdoc.rdoc_dir = "rdoc" + rdoc.title = GEM_NAME + rdoc.rdoc_files.include(*EXTRA_RDOC_FILES) + rdoc.rdoc_files.include('lib/**/*.rb') + rdoc.options << '--line-numbers' << '--inline-source' + end + + begin + require 'jeweler' + Jeweler::Tasks.new do |s| + s.name = GEM_NAME + s.version = Thor::VERSION + s.rubyforge_project = "textmate" + s.platform = Gem::Platform::RUBY + s.summary = "A scripting framework that replaces rake, sake and rubigen" + s.email = "ruby-thor@googlegroups.com" + s.homepage = "http://yehudakatz.com" + s.description = "A scripting framework that replaces rake, sake and rubigen" + s.authors = ['Yehuda Katz', 'José Valim'] + s.has_rdoc = true + s.extra_rdoc_files = EXTRA_RDOC_FILES + s.require_path = 'lib' + s.bindir = "bin" + s.executables = %w( thor rake2thor ) + s.files = s.extra_rdoc_files + Dir.glob("{bin,lib}/**/*") + s.files.exclude 'spec/sandbox/**/*' + s.test_files.exclude 'spec/sandbox/**/*' + end + + Jeweler::RubyforgeTasks.new + rescue LoadError + puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com" + end +end diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor.rb index 3b45c4e9b7..68944f140d 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor.rb @@ -1,4 +1,3 @@ -$:.unshift File.expand_path(File.dirname(__FILE__)) require 'thor/base' require 'thor/group' require 'thor/actions' diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions.rb index d561ccb2aa..d561ccb2aa 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/create_file.rb index 8f6badee27..a3d9296823 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/create_file.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/create_file.rb @@ -60,6 +60,7 @@ class Thor FileUtils.mkdir_p(File.dirname(destination)) File.open(destination, 'w'){ |f| f.write render } end + given_destination end protected diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/directory.rb index be5eb822ac..467e63732a 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/directory.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/directory.rb @@ -40,15 +40,16 @@ class Thor # directory "doc" # directory "doc", "docs", :recursive => false # - def directory(source, destination=nil, config={}) - action Directory.new(self, source, destination || source, config) + def directory(source, destination=nil, config={}, &block) + action Directory.new(self, source, destination || source, config, &block) end class Directory < EmptyDirectory #:nodoc: attr_reader :source - def initialize(base, source, destination=nil, config={}) + def initialize(base, source, destination=nil, config={}, &block) @source = File.expand_path(base.find_in_source_paths(source.to_s)) + @block = block super(base, destination, { :recursive => true }.merge(config)) end @@ -70,14 +71,19 @@ class Thor Dir[lookup].each do |file_source| next if File.directory?(file_source) file_destination = File.join(given_destination, file_source.gsub(source, '.')) + file_destination.gsub!('/./', '/') case file_source when /\.empty_directory$/ - base.empty_directory(File.dirname(file_destination), config) + dirname = File.dirname(file_destination).gsub(/\/\.$/, '') + next if dirname == given_destination + base.empty_directory(dirname, config) when /\.tt$/ - base.template(file_source, file_destination[0..-4], config) + destination = base.template(file_source, file_destination[0..-4], config) + @block.call(destination) if @block else - base.copy_file(file_source, file_destination, config) + destination = base.copy_file(file_source, file_destination, config) + @block.call(destination) if @block end end end diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/empty_directory.rb index 03c1fe4af1..484cb820f8 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/empty_directory.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/empty_directory.rb @@ -55,6 +55,7 @@ class Thor def revoke! say_status :remove, :red ::FileUtils.rm_rf(destination) if !pretend? && exists? + given_destination end protected diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/file_manipulation.rb index d77d90d448..d77d90d448 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/file_manipulation.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/file_manipulation.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/inject_into_file.rb index 0636ec6591..0636ec6591 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/actions/inject_into_file.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/actions/inject_into_file.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/base.rb index 700d794123..700d794123 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/base.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/base.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/hash_with_indifferent_access.rb index 78bc5cf4bf..78bc5cf4bf 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/hash_with_indifferent_access.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/hash_with_indifferent_access.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/ordered_hash.rb index 27fea5bb35..27fea5bb35 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/core_ext/ordered_hash.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/core_ext/ordered_hash.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/error.rb index f9b31a35d1..f9b31a35d1 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/error.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/error.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/group.rb index 1e59df2313..0964a9667a 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/group.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/group.rb @@ -74,7 +74,7 @@ class Thor::Group # def invoke(*names, &block) options = names.last.is_a?(Hash) ? names.pop : {} - verbose = options.fetch(:verbose, :white) + verbose = options.fetch(:verbose, true) names.each do |name| invocations[name] = false diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/invocation.rb index 32e6a72454..32e6a72454 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/invocation.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/invocation.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser.rb index 57a3f6e1a5..57a3f6e1a5 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/argument.rb index aa8ace4719..aa8ace4719 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/argument.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/argument.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/arguments.rb index fb5d965e06..fb5d965e06 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/arguments.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/arguments.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/option.rb index 9e40ec73fa..9e40ec73fa 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/option.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/option.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/options.rb index 75092308b5..75092308b5 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/parser/options.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/parser/options.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/rake_compat.rb index 3ab6bb21f5..0d0757fdda 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/rake_compat.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/rake_compat.rb @@ -22,7 +22,8 @@ class Thor def self.included(base) # Hack. Make rakefile point to invoker, so rdoc task is generated properly. - Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1]) + rakefile = File.basename(caller[0].match(/(.*):\d+/)[1]) + Rake.application.instance_variable_set(:@rakefile, rakefile) self.rake_classes << base end end @@ -43,11 +44,9 @@ class Object #:nodoc: description.strip! klass.desc description, task.comment || non_namespaced_name - klass.class_eval <<-METHOD - def #{non_namespaced_name}(#{task.arg_names.join(', ')}) - Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')}) - end - METHOD + klass.send :define_method, non_namespaced_name do |*args| + Rake::Task[task.name.to_sym].invoke(*args) + end end task diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/runner.rb index 43da09b336..9dc70ea069 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/runner.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/runner.rb @@ -215,7 +215,7 @@ class Thor::Runner < Thor #:nodoc: # 5. c:\ <-- no Thorfiles found! # def thorfiles(relevant_to=nil, skip_lookup=false) - # Deal with deprecated thor when :namespaces: is available as constants + # TODO Remove this dealing with deprecated thor when :namespaces: is available as constants save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml) thorfiles = [] diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell.rb index 0d3f4d5951..1dc8f0e5b4 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell.rb @@ -1,11 +1,17 @@ +require 'rbconfig' require 'thor/shell/color' class Thor module Base - # Returns the shell used in all Thor classes. Default to color one. + # Returns the shell used in all Thor classes. If you are in a Unix platform + # it will use a colored log, otherwise it will use a basic one without color. # def self.shell - @shell ||= Thor::Shell::Color + @shell ||= if Config::CONFIG['host_os'] =~ /mswin|mingw/ + Thor::Shell::Basic + else + Thor::Shell::Color + end end # Sets the shell used in all Thor classes. diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/basic.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/basic.rb index ea9665380b..ea9665380b 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/basic.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/basic.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/color.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/color.rb index 24704f7885..24704f7885 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/shell/color.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/shell/color.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/task.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/task.rb index 91c7564d3f..91c7564d3f 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/task.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/task.rb diff --git a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/util.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/util.rb index fd820d7462..ebae0a3193 100644 --- a/railties/lib/rails/vendor/thor-0.11.6/lib/thor/util.rb +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/util.rb @@ -209,7 +209,7 @@ class Thor # Returns the root where thor files are located, dependending on the OS. # def self.thor_root - File.join(user_home, ".thor") + File.join(user_home, ".thor").gsub(/\\/, '/') end # Returns the files in the thor root. On Windows thor_root will be something @@ -220,7 +220,7 @@ class Thor # If we don't #gsub the \ character, Dir.glob will fail. # def self.thor_root_glob - files = Dir["#{thor_root.gsub(/\\/, '/')}/*"] + files = Dir["#{thor_root}/*"] files.map! do |file| File.directory?(file) ? File.join(file, "main.thor") : file diff --git a/railties/lib/rails/vendor/thor-0.12.0/lib/thor/version.rb b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/version.rb new file mode 100644 index 0000000000..885230fac4 --- /dev/null +++ b/railties/lib/rails/vendor/thor-0.12.0/lib/thor/version.rb @@ -0,0 +1,3 @@ +class Thor + VERSION = "0.11.8".freeze +end diff --git a/railties/lib/rails/vendor_gem_source_index.rb b/railties/lib/rails/vendor_gem_source_index.rb deleted file mode 100644 index 5b7721f303..0000000000 --- a/railties/lib/rails/vendor_gem_source_index.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'rubygems' -require 'yaml' - -module Rails - - class VendorGemSourceIndex - # VendorGemSourceIndex acts as a proxy for the Gem source index, allowing - # gems to be loaded from vendor/gems. Rather than the standard gem repository format, - # vendor/gems contains unpacked gems, with YAML specifications in .specification in - # each gem directory. - include Enumerable - - attr_reader :installed_source_index - attr_reader :vendor_source_index - - @@silence_spec_warnings = false - - def self.silence_spec_warnings - @@silence_spec_warnings - end - - def self.silence_spec_warnings=(v) - @@silence_spec_warnings = v - end - - def initialize(installed_index, vendor_dir=Rails::GemDependency.unpacked_path) - @installed_source_index = installed_index - @vendor_dir = vendor_dir - refresh! - end - - def refresh! - # reload the installed gems - @installed_source_index.refresh! - vendor_gems = {} - - # handle vendor Rails gems - they are identified by having loaded_from set to "" - # we add them manually to the list, so that other gems can find them via dependencies - Gem.loaded_specs.each do |n, s| - next unless s.loaded_from.empty? - vendor_gems[s.full_name] = s - end - - # load specifications from vendor/gems - Dir[File.join(Rails::GemDependency.unpacked_path, '*')].each do |d| - dir_name = File.basename(d) - dir_version = version_for_dir(dir_name) - spec = load_specification(d) - if spec - if spec.full_name != dir_name - # mismatched directory name and gem spec - produced by 2.1.0-era unpack code - if dir_version - # fix the spec version - this is not optimal (spec.files may be wrong) - # but it's better than breaking apps. Complain to remind users to get correct specs. - # use ActiveSupport::Deprecation.warn, as the logger is not set yet - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has a mismatched specification file."+ - " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings - spec.version = dir_version - else - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems is not in a versioned directory"+ - "(should be #{spec.full_name}).") unless @@silence_spec_warnings - # continue, assume everything is OK - end - end - else - # no spec - produced by early-2008 unpack code - # emulate old behavior, and complain. - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems has no specification file."+ - " Run 'rake gems:refresh_specs' to fix this.") unless @@silence_spec_warnings - if dir_version - spec = Gem::Specification.new - spec.version = dir_version - spec.require_paths = ['lib'] - ext_path = File.join(d, 'ext') - spec.require_paths << 'ext' if File.exist?(ext_path) - spec.name = /^(.*)-[^-]+$/.match(dir_name)[1] - files = ['lib'] - # set files to everything in lib/ - files += Dir[File.join(d, 'lib', '*')].map { |v| v.gsub(/^#{d}\//, '') } - files += Dir[File.join(d, 'ext', '*')].map { |v| v.gsub(/^#{d}\//, '') } if ext_path - spec.files = files - else - $stderr.puts("config.gem: Unpacked gem #{dir_name} in vendor/gems not in a versioned directory."+ - " Giving up.") unless @@silence_spec_warnings - next - end - end - spec.loaded_from = File.join(d, '.specification') - # finally, swap out full_gem_path - # it would be better to use a Gem::Specification subclass, but the YAML loads an explicit class - class << spec - def full_gem_path - path = File.join installation_path, full_name - return path if File.directory? path - File.join installation_path, original_name - end - end - vendor_gems[File.basename(d)] = spec - end - @vendor_source_index = Gem::SourceIndex.new(vendor_gems) - end - - def version_for_dir(d) - matches = /-([^-]+)$/.match(d) - Gem::Version.new(matches[1]) if matches - end - - def load_specification(gem_dir) - spec_file = File.join(gem_dir, '.specification') - YAML.load_file(spec_file) if File.exist?(spec_file) - end - - def find_name(*args) - @installed_source_index.find_name(*args) + @vendor_source_index.find_name(*args) - end - - def search(*args) - # look for vendor gems, and then installed gems - later elements take priority - @installed_source_index.search(*args) + @vendor_source_index.search(*args) - end - - def each(&block) - @vendor_source_index.each(&block) - @installed_source_index.each(&block) - end - - def add_spec(spec) - @vendor_source_index.add_spec spec - end - - def remove_spec(spec) - @vendor_source_index.remove_spec spec - end - - def size - @vendor_source_index.size + @installed_source_index.size - end - - end -end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index 6c6af0b2bf..47013d7797 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -1,27 +1,27 @@ ORIG_ARGV = ARGV.dup -require 'rubygems' -gem 'rack', '~> 1.0.0' -gem 'rack-test', '~> 0.5.0' +root = File.expand_path('../../..', __FILE__) +begin + require "#{root}/vendor/gems/environment" +rescue LoadError + %w(activesupport activemodel activerecord actionpack actionmailer activeresource railties).each do |lib| + $:.unshift "#{root}/#{lib}/lib" + end +end -$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" -$:.unshift File.dirname(__FILE__) + "/../../activerecord/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionmailer/lib" -$:.unshift File.dirname(__FILE__) + "/../../activeresource/lib" -$:.unshift File.dirname(__FILE__) + "/../lib" -$:.unshift File.dirname(__FILE__) + "/../builtin/rails_info" +$:.unshift "#{root}/railties/builtin/rails_info" require 'stringio' require 'test/unit' +require 'fileutils' require 'active_support' +require 'active_support/core_ext/logger' require 'active_support/test_case' require 'action_controller' +require 'rails' -if defined?(RAILS_ROOT) - RAILS_ROOT.replace File.dirname(__FILE__) -else - RAILS_ROOT = File.dirname(__FILE__) +Rails::Initializer.run do |config| + config.root = File.dirname(__FILE__) end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb new file mode 100644 index 0000000000..a3e1916494 --- /dev/null +++ b/railties/test/application/configuration_test.rb @@ -0,0 +1,48 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class InitializerTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + boot_rails + end + + test "the application root is set correctly" do + require "#{app_path}/config/environment" + assert_equal Pathname.new(app_path), Rails.application.root + end + + test "the application root can be set" do + FileUtils.mkdir_p("#{app_path}/hello") + add_to_config <<-RUBY + config.frameworks = [] + config.root = '#{app_path}/hello' + RUBY + require "#{app_path}/config/environment" + assert_equal Pathname.new("#{app_path}/hello"), Rails.application.root + end + + test "the application root is detected as where config.ru is located" do + add_to_config <<-RUBY + config.frameworks = [] + RUBY + FileUtils.mv "#{app_path}/config.ru", "#{app_path}/config/config.ru" + require "#{app_path}/config/environment" + assert_equal Pathname.new("#{app_path}/config"), Rails.application.root + end + + test "the application root is Dir.pwd if there is no config.ru" do + File.delete("#{app_path}/config.ru") + add_to_config <<-RUBY + config.frameworks = [] + RUBY + + Dir.chdir("#{app_path}/app") do + require "#{app_path}/config/environment" + assert_equal Pathname.new("#{app_path}/app"), Rails.application.root + end + end + end +end
\ No newline at end of file diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 0d6eb4147a..ccbcd84176 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -5,9 +5,10 @@ module ApplicationTests include ActiveSupport::Testing::Isolation def setup - require "rails/generators" build_app boot_rails + require "rails" + require "rails/generators" end test "generators default values" do @@ -22,7 +23,8 @@ module ApplicationTests Rails::Initializer.run do |c| c.generators.orm = :datamapper c.generators.test_framework = :rspec - expected = { :rails => { :orm => :datamapper, :test_framework => :rspec } } + c.generators.helper = false + expected = { :rails => { :orm => :datamapper, :test_framework => :rspec, :helper => false } } assert_equal(expected, c.generators.options) end end @@ -37,10 +39,14 @@ module ApplicationTests test "generators aliases and options on initialization" do Rails::Initializer.run do |c| + c.frameworks = [] c.generators.rails :aliases => { :test_framework => "-w" } c.generators.orm :datamapper c.generators.test_framework :rspec end + # Initialize the application + Rails.initialize! + Rails::Generators.configure! assert_equal :rspec, Rails::Generators.options[:rails][:test_framework] assert_equal "-w", Rails::Generators.aliases[:rails][:test_framework] @@ -48,8 +54,12 @@ module ApplicationTests test "generators no color on initialization" do Rails::Initializer.run do |c| + c.frameworks = [] c.generators.colorize_logging = false end + # Initialize the application + Rails.initialize! + Rails::Generators.configure! assert_equal Thor::Base.shell, Thor::Shell::Basic end @@ -86,4 +96,4 @@ module ApplicationTests assert Rails::Generators.options.size >= 1 end end -end
\ No newline at end of file +end diff --git a/railties/test/application/initializer_test.rb b/railties/test/application/initializer_test.rb index f46bf2b656..719520bf68 100644 --- a/railties/test/application/initializer_test.rb +++ b/railties/test/application/initializer_test.rb @@ -7,36 +7,42 @@ module ApplicationTests def setup build_app boot_rails + require "rails" end test "initializing an application initializes rails" do - class MyApp < Rails::Application ; end + Rails::Initializer.run do |config| + config.root = app_path + end if RUBY_VERSION < '1.9' $KCODE = '' - MyApp.new + Rails.initialize! assert_equal 'UTF8', $KCODE else Encoding.default_external = Encoding::US_ASCII - MyApp.new + Rails.initialize! assert_equal Encoding::UTF_8, Encoding.default_external end end test "initializing an application adds the application paths to the load path" do - class MyApp < Rails::Application ; end + Rails::Initializer.run do |config| + config.root = app_path + end - MyApp.new + Rails.initialize! assert $:.include?("#{app_path}/app/models") end test "adding an unknown framework raises an error" do - class MyApp < Rails::Application + Rails::Initializer.run do |config| + config.root = app_path config.frameworks << :action_foo end assert_raises RuntimeError do - MyApp.new + Rails.initialize! end end @@ -49,48 +55,40 @@ module ApplicationTests ZOO Rails::Initializer.run do |config| + config.root = app_path config.eager_load_paths = "#{app_path}/lib" end + Rails.initialize! + assert Zoo end test "load environment with global" do app_file "config/environments/development.rb", "$initialize_test_set_from_env = 'success'" assert_nil $initialize_test_set_from_env - Rails::Initializer.run { } + Rails::Initializer.run { |config| config.root = app_path } + Rails.initialize! assert_equal "success", $initialize_test_set_from_env end test "action_controller load paths set only if action controller in use" do assert_nothing_raised NameError do Rails::Initializer.run do |config| + config.root = app_path config.frameworks = [] end + Rails.initialize! end end - test "action_pack is added to the load path if action_controller is required" do - Rails::Initializer.run do |config| - config.frameworks = [:action_controller] - end - - assert $:.include?("#{framework_path}/actionpack/lib") - end - - test "action_pack is added to the load path if action_view is required" do - Rails::Initializer.run do |config| - config.frameworks = [:action_view] - end - - assert $:.include?("#{framework_path}/actionpack/lib") - end - test "after_initialize block works correctly" do Rails::Initializer.run do |config| + config.root = app_path config.after_initialize { $test_after_initialize_block1 = "success" } config.after_initialize { $test_after_initialize_block2 = "congratulations" } end + Rails.initialize! assert_equal "success", $test_after_initialize_block1 assert_equal "congratulations", $test_after_initialize_block2 @@ -98,10 +96,12 @@ module ApplicationTests test "after_initialize block works correctly when no block is passed" do Rails::Initializer.run do |config| + config.root = app_path config.after_initialize { $test_after_initialize_block1 = "success" } config.after_initialize # don't pass a block, this is what we're testing! config.after_initialize { $test_after_initialize_block2 = "congratulations" } end + Rails.initialize! assert_equal "success", $test_after_initialize_block1 assert_equal "congratulations", $test_after_initialize_block2 @@ -110,26 +110,32 @@ module ApplicationTests # i18n test "setting another default locale" do Rails::Initializer.run do |config| + config.root = app_path config.i18n.default_locale = :de end + Rails.initialize! + assert_equal :de, I18n.default_locale end test "no config locales dir present should return empty load path" do FileUtils.rm_rf "#{app_path}/config/locales" Rails::Initializer.run do |c| + c.root = app_path assert_equal [], c.i18n.load_path end end test "config locales dir present should be added to load path" do Rails::Initializer.run do |c| + c.root = app_path assert_equal ["#{app_path}/config/locales/en.yml"], c.i18n.load_path end end test "config defaults should be added with config settings" do Rails::Initializer.run do |c| + c.root = app_path c.i18n.load_path << "my/other/locale.yml" end @@ -141,14 +147,17 @@ module ApplicationTests # DB middleware test "database middleware doesn't initialize when session store is not active_record" do Rails::Initializer.run do |config| + config.root = app_path config.action_controller.session_store = :cookie_store end + Rails.initialize! assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore) end test "database middleware doesn't initialize when activerecord is not in frameworks" do Rails::Initializer.run do |c| + c.root = app_path c.frameworks = [] end assert_equal [], Rails.application.config.middleware @@ -156,8 +165,10 @@ module ApplicationTests test "database middleware initializes when session store is active record" do Rails::Initializer.run do |c| + c.root = app_path c.action_controller.session_store = :active_record_store end + Rails.initialize! expects = [ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActiveRecord::SessionStore] middleware = Rails.application.config.middleware.map { |m| m.klass } @@ -166,9 +177,11 @@ module ApplicationTests test "ensure database middleware doesn't use action_controller on initializing" do Rails::Initializer.run do |c| + c.root = app_path c.frameworks -= [:action_controller] c.action_controller.session_store = :active_record_store end + Rails.initialize! assert !Rails.application.config.middleware.include?(ActiveRecord::SessionStore) end @@ -176,18 +189,20 @@ module ApplicationTests # Pathview test test "load view paths doesn't perform anything when action_view not in frameworks" do Rails::Initializer.run do |c| + c.root = app_path c.frameworks -= [:action_view] end + Rails.initialize! + assert_equal nil, ActionMailer::Base.template_root assert_equal [], ActionController::Base.view_paths end - # Rails root test - test "Rails.root == RAILS_ROOT" do - assert_equal RAILS_ROOT, Rails.root.to_s - end - test "Rails.root should be a Pathname" do + Rails::Initializer.run do |c| + c.root = app_path + end + Rails.initialize! assert_instance_of Pathname, Rails.root end end diff --git a/railties/test/application/load_test.rb b/railties/test/application/load_test.rb index 5c3d35fb16..3da51c4355 100644 --- a/railties/test/application/load_test.rb +++ b/railties/test/application/load_test.rb @@ -40,7 +40,7 @@ module ApplicationTests test "Rails.application is available after config.ru has been racked up" do rackup - assert Rails.application.new < Rails::Application + assert Rails.application < Rails::Application end # Passenger still uses AC::Dispatcher, so we need to diff --git a/railties/test/application/notifications_test.rb b/railties/test/application/notifications_test.rb new file mode 100644 index 0000000000..62ed4f4ad4 --- /dev/null +++ b/railties/test/application/notifications_test.rb @@ -0,0 +1,46 @@ +require "isolation/abstract_unit" + +module ApplicationTests + class NotificationsTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + class MyQueue + attr_reader :events, :subscribers + + def initialize + @events = [] + @subscribers = [] + end + + def publish(name, *args) + @events << name + end + + def subscribe(pattern=nil, &block) + @subscribers << pattern + end + end + + def setup + build_app + boot_rails + require "rails" + require "active_support/notifications" + Rails::Initializer.run do |c| + c.notifications.queue = MyQueue.new + c.notifications.subscribe(/listening/) do + puts "Cool" + end + end + end + + test "new queue is set" do + ActiveSupport::Notifications.instrument(:foo) + assert_equal :foo, ActiveSupport::Notifications.queue.events.first + end + + test "configuration subscribers are loaded" do + assert_equal 1, ActiveSupport::Notifications.queue.subscribers.count { |s| s == /listening/ } + end + end +end diff --git a/railties/test/application/plugins_test.rb b/railties/test/application/plugins_test.rb deleted file mode 100644 index 81e7f4d88c..0000000000 --- a/railties/test/application/plugins_test.rb +++ /dev/null @@ -1,101 +0,0 @@ -require "isolation/abstract_unit" - -module ApplicationTests - class PluginTest < Test::Unit::TestCase - include ActiveSupport::Testing::Isolation - - def assert_plugins(list_of_names, array_of_plugins, message=nil) - assert_equal list_of_names.map { |n| n.to_s }, array_of_plugins.map { |p| p.name }, message - end - - def setup - build_app - boot_rails - @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - # Tmp hax to get tests working - FileUtils.cp_r "#{File.dirname(__FILE__)}/../fixtures/plugins", "#{app_path}/vendor" - end - - test "all plugins are loaded when registered plugin list is untouched" do - Rails::Initializer.run { } - assert_plugins [ - :a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby - ], Rails.application.config.loaded_plugins, @failure_tip - end - - test "no plugins are loaded if the configuration has an empty plugin list" do - Rails::Initializer.run { |c| c.plugins = [] } - assert_plugins [], Rails.application.config.loaded_plugins - end - - test "only the specified plugins are located in the order listed" do - plugin_names = [:plugin_with_no_lib_dir, :acts_as_chunky_bacon] - Rails::Initializer.run { |c| c.plugins = plugin_names } - assert_plugins plugin_names, Rails.application.config.loaded_plugins - end - - test "all plugins loaded after all" do - Rails::Initializer.run do |config| - config.plugins = [:stubby, :all, :acts_as_chunky_bacon] - end - assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], Rails.application.config.loaded_plugins, @failure_tip - end - - test "plugin names may be strings" do - plugin_names = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - Rails::Initializer.run do |config| - config.plugins = ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - end - - assert_plugins plugin_names, Rails.application.config.loaded_plugins, @failure_tip - end - - test "all plugins loaded when all is used" do - Rails::Initializer.run do |config| - config.plugins = [:stubby, :acts_as_chunky_bacon, :all] - end - - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], Rails.application.config.loaded_plugins, @failure_tip - end - - test "all loaded plugins are added to the load paths" do - Rails::Initializer.run do |config| - config.plugins = [:stubby, :acts_as_chunky_bacon] - end - - assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/stubby/lib") - assert $LOAD_PATH.include?("#{app_path}/vendor/plugins/default/acts/acts_as_chunky_bacon/lib") - end - - test "registering a plugin name that does not exist raises a load error" do - assert_raise(LoadError) do - Rails::Initializer.run do |config| - config.plugins = [:stubby, :acts_as_a_non_existant_plugin] - end - end - end - - test "load error messages mention missing plugins and no others" do - valid_plugins = [:stubby, :acts_as_chunky_bacon] - invalid_plugins = [:non_existant_plugin1, :non_existant_plugin2] - - begin - Rails::Initializer.run do |config| - config.plugins = [:stubby, :acts_as_chunky_bacon, :non_existant_plugin1, :non_existant_plugin2] - end - flunk "Expected a LoadError but did not get one" - rescue LoadError => e - assert_plugins valid_plugins, Rails.application.config.loaded_plugins, @failure_tip - - invalid_plugins.each do |plugin| - assert_match(/#{plugin.to_s}/, e.message, "LoadError message should mention plugin '#{plugin}'") - end - - valid_plugins.each do |plugin| - assert_no_match(/#{plugin.to_s}/, e.message, "LoadError message should not mention '#{plugin}'") - end - end - end - - end -end
\ No newline at end of file diff --git a/railties/test/backtrace_cleaner_test.rb b/railties/test/backtrace_cleaner_test.rb index c3e4f970fe..6cff591b94 100644 --- a/railties/test/backtrace_cleaner_test.rb +++ b/railties/test/backtrace_cleaner_test.rb @@ -1,6 +1,4 @@ require 'abstract_unit' - -require 'rails/initializer' require 'rails/backtrace_cleaner' if defined? Test::Unit::Util::BacktraceFilter @@ -18,12 +16,12 @@ if defined? Test::Unit::Util::BacktraceFilter test "test with backtrace should use the rails backtrace cleaner to clean" do Rails.stubs(:backtrace_cleaner).returns(stub(:clean)) Rails.backtrace_cleaner.expects(:clean).with(@backtrace, nil) - @test.filter_backtrace(@backtrace) + @test.send(:filter_backtrace, @backtrace) end test "filter backtrace should have the same arity as Test::Unit::Util::BacktraceFilter" do assert_nothing_raised do - @test.filter_backtrace(@backtrace, '/opt/local/lib') + @test.send(:filter_backtrace, @backtrace, '/opt/local/lib') end end end @@ -31,31 +29,26 @@ else $stderr.puts 'No BacktraceFilter for minitest' end -class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase - def setup - @cleaner = Rails::BacktraceCleaner.new - end - - test "should format installed gems correctly" do - @backtrace = [ "#{Gem.default_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace) - assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] - end +if defined? Gem + class BacktraceCleanerVendorGemTest < ActiveSupport::TestCase + def setup + @cleaner = Rails::BacktraceCleaner.new + end - test "should format installed gems not in Gem.default_dir correctly" do - @target_dir = Gem.path.detect { |p| p != Gem.default_dir } - # skip this test if default_dir is the only directory on Gem.path - if @target_dir - @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + test "should format installed gems correctly" do + @backtrace = [ "#{Gem.path[0]}/gems/nosuchgem-1.2.3/lib/foo.rb" ] @result = @cleaner.clean(@backtrace) assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] end - end - test "should format vendor gems correctly" do - @backtrace = [ "#{Rails::GemDependency.unpacked_path}/nosuchgem-1.2.3/lib/foo.rb" ] - @result = @cleaner.clean(@backtrace) - assert_equal "nosuchgem (1.2.3) [v] lib/foo.rb", @result[0] + test "should format installed gems not in Gem.default_dir correctly" do + @target_dir = Gem.path.detect { |p| p != Gem.default_dir } + # skip this test if default_dir is the only directory on Gem.path + if @target_dir + @backtrace = [ "#{@target_dir}/gems/nosuchgem-1.2.3/lib/foo.rb" ] + @result = @cleaner.clean(@backtrace) + assert_equal "nosuchgem (1.2.3) lib/foo.rb", @result[0] + end + end end - end diff --git a/railties/test/boot_test.rb b/railties/test/boot_test.rb deleted file mode 100644 index 1280d27ffe..0000000000 --- a/railties/test/boot_test.rb +++ /dev/null @@ -1,172 +0,0 @@ -require 'abstract_unit' -require 'rails/initializer' -require "#{File.dirname(__FILE__)}/../lib/rails/generators/rails/app/templates/config/boot" -require 'rails/gem_dependency' - -class BootTest < Test::Unit::TestCase - def test_boot_returns_if_booted - Rails.expects(:booted?).returns(true) - Rails.expects(:pick_boot).never - assert_nil Rails.boot! - end - - def test_boot_preinitializes_then_picks_and_runs_if_not_booted - Rails.expects(:booted?).returns(false) - Rails.expects(:preinitialize) - Rails.expects(:pick_boot).returns(mock(:run => 'result')) - assert_equal 'result', Rails.boot! - end - - def test_preinitialize_does_not_raise_exception_if_preinitializer_file_does_not_exist - Rails.stubs(:preinitializer_path).returns('/there/is/no/such/file') - - assert_nothing_raised { Rails.preinitialize } - end - - def test_load_preinitializer_loads_preinitializer_file - Rails.stubs(:preinitializer_path).returns("#{File.dirname(__FILE__)}/fixtures/environment_with_constant.rb") - - assert_nil $initialize_test_set_from_env - Rails.preinitialize - assert_equal "success", $initialize_test_set_from_env - ensure - $initialize_test_set_from_env = nil - end - - def test_boot_vendor_rails_by_default - Rails.expects(:booted?).returns(false) - Rails.expects(:preinitialize) - File.expects(:exist?).with("#{RAILS_ROOT}/vendor/rails").returns(true) - Rails::VendorBoot.any_instance.expects(:run).returns('result') - assert_equal 'result', Rails.boot! - end - - def test_boot_gem_rails_otherwise - Rails.expects(:booted?).returns(false) - Rails.expects(:preinitialize) - File.expects(:exist?).with("#{RAILS_ROOT}/vendor/rails").returns(false) - Rails::GemBoot.any_instance.expects(:run).returns('result') - assert_equal 'result', Rails.boot! - end -end - -class VendorBootTest < Test::Unit::TestCase - include Rails - - def test_load_initializer_requires_from_vendor_rails - boot = VendorBoot.new - boot.expects(:require).with("rails") - boot.expects(:install_gem_spec_stubs) - Rails::GemDependency.expects(:add_frozen_gem_path) - boot.load_initializer - end -end - -class GemBootTest < Test::Unit::TestCase - include Rails - - def test_load_initializer_loads_rubygems_and_the_rails_gem - boot = GemBoot.new - GemBoot.expects(:load_rubygems) - boot.expects(:load_rails_gem) - boot.expects(:require).with('rails') - boot.load_initializer - end - - def test_load_rubygems_exits_with_error_if_missing - GemBoot.expects(:require).with('rubygems').raises(LoadError, 'missing rubygems') - STDERR.expects(:puts) - GemBoot.expects(:exit).with(1) - GemBoot.load_rubygems - end - - def test_load_rubygems_exits_with_error_if_too_old - GemBoot.stubs(:rubygems_version).returns('0.0.1') - GemBoot.expects(:require).with('rubygems').returns(true) - STDERR.expects(:puts) - GemBoot.expects(:exit).with(1) - GemBoot.load_rubygems - end - - def test_load_rails_gem_activates_specific_gem_if_version_given - GemBoot.stubs(:gem_version).returns('0.0.1') - - boot = GemBoot.new - boot.expects(:gem).with('rails', '0.0.1') - boot.load_rails_gem - end - - def test_load_rails_gem_activates_latest_gem_if_no_version_given - GemBoot.stubs(:gem_version).returns(nil) - - boot = GemBoot.new - boot.expects(:gem).with('rails') - boot.load_rails_gem - end - - def test_load_rails_gem_exits_with_error_if_missing - GemBoot.stubs(:gem_version).returns('0.0.1') - - boot = GemBoot.new - boot.expects(:gem).with('rails', '0.0.1').raises(Gem::LoadError, 'missing rails 0.0.1 gem') - STDERR.expects(:puts) - boot.expects(:exit).with(1) - boot.load_rails_gem - end -end - -class ParseGemVersionTest < Test::Unit::TestCase - def test_should_return_nil_if_no_lines_are_passed - assert_equal nil, parse('') - assert_equal nil, parse(nil) - end - - def test_should_accept_either_single_or_double_quotes - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse('RAILS_GEM_VERSION = "1.2.3"') - end - - def test_should_return_nil_if_no_lines_match - assert_equal nil, parse('nothing matches on this line\nor on this line') - end - - def test_should_parse_with_no_leading_space - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION") - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'") - end - - def test_should_parse_with_any_number_of_leading_spaces - assert_equal nil, parse([]) - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION") - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3' unless defined? RAILS_GEM_VERSION") - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse(" RAILS_GEM_VERSION = '1.2.3'") - end - - def test_should_ignore_unrelated_comments - assert_equal "1.2.3", parse("# comment\nRAILS_GEM_VERSION = '1.2.3'\n# comment") - end - - def test_should_ignore_commented_version_lines - assert_equal "1.2.3", parse("#RAILS_GEM_VERSION = '9.8.7'\nRAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse("# RAILS_GEM_VERSION = '9.8.7'\nRAILS_GEM_VERSION = '1.2.3'") - assert_equal "1.2.3", parse("RAILS_GEM_VERSION = '1.2.3'\n# RAILS_GEM_VERSION = '9.8.7'") - end - - def test_should_allow_advanced_rubygems_version_specifications - # See http://rubygems.org/read/chapter/16 - assert_equal "=1.2.3", parse("RAILS_GEM_VERSION = '=1.2.3'") # equal sign - assert_equal "= 1.2.3", parse("RAILS_GEM_VERSION = '= 1.2.3'") # with space - assert_equal "!=1.2.3", parse("RAILS_GEM_VERSION = '!=1.2.3'") # not equal - assert_equal ">1.2.3", parse("RAILS_GEM_VERSION = '>1.2.3'") # greater than - assert_equal "<1.2.3", parse("RAILS_GEM_VERSION = '<1.2.3'") # less than - assert_equal ">=1.2.3", parse("RAILS_GEM_VERSION = '>=1.2.3'") # greater than or equal - assert_equal "<=1.2.3", parse("RAILS_GEM_VERSION = '<=1.2.3'") # less than or equal - assert_equal "~>1.2.3.0", parse("RAILS_GEM_VERSION = '~>1.2.3.0'") # approximately greater than - end - - private - def parse(text) - Rails::GemBoot.parse_gem_version(text) - end -end diff --git a/railties/test/fixtures/lib/generators/foobar/foobar_generator.rb b/railties/test/fixtures/lib/generators/foobar/foobar_generator.rb new file mode 100644 index 0000000000..d1de8c56fa --- /dev/null +++ b/railties/test/fixtures/lib/generators/foobar/foobar_generator.rb @@ -0,0 +1,4 @@ +module Foobar + class FoobarGenerator < Rails::Generators::Base + end +end diff --git a/railties/test/gem_dependency_test.rb b/railties/test/gem_dependency_test.rb deleted file mode 100644 index 92132be992..0000000000 --- a/railties/test/gem_dependency_test.rb +++ /dev/null @@ -1,220 +0,0 @@ -require 'plugin_test_helper' -require 'rails/gem_dependency' - -class Rails::GemDependency - public :install_command, :unpack_command -end - -Rails::VendorGemSourceIndex.silence_spec_warnings = true - -class GemDependencyTest < Test::Unit::TestCase - def setup - @gem = Rails::GemDependency.new "xhpricotx" - @gem_with_source = Rails::GemDependency.new "xhpricotx", :source => "http://code.whytheluckystiff.net" - @gem_with_version = Rails::GemDependency.new "xhpricotx", :version => "= 0.6" - @gem_with_lib = Rails::GemDependency.new "xaws-s3x", :lib => "aws/s3" - @gem_without_load = Rails::GemDependency.new "xhpricotx", :lib => false - end - - def test_configuration_adds_gem_dependency - config = Rails::Configuration.new - config.gem "xaws-s3x", :lib => "aws/s3", :version => "0.4.0" - assert_equal [["install", "xaws-s3x", "--version", '"= 0.4.0"']], config.gems.collect { |g| g.install_command } - end - - def test_gem_creates_install_command - assert_equal %w(install xhpricotx), @gem.install_command - end - - def test_gem_with_source_creates_install_command - assert_equal %w(install xhpricotx --source http://code.whytheluckystiff.net), @gem_with_source.install_command - end - - def test_gem_with_version_creates_install_command - assert_equal ["install", "xhpricotx", "--version", '"= 0.6"'], @gem_with_version.install_command - end - - def test_gem_creates_unpack_command - assert_equal %w(unpack xhpricotx), @gem.unpack_command - end - - def test_gem_with_version_unpack_install_command - # stub out specification method, or else test will fail if hpricot 0.6 isn't installed - mock_spec = mock() - mock_spec.stubs(:version).returns('0.6') - @gem_with_version.stubs(:specification).returns(mock_spec) - assert_equal ["unpack", "xhpricotx", "--version", '= 0.6'], @gem_with_version.unpack_command - end - - def test_gem_adds_load_paths - @gem.expects(:gem).with(@gem) - @gem.add_load_paths - end - - def test_gem_with_version_adds_load_paths - @gem_with_version.expects(:gem).with(@gem_with_version) - @gem_with_version.add_load_paths - assert @gem_with_version.load_paths_added? - end - - def test_gem_loading - @gem.expects(:gem).with(@gem) - @gem.expects(:require).with(@gem.name) - @gem.add_load_paths - @gem.load - assert @gem.loaded? - end - - def test_gem_with_lib_loading - @gem_with_lib.expects(:gem).with(@gem_with_lib) - @gem_with_lib.expects(:require).with(@gem_with_lib.lib) - @gem_with_lib.add_load_paths - @gem_with_lib.load - assert @gem_with_lib.loaded? - end - - def test_gem_without_lib_loading - @gem_without_load.expects(:gem).with(@gem_without_load) - @gem_without_load.expects(:require).with(@gem_without_load.lib).never - @gem_without_load.add_load_paths - @gem_without_load.load - end - - def test_gem_dependencies_compare_for_uniq - gem1 = Rails::GemDependency.new "gem1" - gem1a = Rails::GemDependency.new "gem1" - gem2 = Rails::GemDependency.new "gem2" - gem2a = Rails::GemDependency.new "gem2" - gem3 = Rails::GemDependency.new "gem2", :version => ">=0.1" - gem3a = Rails::GemDependency.new "gem2", :version => ">=0.1" - gem4 = Rails::GemDependency.new "gem3" - gems = [gem1, gem2, gem1a, gem3, gem2a, gem4, gem3a, gem2, gem4] - assert_equal 4, gems.uniq.size - end - - def test_gem_load_frozen - dummy_gem = Rails::GemDependency.new "dummy-gem-a" - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_A_VERSION - end - - def test_gem_load_frozen_specific_version - dummy_gem = Rails::GemDependency.new "dummy-gem-b", :version => '0.4.0' - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_B_VERSION - assert_equal '0.4.0', DUMMY_GEM_B_VERSION - end - - def test_gem_load_frozen_minimum_version - dummy_gem = Rails::GemDependency.new "dummy-gem-c", :version => '>=0.5.0' - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_C_VERSION - assert_equal '0.6.0', DUMMY_GEM_C_VERSION - end - - def test_gem_load_missing_specification - dummy_gem = Rails::GemDependency.new "dummy-gem-d" - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_D_VERSION - assert_equal '1.0.0', DUMMY_GEM_D_VERSION - assert_equal ['lib', 'lib/dummy-gem-d.rb'], dummy_gem.specification.files - end - - def test_gem_load_bad_specification - dummy_gem = Rails::GemDependency.new "dummy-gem-e", :version => "= 1.0.0" - dummy_gem.add_load_paths - dummy_gem.load - assert_not_nil DUMMY_GEM_E_VERSION - assert_equal '1.0.0', DUMMY_GEM_E_VERSION - end - - def test_gem_handle_missing_dependencies - dummy_gem = Rails::GemDependency.new "dummy-gem-g" - dummy_gem.add_load_paths - dummy_gem.load - assert_equal 1, dummy_gem.dependencies.size - assert_equal 1, dummy_gem.dependencies.first.dependencies.size - assert_nothing_raised do - dummy_gem.dependencies.each do |g| - g.dependencies - end - end - end - - def test_gem_ignores_development_dependencies - dummy_gem = Rails::GemDependency.new "dummy-gem-k" - dummy_gem.add_load_paths - dummy_gem.load - assert_equal 1, dummy_gem.dependencies.size - end - - def test_gem_guards_against_duplicate_unpacks - dummy_gem = Rails::GemDependency.new "dummy-gem-a" - dummy_gem.stubs(:frozen?).returns(true) - dummy_gem.expects(:unpack_base).never - dummy_gem.unpack - end - - def test_gem_does_not_unpack_framework_gems - dummy_gem = Rails::GemDependency.new "dummy-gem-a" - dummy_gem.stubs(:framework_gem?).returns(true) - dummy_gem.expects(:unpack_base).never - dummy_gem.unpack - end - - def test_gem_from_directory_name_attempts_to_load_specification - assert_raises RuntimeError do - dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem-1.1') - end - end - - def test_gem_from_directory_name - dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem-1.1', false) - assert_equal 'dummy-gem', dummy_gem.name - assert_equal '= 1.1', dummy_gem.version_requirements.to_s - end - - def test_gem_from_directory_name_loads_specification_successfully - assert_nothing_raised do - dummy_gem = Rails::GemDependency.from_directory_name(File.join(Rails::GemDependency.unpacked_path, 'dummy-gem-g-1.0.0')) - assert_not_nil dummy_gem.specification - end - end - - def test_gem_from_invalid_directory_name - assert_raises RuntimeError do - dummy_gem = Rails::GemDependency.from_directory_name('dummy-gem') - end - assert_raises RuntimeError do - dummy_gem = Rails::GemDependency.from_directory_name('dummy') - end - end - - def test_gem_determines_build_status - assert_equal true, Rails::GemDependency.new("dummy-gem-a").built? - assert_equal true, Rails::GemDependency.new("dummy-gem-i").built? - assert_equal false, Rails::GemDependency.new("dummy-gem-j").built? - end - - def test_gem_determines_build_status_only_on_vendor_gems - framework_gem = Rails::GemDependency.new('dummy-framework-gem') - framework_gem.stubs(:framework_gem?).returns(true) # already loaded - framework_gem.stubs(:vendor_rails?).returns(false) # but not in vendor/rails - framework_gem.stubs(:vendor_gem?).returns(false) # and not in vendor/gems - framework_gem.add_load_paths # freeze framework gem early - assert framework_gem.built? - end - - def test_gem_build_passes_options_to_dependencies - start_gem = Rails::GemDependency.new("dummy-gem-g") - dep_gem = Rails::GemDependency.new("dummy-gem-f") - start_gem.stubs(:dependencies).returns([dep_gem]) - dep_gem.expects(:build).with({ :force => true }).once - start_gem.build(:force => true) - end - -end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index f226e184d1..7d03a37f2a 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -54,44 +54,44 @@ class ActionsTest < GeneratorsTestCase action :plugin, 'rest_auth', {} end - def test_gem_should_put_gem_dependency_in_enviroment + def test_add_source_adds_source_to_gemfile run_generator - action :gem, 'will-paginate' - assert_file 'config/environment.rb', /config\.gem 'will\-paginate'/ + action :add_source, 'http://gems.github.com' + assert_file 'Gemfile', /source "http:\/\/gems\.github\.com"/ end - def test_gem_with_options_should_include_options_in_gem_dependency_in_environment + def test_gem_should_put_gem_dependency_in_gemfile run_generator - action :gem, 'mislav-will-paginate', :lib => 'will-paginate', :source => 'http://gems.github.com' - - regexp = /#{Regexp.escape("config.gem 'mislav-will-paginate', :lib => 'will-paginate', :source => 'http://gems.github.com'")}/ - assert_file 'config/environment.rb', regexp + action :gem, 'will-paginate' + assert_file 'Gemfile', /gem "will\-paginate"/ end - def test_gem_with_env_string_should_put_gem_dependency_in_specified_environment + def test_gem_with_options_should_include_all_options_in_gemfile run_generator - action :gem, 'rspec', :env => 'test' - assert_file 'config/environments/test.rb', /config\.gem 'rspec'/ - end - def test_gem_with_env_array_should_put_gem_dependency_in_specified_environments - run_generator - action :gem, 'quietbacktrace', :env => %w[ development test ] - assert_file 'config/environments/development.rb', /config\.gem 'quietbacktrace'/ - assert_file 'config/environments/test.rb', /config\.gem 'quietbacktrace'/ + assert_deprecated do + action :gem, 'mislav-will-paginate', :lib => 'will-paginate', :source => 'http://gems.github.com' + end + + assert_file 'Gemfile', /gem "mislav\-will\-paginate", :require_as => "will\-paginate"/ + assert_file 'Gemfile', /source "http:\/\/gems\.github\.com"/ end - def test_gem_with_lib_option_set_to_false_should_put_gem_dependency_in_enviroment_correctly + def test_gem_with_env_should_include_all_dependencies_in_gemfile run_generator - action :gem, 'mislav-will-paginate', :lib => false - assert_file 'config/environment.rb', /config\.gem 'mislav\-will\-paginate'\, :lib => false/ + + assert_deprecated do + action :gem, 'rspec', :env => %w(development test) + end + + assert_file 'Gemfile', /gem "rspec", :only => \["development", "test"\]/ end def test_environment_should_include_data_in_environment_initializer_block run_generator - load_paths = 'config.load_paths += %w["#{RAILS_ROOT}/app/extras"]' + load_paths = 'config.load_paths += %w["#{Rails.root}/app/extras"]' action :environment, load_paths - assert_file 'config/environment.rb', /#{Regexp.escape(load_paths)}/ + assert_file 'config/application.rb', /#{Regexp.escape(load_paths)}/ end def test_environment_with_block_should_include_block_contents_in_environment_initializer_block @@ -102,7 +102,7 @@ class ActionsTest < GeneratorsTestCase '# This will be added' end - assert_file 'config/environment.rb' do |content| + assert_file 'config/application.rb' do |content| assert_match /# This will be added/, content assert_no_match /# This wont be added/, content end @@ -163,9 +163,10 @@ class ActionsTest < GeneratorsTestCase action :capify! end - def test_freeze_should_freeze_rails_edge - generator.expects(:run).once.with('rake rails:freeze:edge', :verbose => false) - action :freeze! + def test_freeze_is_deprecated + assert_deprecated do + action :freeze! + end end def test_route_should_add_data_to_the_routes_block_in_config_routes diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 6e46c4ddc0..10d0bc6bc2 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -65,7 +65,7 @@ class AppGeneratorTest < GeneratorsTestCase def test_activerecord_is_removed_from_frameworks_if_skip_activerecord_is_given run_generator ["--skip-activerecord"] - assert_file "config/environment.rb", /config\.frameworks \-= \[ :active_record \]/ + assert_file "config/application.rb", /config\.frameworks \-= \[ :active_record \]/ end def test_prototype_and_test_unit_are_added_by_default @@ -110,15 +110,8 @@ class AppGeneratorTest < GeneratorsTestCase ).each { |path| assert_file "script/#{path}", /#!\/usr\/bin\/env/ } end - def test_rails_is_frozen - generator(:freeze => true, :database => "sqlite3").expects(:run). - with("rake rails:freeze:edge", :verbose => false) - silence(:stdout){ generator.invoke } - assert_file 'config/environment.rb', /# RAILS_GEM_VERSION/ - end - def test_template_from_dir_pwd - FileUtils.cd(RAILS_ROOT) + FileUtils.cd(Rails.root) assert_match /It works from file!/, run_generator(["-m", "lib/template.rb"]) end diff --git a/railties/test/generators/generators_test_helper.rb b/railties/test/generators/generators_test_helper.rb index d917812383..4ce48a453b 100644 --- a/railties/test/generators/generators_test_helper.rb +++ b/railties/test/generators/generators_test_helper.rb @@ -1,31 +1,27 @@ -require 'test/unit' -require 'fileutils' - -fixtures = File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures')) -if defined?(RAILS_ROOT) - RAILS_ROOT.replace fixtures -else - RAILS_ROOT = fixtures +# TODO: Fix this RAILS_ENV stuff +RAILS_ENV = 'test' unless defined?(RAILS_ENV) +require 'abstract_unit' + +module Rails + def self.root + @root ||= File.expand_path(File.join(File.dirname(__FILE__), '..', 'fixtures')) + end end +Rails.application.config.root = Rails.root -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../lib" require 'rails/generators' - require 'rubygems' -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../activerecord/lib" -$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/../../../actionpack/lib" require 'active_record' require 'action_dispatch' CURRENT_PATH = File.expand_path(Dir.pwd) Rails::Generators.no_color! -class GeneratorsTestCase < Test::Unit::TestCase +class GeneratorsTestCase < ActiveSupport::TestCase include FileUtils def destination_root - @destination_root ||= File.expand_path(File.join(File.dirname(__FILE__), - '..', 'fixtures', 'tmp')) + File.join(Rails.root, "tmp") end def setup diff --git a/railties/test/generators/resource_generator_test.rb b/railties/test/generators/resource_generator_test.rb index dcae81c204..886af01b22 100644 --- a/railties/test/generators/resource_generator_test.rb +++ b/railties/test/generators/resource_generator_test.rb @@ -75,7 +75,7 @@ class ResourceGeneratorTest < GeneratorsTestCase end def test_plural_names_are_singularized - content = run_generator ["accounts"] + content = run_generator ["accounts".freeze] assert_file "app/models/account.rb", /class Account < ActiveRecord::Base/ assert_file "test/unit/account_test.rb", /class AccountTest/ assert_match /Plural version of the model detected, using singularized version. Override with --force-plural./, content diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index f555725eb8..02155c295c 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -2,6 +2,11 @@ require 'abstract_unit' require 'generators/generators_test_helper' require 'rails/generators/rails/scaffold_controller/scaffold_controller_generator' +module Unknown + module Generators + end +end + class ScaffoldControllerGeneratorTest < GeneratorsTestCase def test_controller_skeleton_is_created @@ -97,10 +102,38 @@ class ScaffoldControllerGeneratorTest < GeneratorsTestCase assert_no_file "app/views/layouts/users.html.erb" end - def test_error_is_shown_if_orm_does_not_provide_interface - error = capture(:stderr){ run_generator ["User", "--orm=unknown"] } - assert_equal "Could not load Unknown::Generators::ActiveModel, skipping controller. " << - "Error: no such file to load -- rails/generators/unknown.\n", error + def test_default_orm_is_used + run_generator ["User", "--orm=unknown"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match /class UsersController < ApplicationController/, content + + assert_instance_method content, :index do |m| + assert_match /@users = User\.all/, m + end + end + end + + def test_customized_orm_is_used + klass = Class.new(Rails::Generators::ActiveModel) do + def self.all(klass) + "#{klass}.find(:all)" + end + end + + Unknown::Generators.const_set(:ActiveModel, klass) + run_generator ["User", "--orm=unknown"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match /class UsersController < ApplicationController/, content + + assert_instance_method content, :index do |m| + assert_match /@users = User\.find\(:all\)/, m + assert_no_match /@users = User\.all/, m + end + end + ensure + Unknown::Generators.send :remove_const, :ActiveModel end protected diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 7e6b7b183c..a8716d9992 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -1,4 +1,4 @@ -require File.join(File.dirname(__FILE__), 'generators', 'generators_test_helper') +require 'generators/generators_test_helper' require 'rails/generators/rails/model/model_generator' require 'rails/generators/test_unit/model/model_generator' require 'mocha' @@ -45,6 +45,12 @@ class GeneratorsTest < GeneratorsTestCase assert_equal "test_unit:generators:model", klass.namespace end + def test_find_by_namespace_with_duplicated_name + klass = Rails::Generators.find_by_namespace(:foobar) + assert klass + assert_equal "foobar:foobar", klass.namespace + end + def test_find_by_namespace_add_generators_to_raw_lookups klass = Rails::Generators.find_by_namespace("test_unit:model") assert klass @@ -80,7 +86,7 @@ class GeneratorsTest < GeneratorsTestCase Rails::Generators.instance_variable_set(:@load_paths, nil) spec = Gem::Specification.new - spec.expects(:full_gem_path).returns(File.join(RAILS_ROOT, 'vendor', 'another_gem_path', 'xspec')) + spec.expects(:full_gem_path).returns(File.join(Rails.root, 'vendor', 'another_gem_path', 'xspec')) Gem.expects(:respond_to?).with(:loaded_specs).returns(true) Gem.expects(:loaded_specs).returns(:spec => spec) @@ -101,13 +107,15 @@ class GeneratorsTest < GeneratorsTestCase def test_rails_generators_with_others_information output = capture(:stdout){ Rails::Generators.help }.split("\n").last - assert_equal "Others: active_record:fixjour, fixjour, mspec, rails:javascripts, wrong.", output + assert_equal "Others: active_record:fixjour, fixjour, foobar, mspec, rails:javascripts.", output end def test_warning_is_shown_if_generator_cant_be_loaded + Rails::Generators.load_paths << File.join(Rails.root, "vendor", "gems", "gems", "wrong") output = capture(:stderr){ Rails::Generators.find_by_namespace(:wrong) } + assert_match /\[WARNING\] Could not load generator at/, output - assert_match /Error: uninitialized constant Rails::Generator/, output + assert_match /Rails 2\.x generator/, output end def test_no_color_sets_proper_shell @@ -118,7 +126,7 @@ class GeneratorsTest < GeneratorsTestCase end def test_rails_root_templates - template = File.join(RAILS_ROOT, "lib", "templates", "active_record", "model", "model.rb") + template = File.join(Rails.root, "lib", "templates", "active_record", "model", "model.rb") # Create template mkdir_p(File.dirname(template)) @@ -158,10 +166,7 @@ class GeneratorsTest < GeneratorsTestCase Rails::Generators.options[:new_generator] = { :generate => false } klass = Class.new(Rails::Generators::Base) do - def self.name - "NewGenerator" - end - + def self.name() 'NewGenerator' end class_option :generate, :default => true end @@ -170,6 +175,6 @@ class GeneratorsTest < GeneratorsTestCase def test_source_paths_for_not_namespaced_generators mspec = Rails::Generators.find_by_namespace :mspec - assert mspec.source_paths.include?(File.join(RAILS_ROOT, "lib", "templates", "mspec")) + assert mspec.source_paths.include?(File.join(Rails.root, "lib", "templates", "mspec")) end end diff --git a/railties/test/initializable_test.rb b/railties/test/initializable_test.rb index 7c8aed00c9..a9e60680ac 100644 --- a/railties/test/initializable_test.rb +++ b/railties/test/initializable_test.rb @@ -4,65 +4,196 @@ require 'rails/initializable' module InitializableTests class Foo - extend Rails::Initializable + include Rails::Initializable class << self attr_accessor :foo, :bar end - initializer :omg do + initializer :omg, :global => true do @foo ||= 0 @foo += 1 end end class Bar < Foo - initializer :bar do + initializer :bar, :global => true do @bar ||= 0 @bar += 1 end end module Word - extend Rails::Initializable + include Rails::Initializable - initializer :word do + initializer :word, :global => true do $word = "bird" end end + class Parent + include Rails::Initializable + + initializer :one, :global => true do + $arr << 1 + end + + initializer :two, :global => true do + $arr << 2 + end + end + + class Child < Parent + include Rails::Initializable + + initializer :three, :before => :one, :global => true do + $arr << 3 + end + + initializer :four, :after => :one, :global => true do + $arr << 4 + end + end + + class Parent + initializer :five, :before => :one, :global => true do + $arr << 5 + end + end + + class Instance + include Rails::Initializable + + initializer :one do + $arr << 1 + end + + initializer :two do + $arr << 2 + end + + initializer :three, :global => true do + $arr << 3 + end + + initializer :four, :global => true do + $arr << 4 + end + end + + class WithArgs + include Rails::Initializable + + initializer :foo do |arg| + $with_arg = arg + end + end + + class OverriddenInitializer + class MoreInitializers + include Rails::Initializable + + initializer :startup, :before => :last do + $arr << 3 + end + + initializer :terminate, :after => :first do + $arr << two + end + + def two + 2 + end + end + + include Rails::Initializable + + initializer :first do + $arr << 1 + end + + initializer :last do + $arr << 4 + end + + def self.initializers + super + MoreInitializers.new.initializers + end + end + class Basic < ActiveSupport::TestCase include ActiveSupport::Testing::Isolation test "initializers run" do - Foo.initializers.run + Foo.run_initializers assert_equal 1, Foo.foo end test "initializers are inherited" do - Bar.initializers.run + Bar.run_initializers assert_equal [1, 1], [Bar.foo, Bar.bar] end test "initializers only get run once" do - Foo.initializers.run - Foo.initializers.run + Foo.run_initializers + Foo.run_initializers assert_equal 1, Foo.foo end test "running initializers on children does not effect the parent" do - Bar.initializers.run + Bar.run_initializers assert_nil Foo.foo assert_nil Foo.bar end - test "inherited initializers are the same objects" do - assert Foo.initializers[:foo].eql?(Bar.initializers[:foo]) - end - test "initializing with modules" do - Word.initializers.run + Word.run_initializers assert_equal "bird", $word end end + + class BeforeAfter < ActiveSupport::TestCase + test "running on parent" do + $arr = [] + Parent.run_initializers + assert_equal [5, 1, 2], $arr + end + + test "running on child" do + $arr = [] + Child.run_initializers + assert_equal [5, 3, 1, 4, 2], $arr + end + end + + class InstanceTest < ActiveSupport::TestCase + test "running locals" do + $arr = [] + instance = Instance.new + instance.run_initializers + assert_equal [1, 2], $arr + end + + test "running globals" do + $arr = [] + Instance.run_initializers + assert_equal [3, 4], $arr + end + end + + class WithArgsTest < ActiveSupport::TestCase + test "running initializers with args" do + $with_arg = nil + WithArgs.new.run_initializers('foo') + assert_equal 'foo', $with_arg + end + end + + class OverriddenInitializerTest < ActiveSupport::TestCase + test "merges in the initializers from the parent in the right order" do + $arr = [] + OverriddenInitializer.new.run_initializers + assert_equal [1, 2, 3, 4], $arr + end + end end
\ No newline at end of file diff --git a/railties/test/initializer/check_ruby_version_test.rb b/railties/test/initializer/check_ruby_version_test.rb index 1852fea4df..97d884e1be 100644 --- a/railties/test/initializer/check_ruby_version_test.rb +++ b/railties/test/initializer/check_ruby_version_test.rb @@ -7,30 +7,39 @@ module InitializerTests def setup build_app boot_rails + require "rails" end test "rails does not initialize with ruby version 1.8.1" do assert_rails_does_not_boot "1.8.1" end - test "rails initializes with ruby version 1.8.2" do - assert_rails_boots "1.8.2" + test "rails does not initialize with ruby version 1.8.2" do + assert_rails_does_not_boot "1.8.2" end test "rails does not initialize with ruby version 1.8.3" do assert_rails_does_not_boot "1.8.3" end - test "rails initializes with ruby version 1.8.4" do - assert_rails_boots "1.8.4" + test "rails does not initialize with ruby version 1.8.4" do + assert_rails_does_not_boot "1.8.4" end - test "rails initializes with ruby version 1.8.5" do - assert_rails_boots "1.8.5" + test "rails does not initializes with ruby version 1.8.5" do + assert_rails_does_not_boot "1.8.5" end - test "rails initializes with ruby version 1.8.6" do - assert_rails_boots "1.8.6" + test "rails does not initialize with ruby version 1.8.6" do + assert_rails_does_not_boot "1.8.6" + end + + test "rails initializes with ruby version 1.8.7" do + assert_rails_boots "1.8.7" + end + + test "rails initializes with the current version of Ruby" do + assert_rails_boots end def set_ruby_version(version) @@ -38,10 +47,11 @@ module InitializerTests Object.const_set(:RUBY_VERSION, version.freeze) end - def assert_rails_boots(version) - set_ruby_version(version) + def assert_rails_boots(version = nil) + set_ruby_version(version) if version assert_nothing_raised "It appears that rails does not boot" do Rails::Initializer.run { |c| c.frameworks = [] } + Rails.initialize! end end @@ -50,6 +60,7 @@ module InitializerTests $stderr = File.open("/dev/null", "w") assert_raises(SystemExit) do Rails::Initializer.run { |c| c.frameworks = [] } + Rails.initialize! end end end diff --git a/railties/test/initializer/initialize_i18n_test.rb b/railties/test/initializer/initialize_i18n_test.rb index e909688817..076816d73b 100644 --- a/railties/test/initializer/initialize_i18n_test.rb +++ b/railties/test/initializer/initialize_i18n_test.rb @@ -7,13 +7,16 @@ module InitializerTests def setup build_app boot_rails + require "rails" end # test_config_defaults_and_settings_should_be_added_to_i18n_defaults test "i18n config defaults and settings should be added to i18n defaults" do Rails::Initializer.run do |c| + c.root = app_path c.i18n.load_path << "my/other/locale.yml" end + Rails.initialize! #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml assert_equal %W( @@ -23,29 +26,31 @@ module InitializerTests #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml #{RAILS_FRAMEWORK_ROOT}/railties/tmp/app/config/locales/en.yml my/other/locale.yml - ), I18n.load_path + ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) } end test "i18n finds locale files in engines" do - app_file "vendor/plugins/engine/init.rb", "" - app_file "vendor/plugins/engine/app/models/hellos.rb", "class Hello ; end" - app_file "vendor/plugins/engine/lib/omg.rb", "puts 'omg'" - app_file "vendor/plugins/engine/config/locales/en.yml", "hello:" - - Rails::Initializer.run do |c| - c.i18n.load_path << "my/other/locale.yml" - end - - #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml - assert_equal %W( - #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml - #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml - #{app_path}/config/locales/en.yml - my/other/locale.yml - #{app_path}/vendor/plugins/engine/config/locales/en.yml - ), I18n.load_path + # app_file "vendor/plugins/engine/init.rb", "" + # app_file "vendor/plugins/engine/app/models/hellos.rb", "class Hello ; end" + # app_file "vendor/plugins/engine/lib/omg.rb", "puts 'omg'" + # app_file "vendor/plugins/engine/config/locales/en.yml", "hello:" + # + # Rails::Initializer.run do |c| + # c.root = app_path + # c.i18n.load_path << "my/other/locale.yml" + # end + # Rails.initialize! + # + # #{RAILS_FRAMEWORK_ROOT}/railties/test/fixtures/plugins/engines/engine/config/locales/en.yml + # assert_equal %W( + # #{RAILS_FRAMEWORK_ROOT}/activesupport/lib/active_support/locale/en.yml + # #{RAILS_FRAMEWORK_ROOT}/activemodel/lib/active_model/locale/en.yml + # #{RAILS_FRAMEWORK_ROOT}/activerecord/lib/active_record/locale/en.yml + # #{RAILS_FRAMEWORK_ROOT}/actionpack/lib/action_view/locale/en.yml + # #{app_path}/config/locales/en.yml + # my/other/locale.yml + # #{app_path}/vendor/plugins/engine/config/locales/en.yml + # ).map { |path| File.expand_path(path) }, I18n.load_path.map { |path| File.expand_path(path) } end end end
\ No newline at end of file diff --git a/railties/test/initializer/path_test.rb b/railties/test/initializer/path_test.rb index 72ff8d88e0..1b58a58555 100644 --- a/railties/test/initializer/path_test.rb +++ b/railties/test/initializer/path_test.rb @@ -6,12 +6,15 @@ class PathsTest < Test::Unit::TestCase def setup build_app boot_rails + require "rails" Rails::Initializer.run do |config| + config.root = app_path config.frameworks = [:action_controller, :action_view, :action_mailer, :active_record] config.after_initialize do ActionController::Base.session_store = nil end end + Rails.initialize! @paths = Rails.application.config.paths end diff --git a/railties/test/initializer_test.rb b/railties/test/initializer_test.rb deleted file mode 100644 index 80e774b7b7..0000000000 --- a/railties/test/initializer_test.rb +++ /dev/null @@ -1,20 +0,0 @@ -require 'abstract_unit' -require 'rails/initializer' -require 'rails/generators' - -require 'action_view' -require 'action_mailer' -require 'active_record' - -require 'plugin_test_helper' - -class RailsRootTest < Test::Unit::TestCase - def test_rails_dot_root_equals_rails_root - assert_equal RAILS_ROOT, Rails.root.to_s - end - - def test_rails_dot_root_should_be_a_pathname - assert_equal File.join(RAILS_ROOT, 'app', 'controllers'), Rails.root.join('app', 'controllers').to_s - end -end - diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index f83e0151a4..ba8b35d5cc 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -6,7 +6,6 @@ # # It is also good to know what is the bare minimum to get # Rails booted up. - require 'fileutils' # TODO: Remove rubygems when possible @@ -25,8 +24,10 @@ module TestHelpers module Paths module_function + TMP_PATH = File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp])) + def tmp_path(*args) - File.expand_path(File.join(File.dirname(__FILE__), *%w[.. .. tmp] + args)) + File.join(TMP_PATH, *args) end def app_path(*args) @@ -88,10 +89,48 @@ module TestHelpers end end - environment = File.read("#{app_path}/config/environment.rb") + add_to_config 'config.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }' + end + + class Bukkit + def initialize(path) + @path = path + end + + def write(file, string) + path = "#{@path}/#{file}" + FileUtils.mkdir_p(File.dirname(path)) + File.open(path, "w") {|f| f.puts string } + end + + def delete(file) + File.delete("#{@path}/#{file}") + end + end + + def plugin(name, string = "") + dir = "#{app_path}/vendor/plugins/#{name}" + FileUtils.mkdir_p(dir) + File.open("#{dir}/init.rb", 'w') do |f| + f.puts "::#{name.upcase} = 'loaded'" + f.puts string + end + Bukkit.new(dir).tap do |bukkit| + yield bukkit if block_given? + end + end + + def script(script) + Dir.chdir(app_path) do + `#{Gem.ruby} #{app_path}/script/#{script}` + end + end + + def add_to_config(str) + environment = File.read("#{app_path}/config/application.rb") if environment =~ /(\n\s*end\s*)\Z/ - File.open("#{app_path}/config/environment.rb", 'w') do |f| - f.puts $` + %'\nconfig.action_controller.session = { :key => "_myapp_session", :secret => "bac838a849c1d5c4de2e6a50af826079" }\n' + $1 + File.open("#{app_path}/config/application.rb", 'w') do |f| + f.puts $` + "\n#{str}\n" + $1 end end end @@ -108,16 +147,23 @@ module TestHelpers end def boot_rails - # TMP mega hax to prevent boot.rb from actually booting - Object.class_eval <<-RUBY, __FILE__, __LINE__+1 - module Rails - Initializer = 'lol' - require "#{app_path}/config/boot" - remove_const(:Initializer) - booter = VendorBoot.new - booter.run + root = File.expand_path('../../../..', __FILE__) + begin + require "#{root}/vendor/gems/environment" + rescue LoadError + %w( + actionmailer/lib + actionpack/lib + activemodel/lib + activerecord/lib + activeresource/lib + activesupport/lib + railties/lib + railties + ).reverse_each do |path| + $:.unshift "#{root}/#{path}" end - RUBY + end end end end @@ -136,7 +182,16 @@ Module.new do if File.exist?(tmp_path) FileUtils.rm_rf(tmp_path) end - FileUtils.mkdir(tmp_path) - `#{Gem.ruby} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}` + + environment = File.expand_path('../../../../vendor/gems/environment', __FILE__) + if File.exist?("#{environment}.rb") + require_environment = "-r #{environment}" + end + + `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails #{tmp_path('app_template')}` + File.open("#{tmp_path}/app_template/config/boot.rb", 'w') do |f| + f.puts "require '#{environment}'" if require_environment + f.puts "require 'rails'" + end end diff --git a/railties/test/metal_test.rb b/railties/test/metal_test.rb index 6864254e4c..2256b191e2 100644 --- a/railties/test/metal_test.rb +++ b/railties/test/metal_test.rb @@ -85,7 +85,7 @@ class MetalTest < Test::Unit::TestCase private def app - lambda{[402,{},["End of the Line"]]} + lambda{|env|[402,{},["End of the Line"]]} end def use_appdir(root) diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index d50882110f..c724799d64 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -12,11 +12,32 @@ class PathsTest < ActiveSupport::TestCase assert_equal "/fiz/baz", root.path end + test "the paths object can be initialized with nil" do + assert_nothing_raised do + Rails::Application::Root.new(nil) + end + end + + test "a paths object initialized with nil can be updated" do + root = Rails::Application::Root.new(nil) + root.app = "app" + root.path = "/root" + assert_equal ["/root/app"], root.app.to_a + end + test "creating a root level path" do @root.app = "/foo/bar" assert_equal ["/foo/bar"], @root.app.to_a end + test "raises exception if root path never set" do + root = Rails::Application::Root.new(nil) + root.app = "app" + assert_raises RuntimeError do + root.app.to_a + end + end + test "creating a root level path without assignment" do @root.app "/foo/bar" assert_equal ["/foo/bar"], @root.app.to_a diff --git a/railties/test/plugin_loader_test.rb b/railties/test/plugin_loader_test.rb deleted file mode 100644 index 0b43c49bb2..0000000000 --- a/railties/test/plugin_loader_test.rb +++ /dev/null @@ -1,176 +0,0 @@ -require 'plugin_test_helper' - -$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionmailer/lib" -require 'action_controller' -require 'action_mailer' - -# TODO: Rewrite all these tests -class FakeInitializerSlashApplication - attr_reader :config - alias configuration config - - def initialize - @config = Rails::Configuration.new - end -end - -class TestPluginLoader < Test::Unit::TestCase - ORIGINAL_LOAD_PATH = $LOAD_PATH.dup - - def setup - reset_load_path! - - @initializer = FakeInitializerSlashApplication.new - @configuration = @initializer.config - Rails.application = @initializer - @configuration.plugin_paths << plugin_fixture_root_path - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') - - @failure_tip = "It's likely someone has added a new plugin fixture without updating this list" - - @loader = Rails::Plugin::Loader.new(@initializer) - end - - def test_should_locate_plugins_by_asking_each_locator_specifed_in_configuration_for_its_plugins_result - locator_1 = stub(:plugins => [:a, :b, :c]) - locator_2 = stub(:plugins => [:d, :e, :f]) - locator_class_1 = stub(:new => locator_1) - locator_class_2 = stub(:new => locator_2) - @configuration.plugin_locators = [locator_class_1, locator_class_2] - assert_equal [:a, :b, :c, :d, :e, :f], @loader.send(:locate_plugins) - end - - def test_should_memoize_the_result_of_locate_plugins_as_all_plugins - plugin_list = [:a, :b, :c] - @loader.expects(:locate_plugins).once.returns(plugin_list) - assert_equal plugin_list, @loader.all_plugins - assert_equal plugin_list, @loader.all_plugins # ensuring that locate_plugins isn't called again - end - - def test_should_return_empty_array_if_configuration_plugins_is_empty - @configuration.plugins = [] - assert_equal [], @loader.plugins - end - - def test_should_find_all_availble_plugins_and_return_as_all_plugins - assert_plugins [ :engine, :stubby, :plugin_with_no_lib_dir, :gemlike, :acts_as_chunky_bacon, :a], @loader.all_plugins.reverse, @failure_tip - end - - def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_untouched - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end - - def test_should_return_all_plugins_as_plugins_when_registered_plugin_list_is_nil - @configuration.plugins = nil - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end - - def test_should_return_specific_plugins_named_in_config_plugins_array_if_set - plugin_names = [:acts_as_chunky_bacon, :stubby] - only_load_the_following_plugins! plugin_names - assert_plugins plugin_names, @loader.plugins - end - - def test_should_respect_the_order_of_plugins_given_in_configuration - plugin_names = [:stubby, :acts_as_chunky_bacon] - only_load_the_following_plugins! plugin_names - assert_plugins plugin_names, @loader.plugins - end - - def test_should_load_all_plugins_in_natural_order_when_all_is_used - only_load_the_following_plugins! [:all] - assert_plugins [:a, :acts_as_chunky_bacon, :engine, :gemlike, :plugin_with_no_lib_dir, :stubby], @loader.plugins, @failure_tip - end - - def test_should_load_specified_plugins_in_order_and_then_all_remaining_plugins_when_all_is_used - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon, :all] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :engine, :gemlike, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip - end - - def test_should_be_able_to_specify_loading_of_plugins_loaded_after_all - only_load_the_following_plugins! [:stubby, :all, :acts_as_chunky_bacon] - assert_plugins [:stubby, :a, :engine, :gemlike, :plugin_with_no_lib_dir, :acts_as_chunky_bacon], @loader.plugins, @failure_tip - end - - def test_should_accept_plugin_names_given_as_strings - only_load_the_following_plugins! ['stubby', 'acts_as_chunky_bacon', :a, :plugin_with_no_lib_dir] - assert_plugins [:stubby, :acts_as_chunky_bacon, :a, :plugin_with_no_lib_dir], @loader.plugins, @failure_tip - end - - def test_should_add_plugin_load_paths_to_global_LOAD_PATH_array - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - stubbed_application_lib_index_in_LOAD_PATHS = 4 - @loader.stubs(:application_lib_index).returns(stubbed_application_lib_index_in_LOAD_PATHS) - - @loader.add_plugin_load_paths - - assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/stubby'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS - assert $LOAD_PATH.index(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) >= stubbed_application_lib_index_in_LOAD_PATHS - end - - def test_should_add_plugin_load_paths_to_Dependencies_load_paths - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - - @loader.add_plugin_load_paths - - assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert ActiveSupport::Dependencies.load_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) - end - - def test_should_add_engine_load_paths_to_Dependencies_load_paths - only_load_the_following_plugins! [:engine] - - @loader.add_plugin_load_paths - - %w( models controllers metal helpers ).each do |app_part| - assert ActiveSupport::Dependencies.load_paths.include?( - File.join(plugin_fixture_path('engines/engine'), 'app', app_part) - ), "Couldn't find #{app_part} in load path" - end - end - - def test_engine_controllers_and_action_mailers_should_have_their_view_path_set_when_loaded - only_load_the_following_plugins!([ :engine ]) - - @loader.send :add_engine_view_paths - - assert_equal [ File.join(plugin_fixture_path('engines/engine'), 'app', 'views') ], ActionController::Base.view_paths.map { |p| p.to_s } - assert_equal [ File.join(plugin_fixture_path('engines/engine'), 'app', 'views') ], ActionMailer::Base.view_paths.map { |p| p.to_s } - end - - def test_should_add_plugin_load_paths_to_Dependencies_load_once_paths - only_load_the_following_plugins! [:stubby, :acts_as_chunky_bacon] - - @loader.add_plugin_load_paths - - assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/stubby'), 'lib')) - assert ActiveSupport::Dependencies.load_once_paths.include?(File.join(plugin_fixture_path('default/acts/acts_as_chunky_bacon'), 'lib')) - end - - def test_should_add_all_load_paths_from_a_plugin_to_LOAD_PATH_array - plugin_load_paths = ["a", "b"] - plugin = stub(:load_paths => plugin_load_paths) - @loader.stubs(:plugins).returns([plugin]) - - @loader.add_plugin_load_paths - - plugin_load_paths.each { |path| assert $LOAD_PATH.include?(path) } - end - - def test_should_add_locale_files_to_I18n_load_path - only_load_the_following_plugins! [:engine] - - @loader.send :add_engine_locales - - assert I18n.load_path.include?(File.join(plugin_fixture_path('engines/engine'), 'config', 'locales', 'en.yml')) - end - - - private - def reset_load_path! - $LOAD_PATH.clear - ORIGINAL_LOAD_PATH.each { |path| $LOAD_PATH << path } - end -end diff --git a/railties/test/plugin_locator_test.rb b/railties/test/plugin_locator_test.rb deleted file mode 100644 index ef57e7ed4c..0000000000 --- a/railties/test/plugin_locator_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'plugin_test_helper' - -# TODO: Rewrite all these tests -class FakeInitializerSlashApplication - attr_reader :config - alias configuration config - - def initialize - @config = Rails::Configuration.new - end -end - -class PluginLocatorTest < Test::Unit::TestCase - def test_should_require_subclasses_to_implement_the_plugins_method - assert_raise(RuntimeError) do - Rails::Plugin::Locator.new(nil).plugins - end - end - - def test_should_iterator_over_plugins_returned_by_plugins_when_calling_each - locator = Rails::Plugin::Locator.new(nil) - locator.stubs(:plugins).returns([:a, :b, :c]) - plugin_consumer = mock - plugin_consumer.expects(:consume).with(:a) - plugin_consumer.expects(:consume).with(:b) - plugin_consumer.expects(:consume).with(:c) - - locator.each do |plugin| - plugin_consumer.consume(plugin) - end - end -end - -class PluginFileSystemLocatorTest < Test::Unit::TestCase - def setup - @initializer = FakeInitializerSlashApplication.new - @configuration = @initializer.config - Rails.application = @initializer - # We need to add our testing plugin directory to the plugin paths so - # the locator knows where to look for our plugins - @configuration.plugin_paths << plugin_fixture_root_path - @locator = Rails::Plugin::FileSystemLocator.new(@initializer) - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') - end - - def test_should_return_rails_plugin_instances_when_calling_create_plugin_with_a_valid_plugin_directory - assert_kind_of Rails::Plugin, @locator.send(:create_plugin, @valid_plugin_path) - end - - def test_should_return_nil_when_calling_create_plugin_with_an_invalid_plugin_directory - assert_nil @locator.send(:create_plugin, @empty_plugin_path) - end - - def test_should_return_all_plugins_found_under_the_set_plugin_paths - assert_equal ["a", "acts_as_chunky_bacon", "engine", "gemlike", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map { |p| p.name }.sort - end - - def test_should_find_plugins_only_under_the_plugin_paths_set_in_configuration - @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "default")] - assert_equal ["acts_as_chunky_bacon", "gemlike", "plugin_with_no_lib_dir", "stubby"].sort, @locator.plugins.map { |p| p.name }.sort - - @configuration.plugin_paths = [File.join(plugin_fixture_root_path, "alternate")] - assert_equal ["a"], @locator.plugins.map { |p| p.name } - end - - def test_should_not_raise_any_error_and_return_no_plugins_if_the_plugin_path_value_does_not_exist - @configuration.plugin_paths = ["some_missing_directory"] - assert_nothing_raised do - assert @locator.plugins.empty? - end - end -end diff --git a/railties/test/plugin_test.rb b/railties/test/plugin_test.rb deleted file mode 100644 index 199adcfe39..0000000000 --- a/railties/test/plugin_test.rb +++ /dev/null @@ -1,174 +0,0 @@ -require 'plugin_test_helper' - -# TODO: Rewrite all these tests -class FakeInitializerSlashApplication - attr_reader :config - alias configuration config - - def initialize - @config = Rails::Configuration.new - end -end - -class PluginTest < Test::Unit::TestCase - def setup - @initializer = FakeInitializerSlashApplication.new - @configuration = @initializer.config - Rails.application = @initializer - @valid_plugin_path = plugin_fixture_path('default/stubby') - @empty_plugin_path = plugin_fixture_path('default/empty') - @gemlike_plugin_path = plugin_fixture_path('default/gemlike') - end - - def test_should_determine_plugin_name_from_the_directory_of_the_plugin - assert_equal 'stubby', plugin_for(@valid_plugin_path).name - assert_equal 'empty', plugin_for(@empty_plugin_path).name - end - - def test_should_not_be_loaded_when_created - assert !plugin_for(@valid_plugin_path).loaded? - end - - def test_should_be_marked_as_loaded_when_load_is_called - plugin = plugin_for(@valid_plugin_path) - assert !plugin.loaded? - plugin.stubs(:evaluate_init_rb) - assert_nothing_raised do - plugin.send(:load, anything) - end - assert plugin.loaded? - end - - def test_should_determine_validity_of_given_path - # This is a plugin path, with a lib dir - assert plugin_for(@valid_plugin_path).valid? - # This just has an init.rb and no lib dir - assert plugin_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).valid? - # This would be a plugin path, but the directory is empty - assert !plugin_for(plugin_fixture_path('default/empty')).valid? - # This is a non sense path - assert !plugin_for(plugin_fixture_path('default/this_directory_does_not_exist')).valid? - end - - def test_should_return_empty_array_for_load_paths_when_plugin_has_no_lib_directory - assert_equal [], plugin_for(plugin_fixture_path('default/plugin_with_no_lib_dir')).load_paths - end - - def test_should_return_array_with_lib_path_for_load_paths_when_plugin_has_a_lib_directory - expected_lib_dir = File.join(plugin_fixture_path('default/stubby'), 'lib') - assert_equal [expected_lib_dir], plugin_for(plugin_fixture_path('default/stubby')).load_paths - end - - def test_should_raise_a_load_error_when_trying_to_determine_the_load_paths_from_an_invalid_plugin - assert_nothing_raised do - plugin_for(@valid_plugin_path).load_paths - end - - assert_raise(LoadError) do - plugin_for(@empty_plugin_path).load_paths - end - - assert_raise(LoadError) do - plugin_for('this_is_not_a_plugin_directory').load_paths - end - end - - def test_should_raise_a_load_error_when_trying_to_load_an_invalid_plugin - # This path is fine so nothing is raised - assert_nothing_raised do - plugin = plugin_for(@valid_plugin_path) - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - - # This path is fine so nothing is raised - assert_nothing_raised do - plugin = plugin_for(@gemlike_plugin_path) - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - - # This is an empty path so it raises - assert_raise(LoadError) do - plugin = plugin_for(@empty_plugin_path) - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - - assert_raise(LoadError) do - plugin = plugin_for('this_is_not_a_plugin_directory') - plugin.stubs(:evaluate_init_rb) - plugin.send(:load, @initializer) - end - end - - def test_should_raise_a_load_error_when_trying_to_access_load_paths_of_an_invalid_plugin - # This path is fine so nothing is raised - assert_nothing_raised do - plugin_for(@valid_plugin_path).load_paths - end - - # This is an empty path so it raises - assert_raise(LoadError) do - plugin_for(@empty_plugin_path).load_paths - end - - assert_raise(LoadError) do - plugin_for('this_is_not_a_plugin_directory').load_paths - end - end - - def test_loading_a_plugin_gives_the_init_file_access_to_all_it_needs - failure_tip = "Perhaps someone has written another test that loads this same plugin and therefore makes the StubbyMixin constant defined already." - assert !defined?(StubbyMixin), failure_tip - plugin = plugin_for(@valid_plugin_path) - plugin.load_paths.each { |path| $LOAD_PATH.unshift(path) } - # The init.rb of this plugin raises if it doesn't have access to all the things it needs - assert_nothing_raised do - plugin.load(@initializer) - end - assert defined?(StubbyMixin) - end - - def test_should_sort_naturally_by_name - a = plugin_for("path/a") - b = plugin_for("path/b") - z = plugin_for("path/z") - assert_equal [a, b, z], [b, z, a].sort - end - - def test_should_only_be_loaded_once - plugin = plugin_for(@valid_plugin_path) - assert !plugin.loaded? - plugin.expects(:evaluate_init_rb) - assert_nothing_raised do - plugin.send(:load, @initializer) - plugin.send(:load, @initializer) - end - assert plugin.loaded? - end - - def test_should_make_about_yml_available_as_about_method_on_plugin - plugin = plugin_for(@valid_plugin_path) - assert_equal "Plugin Author", plugin.about['author'] - assert_equal "1.0.0", plugin.about['version'] - end - - def test_should_return_empty_hash_for_about_if_about_yml_is_missing - assert_equal({}, plugin_for(about_yml_plugin_path('plugin_without_about_yaml')).about) - end - - def test_should_return_empty_hash_for_about_if_about_yml_is_malformed - assert_equal({}, plugin_for(about_yml_plugin_path('bad_about_yml')).about) - end - - private - - def about_yml_plugin_path(name) - File.join(File.dirname(__FILE__), 'fixtures', 'about_yml_plugins', name) - end - - def plugin_for(path) - Rails::Plugin.new(path) - end -end diff --git a/railties/test/plugin_test_helper.rb b/railties/test/plugin_test_helper.rb deleted file mode 100644 index 93004e0ddf..0000000000 --- a/railties/test/plugin_test_helper.rb +++ /dev/null @@ -1,29 +0,0 @@ -$:.unshift File.dirname(__FILE__) + "/../lib" -$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" - -require 'test/unit' -require 'active_support' -require 'rails/initializer' -require 'abstract_unit' - -# We need to set RAILS_ROOT if it isn't already set -RAILS_ROOT = '.' unless defined?(RAILS_ROOT) - -class Test::Unit::TestCase - private - def plugin_fixture_root_path - File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'plugins')) - end - - def only_load_the_following_plugins!(plugins) - @initializer.configuration.plugins = plugins - end - - def plugin_fixture_path(path) - File.join(plugin_fixture_root_path, path) - end - - def assert_plugins(list_of_names, array_of_plugins, message=nil) - assert_equal list_of_names.map { |n| n.to_s }, array_of_plugins.map { |p| p.name }, message - end -end diff --git a/railties/test/plugins/vendored_test.rb b/railties/test/plugins/vendored_test.rb new file mode 100644 index 0000000000..9a2d40cad8 --- /dev/null +++ b/railties/test/plugins/vendored_test.rb @@ -0,0 +1,195 @@ +require "isolation/abstract_unit" + +module PluginsTest + class VendoredTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + + @plugin = plugin "bukkits", "::LEVEL = config.log_level" do |plugin| + plugin.write "lib/bukkits.rb", "class Bukkits; end" + end + end + + def boot_rails + super + require "#{app_path}/config/environment" + end + + test "it loads the plugin's init.rb file" do + boot_rails + assert_equal "loaded", BUKKITS + end + + test "the init.rb file has access to the config object" do + boot_rails + assert_equal :debug, LEVEL + end + + test "the plugin puts its lib directory on the load path" do + boot_rails + require "bukkits" + assert_equal "Bukkits", Bukkits.name + end + + test "plugin paths get added to the AS::Dependency list" do + boot_rails + assert_equal "Bukkits", Bukkits.name + end + + test "plugin constants do not get reloaded by default" do + boot_rails + assert_equal "Bukkits", Bukkits.name + ActiveSupport::Dependencies.clear + @plugin.delete("lib/bukkits.rb") + assert_nothing_raised { Bukkits } + end + + test "plugin constants get reloaded if config.reload_plugins is set" do + add_to_config <<-RUBY + config.reload_plugins = true + RUBY + + boot_rails + + assert_equal "Bukkits", Bukkits.name + ActiveSupport::Dependencies.clear + @plugin.delete("lib/bukkits.rb") + assert_raises(NameError) { Bukkits } + end + + test "plugin should work without init.rb" do + @plugin.delete("init.rb") + + boot_rails + + require "bukkits" + assert_nothing_raised { Bukkits } + end + + test "the plugin puts its models directory on the load path" do + @plugin.write "app/models/my_bukkit.rb", "class MyBukkit ; end" + + boot_rails + + assert_nothing_raised { MyBukkit } + end + + test "the plugin puts is controllers directory on the load path" do + @plugin.write "app/controllers/bukkit_controller.rb", "class BukkitController ; end" + + boot_rails + + assert_nothing_raised { BukkitController } + end + + test "the plugin adds its view to the load path" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello bukkits" + + boot_rails + + require "action_controller" + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hello bukkits\n", response[2].body + end + + test "the plugin adds helpers to the controller's views" do + @plugin.write "app/controllers/bukkit_controller.rb", <<-RUBY + class BukkitController < ActionController::Base + def index + end + end + RUBY + + @plugin.write "app/helpers/bukkit_helper.rb", <<-RUBY + module BukkitHelper + def bukkits + "bukkits" + end + end + RUBY + + @plugin.write "app/views/bukkit/index.html.erb", "Hello <%= bukkits %>" + + boot_rails + + require "rack/mock" + response = BukkitController.action(:index).call(Rack::MockRequest.env_for("/")) + assert_equal "Hello bukkits\n", response[2].body + end + + test "routes.rb are added to the router" do + @plugin.write "config/routes.rb", <<-RUBY + class Sprokkit + def self.call(env) + [200, {'Content-Type' => 'text/html'}, ["I am a Sprokkit"]] + end + end + + ActionController::Routing::Routes.draw do + match "/sprokkit", :to => Sprokkit + end + RUBY + + boot_rails + require "rack/mock" + response = Rails.application.call(Rack::MockRequest.env_for("/sprokkit")) + assert_equal "I am a Sprokkit", response[2].join + end + end + + class VendoredOrderingTest < Test::Unit::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + $arr = [] + plugin "a_plugin", "$arr << :a" + plugin "b_plugin", "$arr << :b" + plugin "c_plugin", "$arr << :c" + end + + def boot_rails + super + require "#{app_path}/config/environment" + end + + test "plugins are loaded alphabetically by default" do + boot_rails + assert_equal [:a, :b, :c], $arr + end + + test "if specified, only those plugins are loaded" do + add_to_config "config.plugins = [:b_plugin]" + boot_rails + assert_equal [:b], $arr + end + + test "the plugins are initialized in the order they are specified" do + add_to_config "config.plugins = [:b_plugin, :a_plugin]" + boot_rails + assert_equal [:b, :a], $arr + end + + test "if :all is specified, the remaining plugins are loaded in alphabetical order" do + add_to_config "config.plugins = [:c_plugin, :all]" + boot_rails + assert_equal [:c, :a, :b], $arr + end + + test "if :all is at the beginning, it represents the plugins not otherwise specified" do + add_to_config "config.plugins = [:all, :b_plugin]" + boot_rails + assert_equal [:a, :c, :b], $arr + end + end +end
\ No newline at end of file diff --git a/railties/test/rails_info_controller_test.rb b/railties/test/rails_info_controller_test.rb index 99cf9168e1..a0484c0868 100644 --- a/railties/test/rails_info_controller_test.rb +++ b/railties/test/rails_info_controller_test.rb @@ -4,10 +4,6 @@ require 'action_controller' require 'rails/info' require 'rails/info_controller' -ActionController::Routing::Routes.draw do |map| - map.connect ':controller/:action/:id' -end - module ActionController class Base include ActionController::Testing @@ -18,9 +14,17 @@ class InfoControllerTest < ActionController::TestCase tests Rails::InfoController def setup + ActionController::Routing.use_controllers!(['rails/info']) + ActionController::Routing::Routes.draw do |map| + map.connect ':controller/:action/:id' + end @controller.stubs(:consider_all_requests_local => false, :local_request? => true) end + def teardown + ActionController::Routing.use_controllers! nil + end + test "info controller does not allow remote requests" do @controller.stubs(:consider_all_requests_local => false, :local_request? => false) get :properties diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index dcf9966c0d..fc28d7e912 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -1,15 +1,4 @@ -$:.unshift File.dirname(__FILE__) + "/../lib" -$:.unshift File.dirname(__FILE__) + "/../builtin/rails_info" -$:.unshift File.dirname(__FILE__) + "/../../activesupport/lib" -$:.unshift File.dirname(__FILE__) + "/../../actionpack/lib" - -require 'rubygems' -gem 'rack', '~> 1.0.0' - -require 'test/unit' -require 'active_support' -require 'active_support/test_case' -require 'action_controller' +require 'abstract_unit' unless defined?(Rails) && defined?(Rails::Info) module Rails |