diff options
125 files changed, 1357 insertions, 475 deletions
@@ -9,6 +9,7 @@ gem "mocha", ">= 0.9.8" group :mri do gem 'json' gem 'yajl-ruby' + gem "nokogiri", ">= 1.4.0" if RUBY_VERSION < '1.9' gem "system_timer" @@ -19,7 +20,7 @@ group :mri do end # AR -gem "sqlite3-ruby", ">= 1.2.5", :require => 'sqlite3' +gem "sqlite3-ruby", "= 1.3.0.beta.2", :require => 'sqlite3' group :db do gem "pg", ">= 0.9.0" diff --git a/actionmailer/CHANGELOG b/actionmailer/CHANGELOG index 383ad2105d..208299cfd1 100644 --- a/actionmailer/CHANGELOG +++ b/actionmailer/CHANGELOG @@ -1,3 +1,7 @@ +* Changed encoding behaviour of mail, so updated tests in actionmailer and bumped mail version to 2.2.1 [ML] + +* Added ability to pass Proc objects to the defaults hash [ML] + *Rails 3.0.0 [beta 3] (April 13th, 2010)* * Removed all quoting.rb type files from ActionMailer and put Mail 2.2.0 in instead [ML] diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 01a886c5ee..280771e4db 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -20,6 +20,6 @@ Gem::Specification.new do |s| s.has_rdoc = true s.add_dependency('actionpack', version) - s.add_dependency('mail', '~> 2.2.0') + s.add_dependency('mail', '~> 2.2.1') s.add_dependency('text-format', '~> 1.0.0') end diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 88298966eb..3c41691af7 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -3,6 +3,7 @@ require 'action_mailer/tmail_compat' require 'action_mailer/collector' require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/proc' module ActionMailer #:nodoc: # Action Mailer allows you to send email from your application using a mailer model and views. @@ -22,16 +23,16 @@ module ActionMailer #:nodoc: # class Notifier < ActionMailer::Base # default :from => 'no-reply@example.com', # :return_path => 'system@example.com' - # + # # def welcome(recipient) # @account = recipient # mail(:to => recipient.email_address_with_name, # :bcc => ["bcc@example.com", "Order Watcher <watcher@example.com>"]) # end # end - # + # # Within the mailer method, you have access to the following methods: - # + # # * <tt>attachments[]=</tt> - Allows you to add attachments to your email in an intuitive # manner; <tt>attachments['filename.png'] = File.read('path/to/filename.png')</tt> # @@ -46,16 +47,16 @@ module ActionMailer #:nodoc: # as <tt>headers({'X-No-Spam' => 'True', 'In-Reply-To' => '1234@message.id'})</tt> # # * <tt>mail</tt> - Allows you to specify your email to send. - # + # # The hash passed to the mail method allows you to specify any header that a Mail::Message # will accept (any valid Email header including optional fields). # # The mail method, if not passed a block, will inspect your views and send all the views with # the same name as the method, so the above action would send the +welcome.text.plain.erb+ view # file as well as the +welcome.text.html.erb+ view file in a +multipart/alternative+ email. - # + # # If you want to explicitly render only certain templates, pass a block: - # + # # mail(:to => user.emai) do |format| # format.text # format.html @@ -79,7 +80,7 @@ module ActionMailer #:nodoc: # # Like Action Controller, each mailer class has a corresponding view directory in which each # method of the class looks for a template with its name. - # + # # To define a template to be used with a mailing, create an <tt>.erb</tt> file with the same # name as the method in your mailer model. For example, in the mailer defined above, the template at # <tt>app/views/notifier/signup_notification.text.plain.erb</tt> would be used to generate the email. @@ -104,7 +105,7 @@ module ActionMailer #:nodoc: # # = Generating URLs # - # URLs can be generated in mailer views using <tt>url_for</tt> or named routes. Unlike controllers from + # URLs can be generated in mailer views using <tt>url_for</tt> or named routes. Unlike controllers from # Action Pack, the mailer instance doesn't have any context about the incoming request, so you'll need # to provide all of the details needed to generate a URL. # @@ -176,7 +177,7 @@ module ActionMailer #:nodoc: # mail(:to => recipient, :subject => "New account information") # end # end - # + # # Which will (if it had both a <tt>welcome.text.plain.erb</tt> and <tt>welcome.text.html.erb</tt> # tempalte in the view directory), send a complete <tt>multipart/mixed</tt> email with two parts, # the first part being a <tt>multipart/alternative</tt> with the text and HTML email parts inside, @@ -184,71 +185,71 @@ module ActionMailer #:nodoc: # with the filename +free_book.pdf+. # # = Observing and Intercepting Mails - # + # # Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to # register objects that are called during the mail delivery life cycle. - # + # # An observer object must implement the <tt>:delivered_email(message)</tt> method which will be # called once for every email sent after the email has been sent. - # + # # An interceptor object must implement the <tt>:delivering_email(message)</tt> method which will be # called before the email is sent, allowing you to make modifications to the email before it hits # the delivery agents. Your object should make and needed modifications directly to the passed # in Mail::Message instance. # # = Default Hash - # + # # Action Mailer provides some intelligent defaults for your emails, these are usually specified in a # default method inside the class definition: - # + # # class Notifier < ActionMailer::Base # default :sender => 'system@example.com' # end - # + # # You can pass in any header value that a <tt>Mail::Message</tt>, out of the box, <tt>ActionMailer::Base</tt> # sets the following: - # + # # * <tt>:mime_version => "1.0"</tt> # * <tt>:charset => "UTF-8",</tt> # * <tt>:content_type => "text/plain",</tt> # * <tt>:parts_order => [ "text/plain", "text/enriched", "text/html" ]</tt> - # + # # <tt>parts_order</tt> and <tt>charset</tt> are not actually valid <tt>Mail::Message</tt> header fields, # but Action Mailer translates them appropriately and sets the correct values. - # + # # As you can pass in any header, you need to either quote the header as a string, or pass it in as # an underscorised symbol, so the following will work: - # + # # class Notifier < ActionMailer::Base # default 'Content-Transfer-Encoding' => '7bit', # :content_description => 'This is a description' # end - # + # # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you # can define methods that evaluate as the message is being generated: - # + # # class Notifier < ActionMailer::Base # default 'X-Special-Header' => Proc.new { my_method } - # + # # private - # + # # def my_method # 'some complex call' # end # end - # + # # Note that the proc is evaluated right at the start of the mail message generation, so if you - # set something in the defaults using a proc, and then set the same thing inside of your + # set something in the defaults using a proc, and then set the same thing inside of your # mailer method, it will get over written by the mailer method. - # + # # = Configuration options # - # These options are specified on the class level, like + # These options are specified on the class level, like # <tt>ActionMailer::Base.template_root = "/my/templates"</tt> # # * <tt>default</tt> - You can pass this in at a class level as well as within the class itself as # per the above section. - # + # # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. # @@ -288,16 +289,16 @@ module ActionMailer #:nodoc: # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with # <tt>delivery_method :test</tt>. Most useful for unit and functional testing. # - # * <tt>default_charset</tt> - This is now deprecated, use the +default+ method above to + # * <tt>default_charset</tt> - This is now deprecated, use the +default+ method above to # set the default +:charset+. # - # * <tt>default_content_type</tt> - This is now deprecated, use the +default+ method above + # * <tt>default_content_type</tt> - This is now deprecated, use the +default+ method above # to set the default +:content_type+. # - # * <tt>default_mime_version</tt> - This is now deprecated, use the +default+ method above + # * <tt>default_mime_version</tt> - This is now deprecated, use the +default+ method above # to set the default +:mime_version+. # - # * <tt>default_implicit_parts_order</tt> - This is now deprecated, use the +default+ method above + # * <tt>default_implicit_parts_order</tt> - This is now deprecated, use the +default+ method above # to set the default +:parts_order+. Parts Order is used when a message is built implicitly # (i.e. multiple parts are assembled from templates which specify the content type in their # filenames) this variable controls how the parts are ordered. @@ -315,7 +316,7 @@ module ActionMailer #:nodoc: include ActionMailer::OldApi include ActionMailer::DeprecatedApi - + delegate :register_observer, :to => Mail delegate :register_interceptor, :to => Mail @@ -418,17 +419,17 @@ module ActionMailer #:nodoc: # Allows you to pass random and unusual headers to the new +Mail::Message+ object # which will add them to itself. - # + # # headers['X-Special-Domain-Specific-Header'] = "SecretValue" - # + # # You can also pass a hash into headers of header field names and values, which # will then be set on the Mail::Message object: - # + # # headers 'X-Special-Domain-Specific-Header' => "SecretValue", # 'In-Reply-To' => incoming.message_id - # + # # The resulting Mail::Message will have the following in it's header: - # + # # X-Special-Domain-Specific-Header: SecretValue def headers(args=nil) if args @@ -439,45 +440,45 @@ module ActionMailer #:nodoc: end # Allows you to add attachments to an email, like so: - # + # # mail.attachments['filename.jpg'] = File.read('/path/to/filename.jpg') - # + # # If you do this, then Mail will take the file name and work out the mime type - # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and + # set the Content-Type, Content-Disposition, Content-Transfer-Encoding and # base64 encode the contents of the attachment all for you. - # + # # You can also specify overrides if you want by passing a hash instead of a string: - # + # # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip', # :content => File.read('/path/to/filename.jpg')} - # + # # If you want to use a different encoding than Base64, you can pass an encoding in, # but then it is up to you to pass in the content pre-encoded, and don't expect # Mail to know how to decode this data: - # + # # file_content = SpecialEncode(File.read('/path/to/filename.jpg')) # mail.attachments['filename.jpg'] = {:mime_type => 'application/x-gzip', # :encoding => 'SpecialEncoding', # :content => file_content } - # + # # You can also search for specific attachments: - # + # # # By Filename # mail.attachments['filename.jpg'] #=> Mail::Part object or nil - # + # # # or by index # mail.attachments[0] #=> Mail::Part (first attachment) - # + # def attachments @_message.attachments end # The main method that creates the message and renders the email templates. There are # two ways to call this method, with a block, or without a block. - # + # # Both methods accept a headers hash. This hash allows you to specify the most used headers # in an email message, these are: - # + # # * <tt>:subject</tt> - The subject of the message, if this is omitted, Action Mailer will # ask the Rails I18n class for a translated <tt>:subject</tt> in the scope of # <tt>[:actionmailer, mailer_scope, action_name]</tt> or if this is missing, will translate the @@ -491,25 +492,25 @@ module ActionMailer #:nodoc: # addresses, or an array of addresses. # * <tt>:reply_to</tt> - Who to set the Reply-To header of the email to. # * <tt>:date</tt> - The date to say the email was sent on. - # - # You can set default values for any of the above headers (except :date) by using the <tt>default</tt> + # + # You can set default values for any of the above headers (except :date) by using the <tt>default</tt> # class method: - # + # # class Notifier < ActionMailer::Base # self.default :from => 'no-reply@test.lindsaar.net', # :bcc => 'email_logger@test.lindsaar.net', # :reply_to => 'bounces@test.lindsaar.net' # end - # + # # If you need other headers not listed above, use the <tt>headers['name'] = value</tt> method. # # When a <tt>:return_path</tt> is specified as header, that value will be used as the 'envelope from' # address for the Mail message. Setting this is useful when you want delivery notifications - # sent to a different address than the one in <tt>:from</tt>. Mail will actually use the + # sent to a different address than the one in <tt>:from</tt>. Mail will actually use the # <tt>:return_path</tt> in preference to the <tt>:sender</tt> in preference to the <tt>:from</tt> # field for the 'envelope from' value. # - # If you do not pass a block to the +mail+ method, it will find all templates in the + # If you do not pass a block to the +mail+ method, it will find all templates in the # view paths using by default the mailer name and the method name that it is being # called from, it will then create parts for each of these templates intelligently, # making educated guesses on correct content type and sequence, and return a fully @@ -533,19 +534,19 @@ module ActionMailer #:nodoc: # And now it will look for all templates at "app/views/notifications" with name "another". # # If you do pass a block, you can render specific templates of your choice: - # + # # mail(:to => 'mikel@test.lindsaar.net') do |format| # format.text # format.html # end - # + # # You can even render text directly without using a template: - # + # # mail(:to => 'mikel@test.lindsaar.net') do |format| # format.text { render :text => "Hello Mikel!" } # format.html { render :text => "<h1>Hello Mikel!</h1>" } # end - # + # # Which will render a <tt>multipart/alternative</tt> email with <tt>text/plain</tt> and # <tt>text/html</tt> parts. # @@ -570,7 +571,7 @@ module ActionMailer #:nodoc: default_values = self.class.default.merge(self.class.default) do |k,v| v.respond_to?(:call) ? v.bind(self).call : v end - + # Handle defaults headers = headers.reverse_merge(default_values) headers[:subject] ||= default_i18n_subject @@ -684,6 +685,28 @@ module ActionMailer #:nodoc: container.add_part(part) end + module DeprecatedUrlOptions + def default_url_options + deprecated_url_options + end + + def default_url_options=(val) + deprecated_url_options + end + + def deprecated_url_options + raise "You can no longer call ActionMailer::Base.default_url_options " \ + "directly. You need to set config.action_mailer.default_url_options. " \ + "If you are using ActionMailer standalone, you need to include the " \ + "url_helpers of a router directly." + end + end + + # This module will complain if the user tries to set default_url_options + # directly instead of through the config object. In ActionMailer's Railtie, + # we include the url_helpers of the router, which will override this module + extend DeprecatedUrlOptions + ActiveSupport.run_load_hooks(:action_mailer, self) end end diff --git a/actionmailer/lib/action_mailer/railtie.rb b/actionmailer/lib/action_mailer/railtie.rb index 882e078d1b..0730167a3e 100644 --- a/actionmailer/lib/action_mailer/railtie.rb +++ b/actionmailer/lib/action_mailer/railtie.rb @@ -5,10 +5,6 @@ module ActionMailer class Railtie < Rails::Railtie config.action_mailer = ActiveSupport::OrderedOptions.new - initializer "action_mailer.url_for", :before => :load_environment_config do |app| - ActiveSupport.on_load(:action_mailer) { include app.routes.url_helpers } - end - require "action_mailer/railties/log_subscriber" log_subscriber :action_mailer, ActionMailer::Railties::LogSubscriber.new @@ -18,6 +14,8 @@ module ActionMailer initializer "action_mailer.set_configs" do |app| ActiveSupport.on_load(:action_mailer) do + include app.routes.url_helpers + app.config.action_mailer.each do |k,v| send "#{k}=", v end diff --git a/actionmailer/test/old_base/mail_service_test.rb b/actionmailer/test/old_base/mail_service_test.rb index 9da9132fe1..f91e7f893c 100644 --- a/actionmailer/test/old_base/mail_service_test.rb +++ b/actionmailer/test/old_base/mail_service_test.rb @@ -674,7 +674,7 @@ The body EOF mail = Mail.new(msg) assert_equal "testing testing \326\244", mail.subject - assert_equal "Subject: testing testing =?UTF-8?Q?_=D6=A4=?=\r\n", mail[:subject].encoded + assert_equal "Subject: =?UTF-8?Q?testing_testing_=D6=A4?=\r\n", mail[:subject].encoded end def test_unquote_7bit_subject @@ -863,7 +863,7 @@ EOF def test_multipart_with_utf8_subject mail = TestMailer.multipart_with_utf8_subject(@recipient) - regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=') + regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=') assert_match(/#{regex}/, mail.encoded) string = "Foo áëô îü" assert_match(string, mail.subject) @@ -871,7 +871,7 @@ EOF def test_implicitly_multipart_with_utf8 mail = TestMailer.implicitly_multipart_with_utf8 - regex = Regexp.escape('Subject: Foo =?UTF-8?Q?=C3=A1=C3=AB=C3=B4=?= =?UTF-8?Q?_=C3=AE=C3=BC=?=') + regex = Regexp.escape('Subject: =?UTF-8?Q?Foo_=C3=A1=C3=AB=C3=B4_=C3=AE=C3=BC?=') assert_match(/#{regex}/, mail.encoded) string = "Foo áëô îü" assert_match(string, mail.subject) diff --git a/actionpack/Rakefile b/actionpack/Rakefile index b9ace8658a..f3bd7dfc10 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -88,23 +88,4 @@ task :lines do end puts "Total: Lines #{total_lines}, LOC #{total_codelines}" -end - -# Publishing ------------------------------------------------------ - -task :update_scriptaculous do - for js in %w( controls dragdrop effects ) - system("svn export --force http://dev.rubyonrails.org/svn/rails/spinoffs/scriptaculous/src/#{js}.js #{File.dirname(__FILE__)}/lib/action_view/helpers/javascripts/#{js}.js") - end -end - -desc "Updates actionpack to the latest version of the javascript spinoffs" -task :update_js => [ :update_scriptaculous ] - -# Publishing ------------------------------------------------------ - -desc "Publish the API documentation" -task :pdoc => [:rdoc] do - require 'rake/contrib/sshpublisher' - Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload end
\ No newline at end of file diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 99b36366d6..adde183cdb 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -49,6 +49,16 @@ module ActionDispatch def [](k) @flash[k] end + + # Convenience accessor for flash.now[:alert]= + def alert=(message) + self[:alert] = message + end + + # Convenience accessor for flash.now[:notice]= + def notice=(message) + self[:notice] = message + end end class FlashHash < Hash diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 88ba941676..7114f42003 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -177,7 +177,7 @@ module ActionDispatch if key.blank? raise ArgumentError, 'A key is required to write a ' + 'cookie containing the session data. Use ' + - 'config.action_controller.session_store :cookie_store, { :key => ' + + 'config.session_store :cookie_store, { :key => ' + '"_myapp_session" } in config/application.rb' end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 12a93d6a24..2dd2ec9fe9 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -133,14 +133,10 @@ module ActionDispatch return unless logger ActiveSupport::Deprecation.silence do - if ActionView::Template::Error === exception - logger.fatal(exception.to_s) - else - logger.fatal( - "\n#{exception.class} (#{exception.message}):\n " + - clean_backtrace(exception).join("\n ") + "\n\n" - ) - end + message = "\n#{exception.class} (#{exception.message}):\n" + message << exception.annoted_source_code if exception.respond_to?(:annoted_source_code) + message << exception.backtrace.join("\n ") + logger.fatal("#{message}\n\n") end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index a6b1126e2b..9deabf5b3c 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -298,10 +298,14 @@ module ActionDispatch # found one but expecting two. message ||= content_mismatch if matches.empty? # Test minimum/maximum occurrence. - min, max = equals[:minimum], equals[:maximum] - message = message || %(Expected #{count_description(min, max)} matching "#{selector.to_s}", found #{matches.size}.) - assert matches.size >= min, message if min - assert matches.size <= max, message if max + min, max, count = equals[:minimum], equals[:maximum], equals[:count] + message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.) + if count + assert matches.size == count, message + else + assert matches.size >= min, message if min + assert matches.size <= max, message if max + end # If a block is given call that block. Set @selected to allow # nested assert_select, which can be nested several levels deep. @@ -318,11 +322,13 @@ module ActionDispatch matches end - def count_description(min, max) #:nodoc: + def count_description(min, max, count) #:nodoc: pluralize = lambda {|word, quantity| word << (quantity == 1 ? '' : 's')} if min && max && (max != min) "between #{min} and #{max} elements" + elsif min && max && max == min && count + "exactly #{count} #{pluralize['element', min]}" elsif min && !(min == 1 && max == 1) "at least #{min} #{pluralize['element', min]}" elsif max diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 8731ed0ef3..e1fbc118d5 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -620,7 +620,7 @@ module ActionView options.symbolize_keys! src = options[:src] = path_to_image(source) - options[:alt] ||= File.basename(src, '.*').capitalize + options[:alt] = options.fetch(:alt){ File.basename(src, '.*').capitalize } if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 6e26ae6c29..414a5d4cd9 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -573,8 +573,19 @@ module ActionView # label(:post, :privacy, "Public Post", :value => "public") # # => <label for="post_privacy_public">Public Post</label> # - def label(object_name, method, text = nil, options = {}) - InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options) + # label(:post, :terms) do + # 'Accept <a href="/terms">Terms</a>.' + # end + def label(object_name, method, content_or_options = nil, options = nil, &block) + if block_given? + options = content_or_options if content_or_options.is_a?(Hash) + text = nil + else + text = content_or_options + end + + options ||= {} + InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options, &block) end # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object @@ -823,7 +834,7 @@ module ActionView module InstanceTagMethods #:nodoc: extend ActiveSupport::Concern - include Helpers::TagHelper, Helpers::FormTagHelper + include Helpers::CaptureHelper, Context, Helpers::TagHelper, Helpers::FormTagHelper attr_reader :method_name, :object_name @@ -844,28 +855,38 @@ module ActionView end end - def to_label_tag(text = nil, options = {}) + def to_label_tag(text = nil, options = {}, &block) options = options.stringify_keys tag_value = options.delete("value") name_and_id = options.dup - name_and_id["id"] = name_and_id["for"] + + if name_and_id["for"] + name_and_id["id"] = name_and_id["for"] + else + name_and_id.delete("id") + end + add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options["for"] ||= name_and_id["id"] - content = if text.blank? - I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence + if block_given? + label_tag(name_and_id["id"], options, &block) else - text.to_s - end + content = if text.blank? + I18n.t("helpers.label.#{object_name}.#{method_name}", :default => "").presence + else + text.to_s + end - content ||= if object && object.class.respond_to?(:human_attribute_name) - object.class.human_attribute_name(method_name) - end + content ||= if object && object.class.respond_to?(:human_attribute_name) + object.class.human_attribute_name(method_name) + end - content ||= method_name.humanize + content ||= method_name.humanize - label_tag(name_and_id["id"], content, options) + label_tag(name_and_id["id"], content, options) + end end def to_input_field_tag(field_type, options = {}) @@ -1012,7 +1033,7 @@ module ActionView pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase specified_id = options["id"] add_default_name_and_id(options) - options["id"] += "_#{pretty_tag_value}" unless specified_id + options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present? else add_default_name_and_id(options) end @@ -1021,14 +1042,14 @@ module ActionView def add_default_name_and_id(options) if options.has_key?("index") options["name"] ||= tag_name_with_index(options["index"]) - options["id"] ||= tag_id_with_index(options["index"]) + options["id"] = options.fetch("id", tag_id_with_index(options["index"])) options.delete("index") elsif defined?(@auto_index) options["name"] ||= tag_name_with_index(@auto_index) - options["id"] ||= tag_id_with_index(@auto_index) + options["id"] = options.fetch("id", tag_id_with_index(@auto_index)) else options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '') - options["id"] ||= tag_id + options["id"] = options.fetch("id", tag_id) end end @@ -1137,8 +1158,8 @@ module ActionView @template.fields_for(name, *args, &block) end - def label(method, text = nil, options = {}) - @template.label(@object_name, method, text, objectify_options(options)) + def label(method, text = nil, options = {}, &block) + @template.label(@object_name, method, text, objectify_options(options), &block) end def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 8f8db548c3..fe71d2cdf7 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -270,6 +270,15 @@ module ActionView # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"]) # <option selected="selected">VISA</option>\n<option>MasterCard</option>\n<option selected="selected">Discover</option> # + # You can optionally provide html attributes as the last element of the array. + # + # Examples: + # options_for_select([ "Denmark", ["USA", {:class=>'bold'}], "Sweden" ], ["USA", "Sweden"]) + # <option value="Denmark">Denmark</option>\n<option value="USA" class="bold" selected="selected">USA</option>\n<option value="Sweden" selected="selected">Sweden</option> + # + # options_for_select([["Dollar", "$", {:class=>"bold"}], ["Kroner", "DKK", {:onclick => "alert('HI');"}]]) + # <option value="$" class="bold">Dollar</option>\n<option value="DKK" onclick="alert('HI');">Kroner</option> + # # If you wish to specify disabled option tags, set +selected+ to be a hash, with <tt>:disabled</tt> being either a value # or array of values to be disabled. In this case, you can use <tt>:selected</tt> to specify selected option tags. # @@ -291,10 +300,11 @@ module ActionView selected, disabled = extract_selected_and_disabled(selected) options_for_select = container.inject([]) do |options, element| + html_attributes = option_html_attributes(element) text, value = option_text_and_value(element) selected_attribute = ' selected="selected"' if option_value_selected?(value, selected) disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled) - options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}>#{html_escape(text.to_s)}</option>) + options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text.to_s)}</option>) end options_for_select.join("\n").html_safe @@ -486,9 +496,22 @@ module ActionView end private + def option_html_attributes(element) + return "" unless Array === element + html_attributes = [] + element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v| + html_attributes << " #{k}=\"#{html_escape(v.to_s)}\"" + end + html_attributes.join + end + def option_text_and_value(option) # Options are [text, value] pairs or strings used for both. - if !option.is_a?(String) and option.respond_to?(:first) and option.respond_to?(:last) + case + when Array === option + option = option.reject { |e| Hash === e } + [option.first, option.last] + when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last) [option.first, option.last] else [option, option] diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index b840f77fb5..2a3f826c15 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -142,7 +142,7 @@ module ActionView tag :input, { "type" => "text", "name" => name, "id" => sanitize_to_id(name), "value" => value }.update(options.stringify_keys) end - # Creates a label field + # Creates a label element. Accepts a block. # # ==== Options # * Creates standard HTML attributes for the tag. @@ -156,8 +156,12 @@ module ActionView # # label_tag 'name', nil, :class => 'small_label' # # => <label for="name" class="small_label">Name</label> - def label_tag(name, text = nil, options = {}) - content_tag :label, text || name.to_s.humanize, { "for" => sanitize_to_id(name) }.update(options.stringify_keys) + def label_tag(name = nil, content_or_options = nil, options = nil, &block) + options = content_or_options if block_given? && content_or_options.is_a?(Hash) + options ||= {} + options.stringify_keys! + options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for") + content_tag :label, content_or_options || name.to_s.humanize, options, &block end # Creates a hidden form input field used to transmit data that would be lost due to HTTP's statelessness or diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb index 974345633c..85f67d4f14 100644 --- a/actionpack/lib/action_view/render/partials.rb +++ b/actionpack/lib/action_view/render/partials.rb @@ -241,15 +241,21 @@ module ActionView end def collection_with_template(template = @template) - segments, locals, as, template = [], @locals, @options[:as] || @template.variable_name, @template + segments, locals, template = [], @locals, @template - counter_name = template.counter_name - locals[counter_name] = -1 + if @options[:as] + as = @options[:as] + counter = "#{as}_counter".to_sym + else + as = template.variable_name + counter = template.counter_name + end + + locals[counter] = -1 @collection.each do |object| - locals[counter_name] += 1 + locals[counter] += 1 locals[as] = object - segments << template.render(@view, locals) end @@ -257,13 +263,18 @@ module ActionView end def collection_without_template(collection_paths = @collection_paths) - segments, locals, as = [], @locals, @options[:as] - index, template = -1, nil + segments, locals = [], @locals + index, template = -1, nil + + if @options[:as] + as = @options[:as] + counter = "#{as}_counter" + end @collection.each_with_index do |object, i| template = find_template(collection_paths[i]) - locals[template.counter_name] = (index += 1) locals[as || template.variable_name] = object + locals[counter || template.counter_name] = (index += 1) segments << template.render(@view, locals) end diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index a947d746e3..6866eabf77 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -84,9 +84,8 @@ module ActionView end end - def to_s - "\n#{self.class} (#{message}) #{source_location}:\n\n" + - "#{source_extract(4)}\n #{backtrace.join("\n ")}\n\n" + def annoted_source_code + source_extract(4) end private diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 705c2bf82e..237746437a 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -28,7 +28,7 @@ module ActionView src << "@output_buffer.safe_concat('" << escape_text(text) << "');" end - BLOCK_EXPR = /(do|\{)(\s*\|[^|]*\|)?\s*\Z/ + BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/ def add_expr_literal(src, code) if code =~ BLOCK_EXPR diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 4ef6fa4000..7012c4c9b0 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -80,10 +80,15 @@ class AssertSelectTest < ActionController::TestCase def test_assert_select render_html %Q{<div id="1"></div><div id="2"></div>} assert_select "div", 2 - assert_failure(/Expected at least 3 elements matching \"div\", found 2/) { assert_select "div", 3 } assert_failure(/Expected at least 1 element matching \"p\", found 0/) { assert_select "p" } end + def test_equality_integer + render_html %Q{<div id="1"></div><div id="2"></div>} + assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) { assert_select "div", 3 } + assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", 0 } + end + def test_equality_true_false render_html %Q{<div id="1"></div><div id="2"></div>} assert_nothing_raised { assert_select "div" } @@ -94,6 +99,11 @@ class AssertSelectTest < ActionController::TestCase assert_nothing_raised { assert_select "p", false } end + def test_equality_false_message + render_html %Q{<div id="1"></div><div id="2"></div>} + assert_failure(/Expected exactly 0 elements matching \"div\", found 2/) { assert_select "div", false } + end + def test_equality_string_and_regexp render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", "foo" } @@ -128,7 +138,7 @@ class AssertSelectTest < ActionController::TestCase def test_counts render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", 2 } - assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do + assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do assert_select "div", 3 end assert_nothing_raised { assert_select "div", 1..2 } @@ -136,7 +146,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 3..4 end assert_nothing_raised { assert_select "div", :count=>2 } - assert_failure(/Expected at least 3 elements matching \"div\", found 2/) do + assert_failure(/Expected exactly 3 elements matching \"div\", found 2/) do assert_select "div", :count=>3 end assert_nothing_raised { assert_select "div", :minimum=>1 } @@ -201,7 +211,7 @@ class AssertSelectTest < ActionController::TestCase assert_nothing_raised { assert_select "div", "foo" } assert_nothing_raised { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", /\w*/ } - assert_nothing_raised { assert_select "div", /\w*/, :count=>2 } + assert_nothing_raised { assert_select "div", :text => /\w*/, :count=>2 } assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 } assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" } assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" } @@ -266,8 +276,8 @@ class AssertSelectTest < ActionController::TestCase def test_css_select render_html %Q{<div id="1"></div><div id="2"></div>} - assert 2, css_select("div").size - assert 0, css_select("p").size + assert_equal 2, css_select("div").size + assert_equal 0, css_select("p").size end def test_nested_css_select diff --git a/actionpack/test/controller/capture_test.rb b/actionpack/test/controller/capture_test.rb index 06a5af6b32..d1dbd535c4 100644 --- a/actionpack/test/controller/capture_test.rb +++ b/actionpack/test/controller/capture_test.rb @@ -61,6 +61,11 @@ class CaptureTest < ActionController::TestCase assert_equal expected_content_for_output, @response.body end + def test_proper_block_detection + @todo = "some todo" + get :proper_block_detection + end + private def expected_content_for_output "<title>Putting stuff in the title!</title>\n\nGreat stuff!" diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 3c651ebebc..c662ce264b 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -81,6 +81,16 @@ class FlashTest < ActionController::TestCase redirect_to '/somewhere', :notice => "Good luck in the somewheres!" end + def render_with_flash_now_alert + flash.now.alert = "Beware the nowheres now!" + render :inline => "hello" + end + + def render_with_flash_now_notice + flash.now.notice = "Good luck in the somewheres now!" + render :inline => "hello" + end + def redirect_with_other_flashes redirect_to '/wonderland', :flash => { :joyride => "Horses!" } end @@ -183,6 +193,16 @@ class FlashTest < ActionController::TestCase assert_equal "Good luck in the somewheres!", @controller.send(:flash)[:notice] end + def test_render_with_flash_now_alert + get :render_with_flash_now_alert + assert_equal "Beware the nowheres now!", @controller.send(:flash)[:alert] + end + + def test_render_with_flash_now_notice + get :render_with_flash_now_notice + assert_equal "Good luck in the somewheres now!", @controller.send(:flash)[:notice] + end + def test_redirect_to_with_other_flashes get :redirect_with_other_flashes assert_equal "Horses!", @controller.send(:flash)[:joyride] diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 2f3997518f..52049f2a8a 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -547,6 +547,10 @@ class TestController < ActionController::Base render :partial => "customer_counter", :collection => [ Customer.new("david"), Customer.new("mary") ] end + def partial_collection_with_as_and_counter + render :partial => "customer_counter_with_as", :collection => [ Customer.new("david"), Customer.new("mary") ], :as => :client + end + def partial_collection_with_locals render :partial => "customer_greeting", :collection => [ Customer.new("david"), Customer.new("mary") ], :locals => { :greeting => "Bonjour" } end @@ -1242,6 +1246,11 @@ class RenderTest < ActionController::TestCase assert_equal "david0mary1", @response.body end + def test_partial_collection_with_as_and_counter + get :partial_collection_with_as_and_counter + assert_equal "david0mary1", @response.body + end + def test_partial_collection_with_locals get :partial_collection_with_locals assert_equal "Bonjour: davidBonjour: mary", @response.body @@ -1379,7 +1388,7 @@ class EtagRenderTest < ActionController::TestCase def test_render_against_etag_request_should_have_no_content_length_when_match @request.if_none_match = etag_for("hello david") get :render_hello_world_from_variable - assert !@response.headers.has_key?("Content-Length"), @response.headers['Content-Length'] + assert !@response.headers.has_key?("Content-Length") end def test_render_against_etag_request_should_200_when_no_match @@ -1515,4 +1524,4 @@ class LastModifiedRenderTest < ActionController::TestCase get :conditional_hello_with_bangs assert_response :success end -end
\ No newline at end of file +end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index f2ce2c5b93..d44c642420 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -21,7 +21,7 @@ class XmlParamsParsingTest < ActionController::IntegrationTest def call(env) bar = env['action_dispatch.request.request_parameters']['foo'] result = "<ok>#{bar}</ok>" - [200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, result] + [200, {"Content-Type" => "application/xml", "Content-Length" => result.length.to_s}, [result]] end end req = Rack::MockRequest.new(ActionDispatch::ParamsParser.new(Linted.new)) diff --git a/actionpack/test/fixtures/test/_customer_counter_with_as.erb b/actionpack/test/fixtures/test/_customer_counter_with_as.erb new file mode 100644 index 0000000000..1241eb604d --- /dev/null +++ b/actionpack/test/fixtures/test/_customer_counter_with_as.erb @@ -0,0 +1 @@ +<%= client.name %><%= client_counter %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/proper_block_detection.erb b/actionpack/test/fixtures/test/proper_block_detection.erb new file mode 100644 index 0000000000..23564dbcee --- /dev/null +++ b/actionpack/test/fixtures/test/proper_block_detection.erb @@ -0,0 +1 @@ +<%= @todo %> diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 124bf734ac..b6a6f52876 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -154,7 +154,8 @@ class AssetTagHelperTest < ActionView::TestCase %(image_tag(".pdf.png")) => %(<img alt=".pdf" src="/images/.pdf.png" />), %(image_tag("http://www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="http://www.rubyonrails.com/images/rails.png" />), %(image_tag("mouse.png", :mouseover => "/images/mouse_over.png")) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />), - %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />) + %(image_tag("mouse.png", :mouseover => image_path("mouse_over.png"))) => %(<img alt="Mouse" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" src="/images/mouse.png" />), + %(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />) } FaviconLinkToTag = { diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index bf541c17d3..9f3d68a639 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -74,7 +74,7 @@ class CaptureHelperTest < ActionView::TestCase @av.output_buffer.force_encoding(alt_encoding) @av.with_output_buffer do - assert alt_encoding, @av.output_buffer.encoding + assert_equal alt_encoding, @av.output_buffer.encoding end end end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 2234fbece9..d1e1338a17 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -139,6 +139,10 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal('<label for="post_title_great_title">The title goes here</label>', label("post", "title", "The title goes here", :value => "great title")) end + def test_label_with_block + assert_dom_equal('<label for="post_title">The title, please:</label>', label(:post, :title) { "The title, please:" }) + end + def test_text_field assert_dom_equal( '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title") @@ -419,6 +423,90 @@ class FormHelperTest < ActionView::TestCase check_box("post", "secret", :id => "i mean it") end + def test_nil_id + assert_dom_equal( + '<input name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => nil) + ) + assert_dom_equal( + '<textarea cols="40" name="post[body]" rows="20">Back to the hill and over it again!</textarea>', + text_area("post", "body", "id" => nil) + ) + assert_dom_equal( + '<input name="post[secret]" type="hidden" value="0" /><input checked="checked" name="post[secret]" type="checkbox" value="1" />', + check_box("post", "secret", "id" => nil) + ) + assert_dom_equal( + '<input type="radio" name="post[secret]" value="0" />', + radio_button("post", "secret", "0", "id" => nil) + ) + assert_dom_equal( + '<select name="post[secret]"></select>', + select("post", "secret", [], {}, "id" => nil) + ) + assert_dom_equal text_field("post", "title", "id" => nil), + text_field("post", "title", :id => nil) + assert_dom_equal text_area("post", "body", "id" => nil), + text_area("post", "body", :id => nil) + assert_dom_equal check_box("post", "secret", "id" => nil), + check_box("post", "secret", :id => nil) + assert_dom_equal radio_button("post", "secret", "0", "id" => nil), + radio_button("post", "secret", "0", :id => nil) + end + + def test_index + assert_dom_equal( + '<input name="post[5][title]" size="30" id="post_5_title" type="text" value="Hello World" />', + text_field("post", "title", "index" => 5) + ) + assert_dom_equal( + '<textarea cols="40" name="post[5][body]" id="post_5_body" rows="20">Back to the hill and over it again!</textarea>', + text_area("post", "body", "index" => 5) + ) + assert_dom_equal( + '<input name="post[5][secret]" type="hidden" value="0" /><input checked="checked" name="post[5][secret]" type="checkbox" value="1" id="post_5_secret" />', + check_box("post", "secret", "index" => 5) + ) + assert_dom_equal( + text_field("post", "title", "index" => 5), + text_field("post", "title", "index" => 5) + ) + assert_dom_equal( + text_area("post", "body", "index" => 5), + text_area("post", "body", "index" => 5) + ) + assert_dom_equal( + check_box("post", "secret", "index" => 5), + check_box("post", "secret", "index" => 5) + ) + end + + def test_index_with_nil_id + assert_dom_equal( + '<input name="post[5][title]" size="30" type="text" value="Hello World" />', + text_field("post", "title", "index" => 5, 'id' => nil) + ) + assert_dom_equal( + '<textarea cols="40" name="post[5][body]" rows="20">Back to the hill and over it again!</textarea>', + text_area("post", "body", "index" => 5, 'id' => nil) + ) + assert_dom_equal( + '<input name="post[5][secret]" type="hidden" value="0" /><input checked="checked" name="post[5][secret]" type="checkbox" value="1" />', + check_box("post", "secret", "index" => 5, 'id' => nil) + ) + assert_dom_equal( + text_field("post", "title", "index" => 5, 'id' => nil), + text_field("post", "title", :index => 5, :id => nil) + ) + assert_dom_equal( + text_area("post", "body", "index" => 5, 'id' => nil), + text_area("post", "body", :index => 5, :id => nil) + ) + assert_dom_equal( + check_box("post", "secret", "index" => 5, 'id' => nil), + check_box("post", "secret", :index => 5, :id => nil) + ) + end + def test_auto_index pid = @post.id assert_dom_equal( @@ -445,10 +533,33 @@ class FormHelperTest < ActionView::TestCase ) end + def test_auto_index_with_nil_id + pid = @post.id + assert_dom_equal( + "<input name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", + text_field("post[]","title", :id => nil) + ) + assert_dom_equal( + "<textarea cols=\"40\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>", + text_area("post[]", "body", :id => nil) + ) + assert_dom_equal( + "<input name=\"post[#{pid}][secret]\" type=\"hidden\" value=\"0\" /><input checked=\"checked\" name=\"post[#{pid}][secret]\" type=\"checkbox\" value=\"1\" />", + check_box("post[]", "secret", :id => nil) + ) + assert_dom_equal( +"<input checked=\"checked\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />", + radio_button("post[]", "title", "Hello World", :id => nil) + ) + assert_dom_equal("<input name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />", + radio_button("post[]", "title", "Goodbye World", :id => nil) + ) + end + def test_form_for assert_deprecated do form_for(:post, @post, :html => { :id => 'create-post' }) do |f| - concat f.label(:title) + concat f.label(:title) { "The Title" } concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) @@ -458,7 +569,7 @@ class FormHelperTest < ActionView::TestCase expected = "<form action='http://www.example.com' id='create-post' method='post'>" + - "<label for='post_title'>Title</label>" + + "<label for='post_title'>The Title</label>" + "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + @@ -485,7 +596,7 @@ class FormHelperTest < ActionView::TestCase "<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" + "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + "<input name='other_name[secret]' value='0' type='hidden' />" + - "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" + + "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" + "<input name='commit' id='other_name_submit' value='Create post' type='submit' /></form>" assert_dom_equal expected, output_buffer diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index 98503c32fd..19b73aa810 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -767,6 +767,62 @@ class FormOptionsHelperTest < ActionView::TestCase html end + def test_options_for_select_with_element_attributes + assert_dom_equal( + "<option value=\"<Denmark>\" class=\"bold\"><Denmark></option>\n<option value=\"USA\" onclick=\"alert('Hello World');\">USA</option>\n<option value=\"Sweden\">Sweden</option>\n<option value=\"Germany\">Germany</option>", + options_for_select([ [ "<Denmark>", { :class => 'bold' } ], [ "USA", { :onclick => "alert('Hello World');" } ], [ "Sweden" ], "Germany" ]) + ) + end + + def test_options_for_select_with_element_attributes_and_selection + assert_dom_equal( + "<option value=\"<Denmark>\"><Denmark></option>\n<option value=\"USA\" class=\"bold\" selected=\"selected\">USA</option>\n<option value=\"Sweden\">Sweden</option>", + options_for_select([ "<Denmark>", [ "USA", { :class => 'bold' } ], "Sweden" ], "USA") + ) + end + + def test_options_for_select_with_element_attributes_and_selection_array + assert_dom_equal( + "<option value=\"<Denmark>\"><Denmark></option>\n<option value=\"USA\" class=\"bold\" selected=\"selected\">USA</option>\n<option value=\"Sweden\" selected=\"selected\">Sweden</option>", + options_for_select([ "<Denmark>", [ "USA", { :class => 'bold' } ], "Sweden" ], [ "USA", "Sweden" ]) + ) + end + + def test_option_html_attributes_from_without_hash + assert_dom_equal( + "", + option_html_attributes([ 'foo', 'bar' ]) + ) + end + + def test_option_html_attributes_with_single_element_hash + assert_dom_equal( + " class=\"fancy\"", + option_html_attributes([ 'foo', 'bar', { :class => 'fancy' } ]) + ) + end + + def test_option_html_attributes_with_multiple_element_hash + assert_dom_equal( + " class=\"fancy\" onclick=\"alert('Hello World');\"", + option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ]) + ) + end + + def test_option_html_attributes_with_multiple_hashes + assert_dom_equal( + " class=\"fancy\" onclick=\"alert('Hello World');\"", + option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ]) + ) + end + + def test_option_html_attributes_with_special_characters + assert_dom_equal( + " onclick=\"alert("<code>")\"", + option_html_attributes([ 'foo', 'bar', { :onclick => %(alert("<code>")) } ]) + ) + end + def test_grouped_collection_select @continents = [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index abb0e1df93..1c095b621e 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -288,6 +288,20 @@ class FormTagHelperTest < ActionView::TestCase assert_match VALID_HTML_ID, label_elem['for'] end + def test_label_tag_with_block + assert_dom_equal('<label>Blocked</label>', label_tag { "Blocked" }) + end + + def test_label_tag_with_block_and_argument + output = label_tag("clock") { "Grandfather" } + assert_dom_equal('<label for="clock">Grandfather</label>', output) + end + + def test_label_tag_with_block_and_argument_and_options + output = label_tag("clock", :id => "label_clock") { "Grandfather" } + assert_dom_equal('<label for="clock" id="label_clock">Grandfather</label>', output) + end + def test_boolean_options assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes") assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil) diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index c9a50da418..d0212024ae 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -111,6 +111,7 @@ module RenderTestCases assert_match %r!method.*doesnt_exist!, e.message assert_equal "", e.sub_template_message assert_equal "1", e.line_number + assert_equal "1: <%= doesnt_exist %>", e.annoted_source_code.strip assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index a7ee15a7f6..bbcc345e4b 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -124,11 +124,12 @@ module ActiveModel @previously_changed end + # Map of change <tt>attr => original value</tt>. + def changed_attributes + @changed_attributes ||= {} + end + private - # Map of change <tt>attr => original value</tt>. - def changed_attributes - @changed_attributes ||= {} - end # Handle <tt>*_changed?</tt> for +method_missing+. def attribute_changed?(attr) diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index df7026b3ec..934af2b8a8 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -90,7 +90,7 @@ module ActiveModel @builder = options[:builder] @builder.instruct! unless options[:skip_instruct] - root = (options[:root] || @serializable.class.model_name.singular).to_s + root = (options[:root] || @serializable.class.model_name.element).to_s root = ActiveSupport::XmlMini.rename_key(root, options) args = [root] diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 7c705b8899..f472f50f9b 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -46,6 +46,10 @@ module ActiveModel included do extend ActiveModel::Translation + + extend HelperMethods + include HelperMethods + define_callbacks :validate, :scope => :name attr_accessor :validation_context @@ -138,12 +142,6 @@ module ActiveModel def attribute_method?(attribute) method_defined?(attribute) end - private - - def _merge_attributes(attr_names) - options = attr_names.extract_options! - options.merge(:attributes => attr_names.flatten) - end end # Returns the Errors object that holds all information about attribute error messages. diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index fbd622eb6d..26b12b504b 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -21,7 +21,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example: # # class Person < ActiveRecord::Base diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 8041d4b61f..51445343f2 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -12,7 +12,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example: # # Model: diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 2f51edfa9a..2ee78f5dd2 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -12,7 +12,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Validates that the value of the specified attribute is not in a particular enumerable object. # # class Person < ActiveRecord::Base diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index 9a9e7eca4d..c34c860d4d 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -24,7 +24,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Validates whether the value of the specified attribute is of the correct form, going by the regular expression provided. # You can require that the attribute matches the regular expression: # diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 96dc8b6e15..446646d247 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -12,7 +12,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Validates whether the value of the specified attribute is available in a particular enumerable object. # # class Person < ActiveRecord::Base diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 95da3e93ea..d7218f4f52 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -51,7 +51,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time: # diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index f974999bef..716010e88b 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -70,7 +70,7 @@ module ActiveModel end - module ClassMethods + module HelperMethods # Validates whether the value of the specified attribute is numeric by trying to convert it to # a float with Kernel.Float (if <tt>only_integer</tt> is false) or applying it to the regular expression # <tt>/\A[\+\-]?\d+\Z/</tt> (if <tt>only_integer</tt> is set to true). diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index 4a71cf79b5..b319f4834b 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -8,7 +8,7 @@ module ActiveModel end end - module ClassMethods + module HelperMethods # Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example: # # class Person < ActiveRecord::Base diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 83d3ea80d6..6dbde5bfad 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -1,5 +1,13 @@ module ActiveModel module Validations + module HelperMethods + private + def _merge_attributes(attr_names) + options = attr_names.extract_options! + options.merge(:attributes => attr_names.flatten) + end + end + module ClassMethods # Passes the record off to the class or classes specified and allows them @@ -75,5 +83,50 @@ module ActiveModel end end end + + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. + # + # class Person + # include ActiveModel::Validations + # + # validates :instance_validations + # + # def instance_validations + # validates_with MyValidator + # end + # end + # + # Please consult the class method documentation for more information on + # creating your own validator. + # + # You may also pass it multiple classes, like so: + # + # class Person + # include ActiveModel::Validations + # + # validates :instance_validations, :on => :create + # + # def instance_validations + # validates_with MyValidator, MyOtherValidator + # end + # end + # + # Standard configuration options (:on, :if and :unless), which are + # available on the class version of validates_with, should instead be + # placed on the <tt>validates</tt> method as these are applied and tested + # in the callback + # + # If you pass any additional configuration options, they will be passed + # to the class and available as <tt>options</tt>, please refer to the + # class version of this method for more information + # + def validates_with(*args, &block) + options = args.extract_options! + args.each do |klass| + validator = klass.new(options, &block) + validator.validate(self) + end + end end end
\ No newline at end of file diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 906d239bcc..56179c1a6c 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -88,6 +88,9 @@ module ActiveModel #:nodoc: # klass.send :attr_accessor, :custom_attribute # end # end + # + # This setup method is only called when used with validation macros or the + # class level <tt>validates_with</tt> method. # class Validator attr_reader :options diff --git a/activemodel/test/cases/serializeration/xml_serialization_test.rb b/activemodel/test/cases/serializeration/xml_serialization_test.rb index 3ba826a8d0..4e8e4efa25 100644 --- a/activemodel/test/cases/serializeration/xml_serialization_test.rb +++ b/activemodel/test/cases/serializeration/xml_serialization_test.rb @@ -37,8 +37,8 @@ class XmlSerializationTest < ActiveModel::TestCase test "should serialize namespaced root" do @xml = Admin::Contact.new(@contact.attributes).to_xml - assert_match %r{^<admin-contact>}, @xml - assert_match %r{</admin-contact>$}, @xml + assert_match %r{^<contact>}, @xml + assert_match %r{</contact>$}, @xml end test "should serialize default root with namespace" do diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index d15fb4a524..b85e491a9c 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -45,7 +45,7 @@ class ValidatesTest < ActiveModel::TestCase Person.validates :karma, :presence => true, :email => true, :if => :condition_is_true person = Person.new person.valid? - assert ["can't be blank", "is not an email"], person.errors[:karma].sort + assert_equal ["can't be blank", "is not an email"], person.errors[:karma].sort end def test_validates_with_unless_shared_conditions @@ -111,4 +111,4 @@ class ValidatesTest < ActiveModel::TestCase person.valid? assert_equal ['Local validator please'], person.errors[:title] end -end
\ No newline at end of file +end diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 578ffc27dd..af195af080 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -4,6 +4,7 @@ require 'cases/helper' require 'models/topic' require 'models/reply' require 'models/custom_reader' +require 'models/automobile' class ValidationsTest < ActiveModel::TestCase @@ -227,7 +228,7 @@ class ValidationsTest < ActiveModel::TestCase t = Topic.new assert t.invalid? - assert ["NO BLANKS HERE"], t.errors[:title] + assert_equal ["NO BLANKS HERE"], t.errors[:title] end def test_list_of_validators_for_model @@ -252,4 +253,16 @@ class ValidationsTest < ActiveModel::TestCase Topic.validates_length_of :title, :minimum => 10 assert_equal 10, Topic.validators_on(:title).first.options[:minimum] end + + def test_validations_on_the_instance_level + auto = Automobile.new + + assert auto.invalid? + assert_equal 2, auto.errors.size + + auto.make = 'Toyota' + auto.model = 'Corolla' + + assert auto.valid? + end end diff --git a/activemodel/test/models/automobile.rb b/activemodel/test/models/automobile.rb new file mode 100644 index 0000000000..021ea61c80 --- /dev/null +++ b/activemodel/test/models/automobile.rb @@ -0,0 +1,12 @@ +class Automobile + include ActiveModel::Validations + + validate :validations + + attr_accessor :make, :model + + def validations + validates_presence_of :make + validates_length_of :model, :within => 2..10 + end +end
\ No newline at end of file diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 3f42fa34ef..d8f68d7c28 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,15 @@ *Rails 3.0.0 [beta 4/release candidate] (unreleased)* +* Add index length support for MySQL. #1852 [Emili Parreno, Pratik Naik] + + Example: + + add_index(:accounts, :name, :name => 'by_name', :length => 10) + => CREATE INDEX by_name ON accounts(name(10)) + + add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15}) + => CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + * find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune] * New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9d39a0c49b..f23d881c7b 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1399,7 +1399,7 @@ module ActiveRecord primary_key = reflection.source_reflection.primary_key_name send(through.name).select("DISTINCT #{through.quoted_table_name}.#{primary_key}").map!(&:"#{primary_key}") else - send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").map!(&:id) + send(reflection.name).select("#{reflection.quoted_table_name}.#{reflection.klass.primary_key}").except(:includes).map!(&:id) end end end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index dd44bd8d51..cf4594ad7f 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -8,7 +8,7 @@ module ActiveRecord include AttributeMethods::Write included do - if self < Timestamp + if self < ::ActiveRecord::Timestamp raise "You cannot include Dirty after Timestamp" end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 411330dda2..82d94b848a 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -6,7 +6,7 @@ module ActiveRecord # Returns this record's primary key value wrapped in an Array # or nil if the record is a new_record? def to_key - new_record? ? nil : [ send(self.class.primary_key) ] + new_record? ? nil : [ id ] end module ClassMethods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 650a91b385..c02af328c1 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -668,7 +668,6 @@ module ActiveRecord #:nodoc: name = "#{full_table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}" end - @quoted_table_name = nil set_table_name(name) name end @@ -702,6 +701,7 @@ module ActiveRecord #:nodoc: # set_table_name "project" # end def set_table_name(value = nil, &block) + @quoted_table_name = nil define_attr_method :table_name, value, &block end alias :table_name= :set_table_name @@ -1190,7 +1190,11 @@ module ActiveRecord #:nodoc: # default_scope order('last_name, first_name') # end def default_scope(options = {}) - self.default_scoping << construct_finder_arel(options) + self.default_scoping << construct_finder_arel(options, default_scoping.pop) + end + + def clear_default_scope + self.default_scoping.clear end def scoped_methods #:nodoc: @@ -1463,6 +1467,7 @@ module ActiveRecord #:nodoc: @attributes_cache = {} @new_record = true ensure_proper_type + @changed_attributes = other.changed_attributes.dup if scope = self.class.send(:current_scoped_methods) create_with = scope.scope_for_create diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 6c477e48ce..7d58bc2adf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -258,7 +258,7 @@ module ActiveRecord end end - class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc: end # Abstract representation of a column definition. Instances of this type @@ -494,7 +494,7 @@ module ActiveRecord end %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{column_type}(*args) # def string(*args) options = args.extract_options! # options = args.extract_options! column_names = args # column_names = args @@ -694,7 +694,7 @@ module ActiveRecord # t.string(:goat) # t.string(:goat, :sheep) %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{column_type}(*args) # def string(*args) options = args.extract_options! # options = args.extract_options! column_names = args # column_names = args 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 e8cba1bd41..1255ef09be 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -256,18 +256,32 @@ module ActiveRecord # name. # # ===== Examples + # # ====== Creating a simple index # add_index(:suppliers, :name) # generates # CREATE INDEX suppliers_name_index ON suppliers(name) + # # ====== Creating a unique index # add_index(:accounts, [:branch_id, :party_id], :unique => true) # generates # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id) + # # ====== Creating a named index # add_index(:accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party') # generates # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id) + # + # ====== Creating an index with specific key length + # add_index(:accounts, :name, :name => 'by_name', :length => 10) + # generates + # CREATE INDEX by_name ON accounts(name(10)) + # + # add_index(:accounts, [:name, :surname], :name => 'by_name_surname', :length => {:name => 10, :surname => 15}) + # generates + # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) + # + # Note: SQLite doesn't support index length def add_index(table_name, column_name, options = {}) column_names = Array.wrap(column_name) index_name = index_name(table_name, :column => column_names) @@ -278,7 +292,9 @@ module ActiveRecord else index_type = options end - quoted_column_names = column_names.map { |e| quote_column_name(e) }.join(", ") + + quoted_column_names = quoted_columns_for_index(column_names, options).join(", ") + execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{quoted_column_names})" end @@ -430,6 +446,11 @@ module ActiveRecord end protected + # Overridden by the mysql adapter for supporting index lengths + def quoted_columns_for_index(column_names, options = {}) + column_names.map {|name| quote_column_name(name) } + end + def options_include_default?(options) options.include?(:default) && !(options[:null] == false && options[:default].nil?) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 55d9d20bb5..e12924e63f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -15,26 +15,26 @@ module MysqlCompat #:nodoc: # Ruby driver has a version string and returns null values in each_hash # C driver >= 2.7 returns null values in each_hash if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700) - target.class_eval <<-'end_eval' - def all_hashes # def all_hashes - rows = [] # rows = [] - each_hash { |row| rows << row } # each_hash { |row| rows << row } - rows # rows - end # end + target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1 + def all_hashes # def all_hashes + rows = [] # rows = [] + each_hash { |row| rows << row } # each_hash { |row| rows << row } + rows # rows + end # end end_eval # adapters before 2.7 don't have a version constant # and don't return null values in each_hash else - target.class_eval <<-'end_eval' - def all_hashes # def all_hashes - rows = [] # rows = [] - all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| - fields[f.name] = nil; fields # fields[f.name] = nil; fields - } # } - each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } - rows # rows - end # end + target.class_eval <<-'end_eval', __FILE__, __LINE__ + 1 + def all_hashes # def all_hashes + rows = [] # rows = [] + all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f| + fields[f.name] = nil; fields # fields[f.name] = nil; fields + } # } + each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) } + rows # rows + end # end end_eval end @@ -461,10 +461,11 @@ module ActiveRecord if current_index != row[2] next if row[2] == "PRIMARY" # skip the primary key current_index = row[2] - indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", []) + indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], []) end indexes.last.columns << row[4] + indexes.last.lengths << row[7] end result.free indexes @@ -512,7 +513,7 @@ module ActiveRecord def change_column(table_name, column_name, type, options = {}) #:nodoc: column = column_for(table_name, column_name) - unless options_include_default?(options) + if has_default?(type) && !options_include_default?(options) options[:default] = column.default end @@ -594,6 +595,18 @@ module ActiveRecord end protected + def quoted_columns_for_index(column_names, options = {}) + length = options[:length] if options.is_a?(Hash) + + quoted_column_names = case length + when Hash + column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } + when Fixnum + column_names.map {|name| "#{quote_column_name(name)}(#{length})"} + else + column_names.map {|name| quote_column_name(name) } + end + end def translate_exception(exception, message) return super unless exception.respond_to?(:errno) @@ -662,6 +675,10 @@ module ActiveRecord end column end + + def has_default?(sql_type) + sql_type =~ :binary || sql_type == :text #mysql forbids defaults on blob and text columns + end end end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4bf33c3856..8099aaa7f7 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -891,6 +891,7 @@ module ActiveRecord instances.size == 1 ? instances.first : instances end + private table_name end end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 71057efa15..ceb0902fde 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -113,12 +113,11 @@ 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" - ) + table = self.class.arel_table + predicate = table[self.class.primary_key].eq(id) + predicate = predicate.and(table[self.class.locking_column].eq(previous_value)) + + affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index aeed52e72a..3d8f4a030b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -99,11 +99,7 @@ module ActiveRecord block_given? ? relation.extending(Module.new(&block)) : relation end - singleton_class.instance_eval do - define_method name do |*args| - scopes[name].call(*args) - end - end + singleton_class.send :define_method, name, &scopes[name] end def named_scope(*args, &block) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 898df0a67a..a32fb7d399 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -15,6 +15,12 @@ module ActiveRecord config.generators.orm :active_record, :migration => true, :timestamps => true + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::QueryCache" + + config.app_middleware.insert_after "::ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement" + rake_tasks do load "active_record/railties/databases.rake" end @@ -58,16 +64,9 @@ module ActiveRecord end end - # Setup database middleware after initializers have run - initializer "active_record.initialize_database_middleware", :after => "action_controller.set_configs" do |app| - middleware = app.config.middleware - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::QueryCache - middleware.insert_after "::ActionDispatch::Callbacks", ActiveRecord::ConnectionAdapters::ConnectionManagement - end - initializer "active_record.set_dispatch_hooks", :before => :set_clear_dependencies_hook do |app| - ActiveSupport.on_load(:active_record) do - unless app.config.cache_classes + unless app.config.cache_classes + ActiveSupport.on_load(:active_record) do ActionDispatch::Callbacks.after do ActiveRecord::Base.reset_subclasses ActiveRecord::Base.clear_reloadable_connections! diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 1c61e7d450..412be895c4 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -71,7 +71,12 @@ module ActiveRecord yield records break if records.size < batch_size - records = relation.where(primary_key.gt(records.last.id)).all + + if primary_key_offset = records.last.id + records = relation.where(primary_key.gt(primary_key_offset)).all + else + raise "Primary key not included in the custom select clause" + end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 858d298470..44baeb6c84 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -45,9 +45,8 @@ module ActiveRecord calculate(:count, column_name, options) end - # Calculates the average value on a given column. The value is returned as - # a float, or +nil+ if there's no row. See +calculate+ for examples with - # options. + # Calculates the average value on a given column. Returns +nil+ if there's + # no row. See +calculate+ for examples with options. # # Person.average('age') # => 35.8 def average(column_name, options = {}) @@ -241,9 +240,9 @@ module ActiveRecord def type_cast_calculated_value(value, column, operation = nil) if value.is_a?(String) || value.nil? case operation - when 'count' then value.to_i - when 'sum' then type_cast_using_column(value || '0', column) - when 'average' then value && (value.is_a?(Fixnum) ? value.to_f : value).to_d + when 'count' then value.to_i + when 'sum' then type_cast_using_column(value || '0', column) + when 'average' then value.try(:to_d) else type_cast_using_column(value, column) end else diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8d8bb659e1..6782554854 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -10,7 +10,7 @@ module ActiveRecord attr_accessor :"#{query_method}_values" next if [:where, :having].include?(query_method) - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(*args, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? @@ -22,7 +22,7 @@ module ActiveRecord end [:where, :having].each do |query_method| - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(*args, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? @@ -36,7 +36,7 @@ module ActiveRecord ActiveRecord::Relation::SINGLE_VALUE_METHODS.each do |query_method| attr_accessor :"#{query_method}_value" - class_eval <<-CEVAL, __FILE__ + class_eval <<-CEVAL, __FILE__, __LINE__ + 1 def #{query_method}(value = true, &block) new_relation = clone new_relation.send(:apply_modules, Module.new(&block)) if block_given? diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index c8e1b4f53a..cd54653581 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -178,6 +178,9 @@ HEADER statment_parts << (':name => ' + index.name.inspect) statment_parts << ':unique => true' if index.unique + index_lengths = index.lengths.compact if index.lengths.is_a?(Array) + statment_parts << (':length => ' + Hash[*index.columns.zip(index.lengths).flatten].inspect) if index_lengths.present? + ' ' + statment_parts.join(', ') end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 55c4236874..be64e00bd1 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -36,49 +36,40 @@ module ActiveRecord # The validation process on save can be skipped by passing false. The regular Base#save method is # replaced with this when the validations module is mixed in, which it is by default. - def save(options=nil) - return super if valid?(options) - false - end - - def save_without_validation! - save!(:validate => false) + def save(options={}) + perform_validations(options) ? super : false end # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false # if the record is not valid. - def save!(options = nil) - return super if valid?(options) - raise RecordInvalid.new(self) + def save!(options={}) + perform_validations(options) ? super : raise(RecordInvalid.new(self)) end # Runs all the specified validations and returns true if no errors were added otherwise false. - def valid?(options = nil) - perform_validation = case options - when NilClass - true - when Hash - options[:validate] != false - else - ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller - options - end + def valid?(context = nil) + context ||= (new_record? ? :create : :update) + super(context) - if perform_validation - errors.clear + deprecated_callback_method(:validate) + deprecated_callback_method(:"validate_on_#{context}") - self.validation_context = new_record? ? :create : :update - _run_validate_callbacks + errors.empty? + end - deprecated_callback_method(:validate) + protected - if new_record? - deprecated_callback_method(:validate_on_create) - else - deprecated_callback_method(:validate_on_update) - end + def perform_validations(options={}) + perform_validation = case options + when Hash + options[:validate] != false + else + ActiveSupport::Deprecation.warn "save(#{options}) is deprecated, please give save(:validate => #{options}) instead", caller + options + end - errors.empty? + if perform_validation + valid?(options.is_a?(Hash) ? options[:context] : nil) else true end diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb index 9aff538ce9..f4d123be15 100644 --- a/activerecord/test/cases/active_schema_test_mysql.rb +++ b/activerecord/test/cases/active_schema_test_mysql.rb @@ -15,6 +15,23 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end + def test_add_index + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)" + assert_equal expected, add_index(:people, :last_name, :length => nil) + + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))" + assert_equal expected, add_index(:people, :last_name, :length => 10) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15}) + + expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))" + assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10}) + end + def test_drop_table assert_equal "DROP TABLE `people`", drop_table(:people) end 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 e8db6d5dab..2beb3f8365 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -17,7 +17,7 @@ module Remembered module ClassMethods def remembered; @@remembered ||= []; end - def rand; @@remembered.rand; end + def random_element; @@remembered.random_element; end end end @@ -79,14 +79,14 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase [Circle, Square, Triangle, NonPolyOne, NonPolyTwo].map(&:create!) end 1.upto(NUM_SIMPLE_OBJS) do - PaintColor.create!(:non_poly_one_id => NonPolyOne.rand.id) - PaintTexture.create!(:non_poly_two_id => NonPolyTwo.rand.id) + PaintColor.create!(:non_poly_one_id => NonPolyOne.random_element.id) + PaintTexture.create!(:non_poly_two_id => NonPolyTwo.random_element.id) end 1.upto(NUM_SHAPE_EXPRESSIONS) do - shape_type = [Circle, Square, Triangle].rand - paint_type = [PaintColor, PaintTexture].rand - ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.rand.id, - :paint_type => paint_type.to_s, :paint_id => paint_type.rand.id) + shape_type = [Circle, Square, Triangle].random_element + paint_type = [PaintColor, PaintTexture].random_element + ShapeExpression.create!(:shape_type => shape_type.to_s, :shape_id => shape_type.random_element.id, + :paint_type => paint_type.to_s, :paint_id => paint_type.random_element.id) end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index b55b08bf9d..6e47967696 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -900,6 +900,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert !company.clients.loaded? end + def test_get_ids_ignores_include_option + assert_equal [readers(:michael_welcome).id], posts(:welcome).readers_with_person_ids + end + def test_get_ids_for_unloaded_finder_sql_associations_loads_them company = companies(:first_firm) assert !company.clients_using_sql.loaded? diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 8bdf8bcd55..dca72be4fd 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -289,7 +289,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_array_methods_called_by_method_missing - assert true, authors(:david).categories.any? { |category| category.name == 'General' } + assert authors(:david).categories.any? { |category| category.name == 'General' } assert_nothing_raised { authors(:david).categories.sort } end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 2995cc6f72..5cc5dff633 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -765,7 +765,7 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @ship.destroy @pirate.reload.catchphrase = "Arr" @pirate.save - assert 'Arr', @pirate.reload.catchphrase + assert_equal 'Arr', @pirate.reload.catchphrase end def test_should_automatically_save_the_associated_model @@ -885,7 +885,7 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @pirate.destroy @ship.reload.name = "The Vile Insanity" @ship.save - assert 'The Vile Insanity', @ship.reload.name + assert_equal 'The Vile Insanity', @ship.reload.name end def test_should_automatically_save_the_associated_model diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index bbc4e543d5..b7ae619787 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1793,6 +1793,18 @@ class BasicsTest < ActiveRecord::TestCase assert_equal "bar", k.table_name end + def test_quoted_table_name_after_set_table_name + klass = Class.new(ActiveRecord::Base) + + klass.set_table_name "foo" + assert_equal "foo", klass.table_name + assert_equal klass.connection.quote_table_name("foo"), klass.quoted_table_name + + klass.set_table_name "bar" + assert_equal "bar", klass.table_name + assert_equal klass.connection.quote_table_name("bar"), klass.quoted_table_name + end + def test_set_table_name_with_block k = Class.new( ActiveRecord::Base ) k.set_table_name { "ks" } diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 83deabb5b7..dcc49e12ca 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -17,6 +17,20 @@ class EachTest < ActiveRecord::TestCase end end + def test_each_should_raise_if_select_is_set_without_id + assert_raise(RuntimeError) do + Post.find_each(:select => :title, :batch_size => 1) { |post| post } + end + end + + def test_each_should_execute_if_id_is_in_select + assert_queries(4) do + Post.find_each(:select => "id, title, type", :batch_size => 2) do |post| + assert_kind_of Post, post + end + end + end + def test_each_should_raise_if_the_order_is_set assert_raise(RuntimeError) do Post.find_each(:order => "title") { |post| post } diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 28a1ae5feb..8473150338 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -20,8 +20,7 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_average_field value = Account.average(:credit_limit) - assert_kind_of BigDecimal, value - assert_equal BigDecimal.new('53.0'), value + assert_equal 53.0, value end def test_should_return_nil_as_average diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 7a17ef1ee0..3ea2948f62 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -338,6 +338,15 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.changed? end + def test_cloned_objects_should_not_copy_dirty_flag_from_creator + pirate = Pirate.create!(:catchphrase => "shiver me timbers") + pirate_clone = pirate.clone + pirate_clone.reset_catchphrase! + pirate.catchphrase = "I love Rum" + assert pirate.catchphrase_changed? + assert !pirate_clone.catchphrase_changed? + end + def test_reverted_changes_are_not_dirty phrase = "shiver me timbers" pirate = Pirate.create!(:catchphrase => phrase) diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index e78db8969d..c73ad50a71 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -722,10 +722,10 @@ class FinderTest < ActiveRecord::TestCase def test_find_all_by_one_attribute_with_options topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") - assert topics(:first), topics.last + assert_equal topics(:first), topics.last topics = Topic.find_all_by_content("Have a nice day", :order => "id") - assert topics(:first), topics.first + assert_equal topics(:first), topics.first end def test_find_all_by_array_attribute diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 3ce23209cc..8008b86f81 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -256,6 +256,11 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase def test_fixtures_from_root_yml_without_instantiation assert !defined?(@unknown), "@unknown is not defined" end + + def test_visibility_of_accessor_method + assert_equal false, respond_to?(:topics, false), "should be private method" + assert_equal true, respond_to?(:topics, true), "confirm to respond surely" + end def test_accessor_methods assert_equal "The First Topic", topics(:first).title diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index a3b496a0e6..3a6354ec6d 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -587,6 +587,18 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase end end +class ClearDefaultScopeTest < ActiveRecord::TestCase + fixtures :developers + + def test_should_clear_default_scope + klass = Class.new(DeveloperCalledDavid) + klass.__send__ :clear_default_scope + expected = Developer.all.collect { |dev| dev.name } + actual = klass.all.collect { |dev| dev.name } + assert_equal expected, actual + end +end + class DefaultScopingTest < ActiveRecord::TestCase fixtures :developers, :posts @@ -615,15 +627,30 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scoping_with_inheritance # Inherit a class having a default scope and define a new default scope klass = Class.new(DeveloperOrderedBySalary) - klass.send :default_scope, {} + klass.send :default_scope, :limit => 1 # Scopes added on children should append to parent scope - assert klass.scoped.order_values.blank? + assert_equal 1, klass.scoped.limit_value + assert_equal ['salary DESC'], klass.scoped.order_values # Parent should still have the original scope + assert_equal nil, DeveloperOrderedBySalary.scoped.limit_value assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values end + def test_default_scope_called_twice_merges_conditions + Developer.destroy_all + Developer.create!(:name => "David", :salary => 80000) + Developer.create!(:name => "David", :salary => 100000) + Developer.create!(:name => "Brian", :salary => 100000) + + klass = Class.new(Developer) + klass.__send__ :default_scope, :conditions => { :name => "David" } + klass.__send__ :default_scope, :conditions => { :salary => 100000 } + assert_equal 1, klass.count + assert_equal "David", klass.first.name + assert_equal 100000, klass.first.salary + end def test_method_scope expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary } diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index a3d1ceaa1f..768a44f6df 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -92,6 +92,14 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { Person.connection.remove_index("people", "last_name_and_first_name") } assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) } assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => 10) } + assert_nothing_raised { Person.connection.remove_index("people", "last_name") } + assert_nothing_raised { Person.connection.add_index("people", ["last_name"], :length => {:last_name => 10}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => 10) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } + assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"], :length => {:last_name => 10, :first_name => 20}) } + assert_nothing_raised { Person.connection.remove_index("people", ["last_name", "first_name"]) } end # quoting @@ -852,6 +860,18 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "Tester", Person.new.first_name end + unless current_adapter?(:PostgreSQLAdapter) + def test_change_column_type_default_should_change + old_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + assert !old_columns.find { |c| c.name == 'data' } + + assert_nothing_raised do + Person.connection.add_column "people", "data", :string, :default => '' + Person.connection.change_column "people", "data", :binary + end + end + end + def test_change_column_quotes_column_names Person.connection.create_table :testings do |t| t.column :select, :string diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index e4cafad11e..2007f5492f 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -142,6 +142,11 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal authors(:david).posts & Post.containing_the_letter_a, authors(:david).posts.containing_the_letter_a end + def test_named_scope_with_STI + assert_equal 3,Post.containing_the_letter_a.count + assert_equal 1,SpecialPost.containing_the_letter_a.count + end + def test_has_many_through_associations_have_access_to_named_scopes assert_not_equal Comment.containing_the_letter_e, authors(:david).comments assert !Comment.containing_the_letter_e.empty? @@ -296,7 +301,7 @@ class NamedScopeTest < ActiveRecord::TestCase end def test_rand_should_select_a_random_object_from_proxy - assert_kind_of Topic, Topic.approved.rand + assert_kind_of Topic, Topic.approved.random_element end def test_should_use_where_in_query_for_named_scope diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index e1fb911cc9..3a34df2426 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -62,6 +62,23 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal ["is Wrong Update"], r.errors[:title], "A reply with a bad content should contain an error" end + def test_error_on_given_context + r = WrongReply.new + assert !r.valid?(:special_case) + assert "Invalid", r.errors[:title].join + + r.title = "secret" + r.content = "Good" + assert r.valid?(:special_case) + + r.title = nil + assert !r.save(:context => :special_case) + assert "Invalid", r.errors[:title].join + + r.title = "secret" + assert r.save(:context => :special_case) + end + def test_invalid_record_exception assert_raise(ActiveRecord::RecordInvalid) { WrongReply.create! } assert_raise(ActiveRecord::RecordInvalid) { WrongReply.new.save! } @@ -135,12 +152,6 @@ class ValidationsTest < ActiveRecord::TestCase end end - def test_create_without_validation_bang - count = WrongReply.count - assert_nothing_raised { WrongReply.new.save_without_validation! } - assert count+1, WrongReply.count - end - def test_validates_acceptance_of_with_non_existant_table Object.const_set :IncorporealModel, Class.new(ActiveRecord::Base) diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index d092c4bf09..dd06822cfd 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -69,6 +69,7 @@ class Post < ActiveRecord::Base has_many :authors, :through => :categorizations has_many :readers + has_many :readers_with_person, :include => :person, :class_name => "Reader" has_many :people, :through => :readers has_many :people_with_callbacks, :source=>:person, :through => :readers, :before_add => lambda {|owner, reader| log(:added, :before, reader.first_name) }, diff --git a/activerecord/test/models/reply.rb b/activerecord/test/models/reply.rb index 264a49b465..6cc9ee038a 100644 --- a/activerecord/test/models/reply.rb +++ b/activerecord/test/models/reply.rb @@ -17,6 +17,7 @@ class WrongReply < Reply validate :check_empty_title validate :check_content_mismatch, :on => :create validate :check_wrong_update, :on => :update + validate :check_title_is_secret, :on => :special_case def check_empty_title errors[:title] << "Empty" unless attribute_present?("title") @@ -39,6 +40,10 @@ class WrongReply < Reply def check_wrong_update errors[:title] << "is Wrong Update" if attribute_present?("title") && title == "Wrong Update" end + + def check_title_is_secret + errors[:title] << "Invalid" unless title == "secret" + end end class SillyReply < Reply diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb index ad994214f6..b976844c1c 100644 --- a/activeresource/lib/active_resource/base.rb +++ b/activeresource/lib/active_resource/base.rb @@ -551,11 +551,9 @@ module ActiveResource @headers ||= {} end - # Do not include any modules in the default element name. This makes it easier to seclude ARes objects - # in a separate namespace without having to set element_name repeatedly. - attr_accessor_with_default(:element_name) { ActiveSupport::Inflector.underscore(to_s.split("::").last) } #:nodoc: - + attr_accessor_with_default(:element_name) { model_name.element } #:nodoc: attr_accessor_with_default(:collection_name) { ActiveSupport::Inflector.pluralize(element_name) } #:nodoc: + attr_accessor_with_default(:primary_key, 'id') #:nodoc: # Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.xml</tt>) @@ -1295,6 +1293,14 @@ module ActiveResource end end + def to_json(options={}) + super({ :root => self.class.element_name }.merge(options)) + end + + def to_xml(options={}) + super({ :root => self.class.element_name }.merge(options)) + end + protected def connection(refresh = false) self.class.connection(refresh) diff --git a/activeresource/lib/active_resource/schema.rb b/activeresource/lib/active_resource/schema.rb index 8368b652c2..5758ac9502 100644 --- a/activeresource/lib/active_resource/schema.rb +++ b/activeresource/lib/active_resource/schema.rb @@ -42,7 +42,7 @@ module ActiveResource # :nodoc: # TODO: We should eventually support all of these: # %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |attr_type| KNOWN_ATTRIBUTE_TYPES.each do |attr_type| - class_eval <<-EOV + class_eval <<-EOV, __FILE__, __LINE__ + 1 def #{attr_type.to_s}(*args) options = args.extract_options! attr_names = args diff --git a/activeresource/test/cases/base_test.rb b/activeresource/test/cases/base_test.rb index 2ed7e1c95f..4e21e84596 100644 --- a/activeresource/test/cases/base_test.rb +++ b/activeresource/test/cases/base_test.rb @@ -2,6 +2,7 @@ require 'abstract_unit' require "fixtures/person" require "fixtures/customer" require "fixtures/street_address" +require "fixtures/sound" require "fixtures/beast" require "fixtures/proxy" require 'active_support/json' @@ -563,6 +564,10 @@ class BaseTest < Test::Unit::TestCase assert_equal '/people/Greg/addresses/1.xml', StreetAddress.element_path(1, 'person_id' => 'Greg') end + def test_module_element_path + assert_equal '/sounds/1.xml', Asset::Sound.element_path(1) + end + def test_custom_element_path_with_redefined_to_param Person.module_eval do alias_method :original_to_param_element_path, :to_param @@ -1009,25 +1014,66 @@ class BaseTest < Test::Unit::TestCase def test_to_xml matz = Person.find(1) - xml = matz.encode + encode = matz.encode + xml = matz.to_xml + + assert encode, xml assert xml.include?('<?xml version="1.0" encoding="UTF-8"?>') assert xml.include?('<name>Matz</name>') assert xml.include?('<id type="integer">1</id>') end + def test_to_xml_with_element_name + old_elem_name = Person.element_name + matz = Person.find(1) + Person.element_name = 'ruby_creator' + encode = matz.encode + xml = matz.to_xml + + assert encode, xml + assert xml.include?('<?xml version="1.0" encoding="UTF-8"?>') + assert xml.include?('<ruby-creator>') + assert xml.include?('<name>Matz</name>') + assert xml.include?('<id type="integer">1</id>') + assert xml.include?('</ruby-creator>') + ensure + Person.element_name = old_elem_name + end + def test_to_json Person.include_root_in_json = true Person.format = :json joe = Person.find(6) - json = joe.encode + encode = joe.encode + json = joe.to_json Person.format = :xml + assert encode, json assert_match %r{^\{"person":\{"person":\{}, json assert_match %r{"id":6}, json assert_match %r{"name":"Joe"}, json assert_match %r{\}\}\}$}, json end + def test_to_json_with_element_name + old_elem_name = Person.element_name + Person.include_root_in_json = true + Person.format = :json + joe = Person.find(6) + Person.element_name = 'ruby_creator' + encode = joe.encode + json = joe.to_json + Person.format = :xml + + assert encode, json + assert_match %r{^\{"ruby_creator":\{"person":\{}, json + assert_match %r{"id":6}, json + assert_match %r{"name":"Joe"}, json + assert_match %r{\}\}\}$}, json + ensure + Person.element_name = old_elem_name + end + def test_to_param_quacks_like_active_record new_person = Person.new assert_nil new_person.to_param diff --git a/activeresource/test/fixtures/sound.rb b/activeresource/test/fixtures/sound.rb new file mode 100644 index 0000000000..5c0ef5d55c --- /dev/null +++ b/activeresource/test/fixtures/sound.rb @@ -0,0 +1,5 @@ +module Asset + class Sound < ActiveResource::Base + self.site = "http://37s.sunrise.i:3000" + end +end diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 6146cc6a97..7afd9926b5 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Rails 3.0.0 [beta 4/release candidate] (unreleased)* +* Defines prev_(month|year) in Date and Time, and deprecates last_(month|year). [fxn] + * Aliases Date#sunday to Date#end_of_week. [fxn] * Backports Date#>> from 1.9 so that calculations do the right thing around the calendar reform. [fxn] diff --git a/activesupport/lib/active_support/core_ext/array/random_access.rb b/activesupport/lib/active_support/core_ext/array/random_access.rb index 343003f6f7..5338836b29 100644 --- a/activesupport/lib/active_support/core_ext/array/random_access.rb +++ b/activesupport/lib/active_support/core_ext/array/random_access.rb @@ -1,6 +1,16 @@ class Array + # This method is deprecated because it masks Kernel#rand within the Array class itself, + # which may be used by a 3rd party library extending Array in turn. See + # + # https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4555 + # + def rand # :nodoc: + ActiveSupport::Deprecation.warn "Array#rand is deprecated, use random_element instead", caller + random_element + end + # Returns a random element from the array. - def rand + def random_element self[Kernel.rand(length)] end end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 3038729d34..755d96ce91 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -2,6 +2,7 @@ require 'date' require 'active_support/duration' require 'active_support/core_ext/time/zones' require 'active_support/core_ext/object/acts_like' +require 'active_support/deprecation' class Date if RUBY_VERSION < '1.9' @@ -146,20 +147,30 @@ class Date advance(:years => years) end - # Short-hand for years_ago(1) - def last_year - years_ago(1) + def last_year # :nodoc: + ActiveSupport::Deprecation.warn("Date#last_year has been deprecated, please use Date#prev_year instead", caller) + prev_year end + # Shorthand for years_ago(1) + def prev_year + years_ago(1) + end unless method_defined?(:prev_year) + # Short-hand for years_since(1) def next_year years_since(1) end unless method_defined?(:next_year) + def last_month # :nodoc: + ActiveSupport::Deprecation.warn("Date#last_month has been deprecated, please use Date#prev_month instead", caller) + prev_month + end + # Short-hand for months_ago(1) - def last_month + def prev_month months_ago(1) - end + end unless method_defined?(:prev_month) # Short-hand for months_since(1) def next_month diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index 22749229a3..c4994ca2ee 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/class/attribute_accessors' # Adds the 'around_level' method to Logger. class Logger #:nodoc: def self.define_around_helper(level) - module_eval <<-end_eval + module_eval <<-end_eval, __FILE__, __LINE__ + 1 def around_#{level}(before_message, after_message, &block) # def around_debug(before_message, after_message, &block) self.#{level}(before_message) # self.debug(before_message) return_value = block.call(self) # return_value = block.call(self) diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index dd38b7d261..3209cf7f11 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -1,3 +1,5 @@ +require 'active_support/option_merger' + class Object # An elegant way to factor duplication out of options passed to a series of # method calls. Each method called in the block, with the block variable as diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 2b47ecd543..e27b08ec2e 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -2,6 +2,7 @@ require 'active_support/duration' require 'active_support/core_ext/date/acts_like' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date_time/conversions' +require 'active_support/deprecation' class Time COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] @@ -132,8 +133,13 @@ class Time advance(:years => years) end + def last_year # :nodoc: + ActiveSupport::Deprecation.warn("Time#last_year has been deprecated, please use Time#prev_year instead", caller) + prev_year + end + # Short-hand for years_ago(1) - def last_year + def prev_year years_ago(1) end @@ -142,9 +148,13 @@ class Time years_since(1) end + def last_month # :nodoc: + ActiveSupport::Deprecation.warn("Time#last_month has been deprecated, please use Time#prev_month instead", caller) + prev_month + end # Short-hand for months_ago(1) - def last_month + def prev_month months_ago(1) end diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index 642a4c105c..3664431a28 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -2,16 +2,26 @@ module ActiveSupport @load_hooks = Hash.new {|h,k| h[k] = [] } @loaded = {} - def self.on_load(name, &block) + def self.on_load(name, options = {}, &block) if base = @loaded[name] - base.instance_eval(&block) + execute_hook(base, options, block) else - @load_hooks[name] << block + @load_hooks[name] << [block, options] + end + end + + def self.execute_hook(base, options, block) + if options[:yield] + block.call(base) + else + base.instance_eval(&block) end end def self.run_load_hooks(name, base = Object) - @load_hooks[name].each { |hook| base.instance_eval(&hook) } @loaded[name] = base + @load_hooks[name].each do |hook, options| + execute_hook(base, options, hook) + end end end
\ No newline at end of file diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 7a0ec2e910..5b072d4102 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -73,7 +73,7 @@ class BufferedLoggerTest < Test::Unit::TestCase end @logger.flush - assert !@output.string.empty?, @logger.send(:buffer).size + assert !@output.string.empty?, @logger.send(:buffer).size.to_s end define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_flush_at_max_buffer_size_as_failsafe" do @@ -86,7 +86,7 @@ class BufferedLoggerTest < Test::Unit::TestCase end @logger.info 'there it is.' - assert !@output.string.empty?, @logger.send(:buffer).size + assert !@output.string.empty?, @logger.send(:buffer).size.to_s end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index d9ff1207e7..3e14c754b7 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -375,7 +375,7 @@ module LocalCacheBehavior def test_local_cache_of_write_nil @cache.with_local_cache do - assert true, @cache.write('foo', nil) + assert @cache.write('foo', nil) assert_nil @cache.read('foo') @peek.write('foo', 'bar') assert_nil @cache.read('foo') @@ -394,7 +394,7 @@ module LocalCacheBehavior @cache.with_local_cache do @cache.write('foo', 'bar') @peek.delete('foo') - assert true, @cache.exist?('foo') + assert @cache.exist?('foo') end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index e7617466c2..ebd6806416 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -358,15 +358,19 @@ class ArrayUniqByTests < Test::Unit::TestCase end end -class ArrayExtRandomTests < Test::Unit::TestCase +class ArrayExtRandomTests < ActiveSupport::TestCase def test_random_element_from_array - assert_nil [].rand + assert_nil [].random_element Kernel.expects(:rand).with(1).returns(0) - assert_equal 'x', ['x'].rand + assert_equal 'x', ['x'].random_element Kernel.expects(:rand).with(3).returns(1) - assert_equal 2, [1, 2, 3].rand + assert_equal 2, [1, 2, 3].random_element + end + + def test_deprecated_rand_on_array + assert_deprecated { [].rand } end end diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 2b66fd03d0..1bf118e3b7 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' require 'active_support/time' -class DateExtCalculationsTest < Test::Unit::TestCase +class DateExtCalculationsTest < ActiveSupport::TestCase def test_to_s date = Date.new(2005, 2, 21) assert_equal "2005-02-21", date.to_s @@ -145,16 +145,20 @@ class DateExtCalculationsTest < Test::Unit::TestCase assert_equal Date.new(2005,2,28), Date.new(2004,2,29).years_since(1) # 1 year since leap day end - def test_last_year - assert_equal Date.new(2004,6,5), Date.new(2005,6,5).last_year + def test_last_year_is_deprecated + assert_deprecated { Date.today.last_year } end - def test_last_year_in_leap_years - assert_equal Date.new(1999,2,28), Date.new(2000,2,29).last_year + def test_prev_year + assert_equal Date.new(2004,6,5), Date.new(2005,6,5).prev_year end - def test_last_year_in_calendar_reform - assert_equal Date.new(1582,10,4), Date.new(1583,10,14).last_year + def test_prev_year_in_leap_years + assert_equal Date.new(1999,2,28), Date.new(2000,2,29).prev_year + end + + def test_prev_year_in_calendar_reform + assert_equal Date.new(1582,10,4), Date.new(1583,10,14).prev_year end def test_next_year @@ -225,8 +229,12 @@ class DateExtCalculationsTest < Test::Unit::TestCase assert_equal Date.new(2005, 9, 30), Date.new(2005, 8, 31).next_month end - def test_last_month_on_31st - assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).last_month + def test_last_month_is_deprecated + assert_deprecated { Date.today.last_month } + end + + def test_prev_month_on_31st + assert_equal Date.new(2004, 2, 29), Date.new(2004, 3, 31).prev_month end def test_yesterday_constructor diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index f9af059acd..4780760a19 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -127,8 +127,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal DateTime.civil(2005,2,28,10), DateTime.civil(2004,2,29,10,0,0).years_since(1) # 1 year since leap day end - def test_last_year - assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).last_year + def test_prev_year + assert_equal DateTime.civil(2004,6,5,10), DateTime.civil(2005,6,5,10,0,0).prev_year end def test_next_year @@ -200,8 +200,8 @@ class DateTimeExtCalculationsTest < Test::Unit::TestCase assert_equal DateTime.civil(2005, 9, 30), DateTime.civil(2005, 8, 31).next_month end - def test_last_month_on_31st - assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month + def test_prev_month_on_31st + assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).prev_month end def test_xmlschema diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 342d6ab577..30ee1d1652 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' require 'active_support/time' -class TimeExtCalculationsTest < Test::Unit::TestCase +class TimeExtCalculationsTest < ActiveSupport::TestCase def test_seconds_since_midnight assert_equal 1,Time.local(2005,1,1,0,0,1).seconds_since_midnight assert_equal 60,Time.local(2005,1,1,0,1,0).seconds_since_midnight @@ -166,8 +166,12 @@ class TimeExtCalculationsTest < Test::Unit::TestCase # assert_equal Time.local(2182,6,5,10), Time.local(2005,6,5,10,0,0).years_since(177) end - def test_last_year - assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).last_year + def test_last_year_is_deprecated + assert_deprecated { Time.now.last_year } + end + + def test_prev_year + assert_equal Time.local(2004,6,5,10), Time.local(2005,6,5,10,0,0).prev_year end def test_next_year @@ -615,8 +619,12 @@ class TimeExtCalculationsTest < Test::Unit::TestCase assert_equal Time.local(2005, 9, 30), Time.local(2005, 8, 31).next_month end - def test_last_month_on_31st - assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month + def test_last_month_is_deprecated + assert_deprecated { Time.now.last_month } + end + + def test_prev_month_on_31st + assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).prev_month end def test_xmlschema_is_available diff --git a/activesupport/test/lazy_load_hooks_test.rb b/activesupport/test/lazy_load_hooks_test.rb new file mode 100644 index 0000000000..58ccc14324 --- /dev/null +++ b/activesupport/test/lazy_load_hooks_test.rb @@ -0,0 +1,67 @@ +require 'abstract_unit' + +class LazyLoadHooksTest < ActiveSupport::TestCase + def test_basic_hook + i = 0 + ActiveSupport.on_load(:basic_hook) { i += 1 } + ActiveSupport.run_load_hooks(:basic_hook) + assert_equal 1, i + end + + def test_hook_registered_after_run + i = 0 + ActiveSupport.run_load_hooks(:registered_after) + assert_equal 0, i + ActiveSupport.on_load(:registered_after) { i += 1 } + assert_equal 1, i + end + + def test_hook_receives_a_context + i = 0 + ActiveSupport.on_load(:contextual) { i += incr } + assert_equal 0, i + ActiveSupport.run_load_hooks(:contextual, FakeContext.new(2)) + assert_equal 2, i + end + + def test_hook_receives_a_context_afterward + i = 0 + ActiveSupport.run_load_hooks(:contextual_after, FakeContext.new(2)) + assert_equal 0, i + ActiveSupport.on_load(:contextual_after) { i += incr } + assert_equal 2, i + end + + def test_hook_with_yield_true + i = 0 + ActiveSupport.on_load(:contextual_yield, :yield => true) do |obj| + i += obj.incr + incr_amt + end + assert_equal 0, i + ActiveSupport.run_load_hooks(:contextual_yield, FakeContext.new(2)) + assert_equal 7, i + end + + def test_hook_with_yield_true_afterward + i = 0 + ActiveSupport.run_load_hooks(:contextual_yield_after, FakeContext.new(2)) + assert_equal 0, i + ActiveSupport.on_load(:contextual_yield_after, :yield => true) do |obj| + i += obj.incr + incr_amt + end + assert_equal 7, i + end + +private + + def incr_amt + 5 + end + + class FakeContext + attr_reader :incr + def initialize(incr) + @incr = incr + end + end +end
\ No newline at end of file diff --git a/railties/guides/source/active_record_basics.textile b/railties/guides/source/active_record_basics.textile index 226f1b134b..d81e461e63 100644 --- a/railties/guides/source/active_record_basics.textile +++ b/railties/guides/source/active_record_basics.textile @@ -104,6 +104,14 @@ class Product < ActiveRecord::Base set_table_name "PRODUCT" end </ruby> +If you do so, you will have to define manually the class name that is hosting the fixtures (class_name.yml) using the +set_fixture_class+ method in your test definition: +<ruby> +class FunnyJoke < ActiveSupport::TestCase + set_fixture_class :funny_jokes => 'Joke' + fixtures :funny_jokes + ... +end +</ruby> It's also possible to override the column that should be used as the table's primary key. Use the +ActiveRecord::Base.set_primary_key+ method for that: <ruby> @@ -201,4 +209,4 @@ Active Record callbacks allow you to attach code to certain events in the life-c h3. Migrations -Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any database that Active Record support using rake. Rails keeps track of which files have been committed to the database and provides rollback features. You can learn more about migrations in the "Active Record Migrations guide":migrations.html
\ No newline at end of file +Rails provides a domain-specific language for managing a database schema called migrations. Migrations are stored in files which are executed against any database that Active Record support using rake. Rails keeps track of which files have been committed to the database and provides rollback features. You can learn more about migrations in the "Active Record Migrations guide":migrations.html diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile index 72194e567a..08fddd2926 100644 --- a/railties/guides/source/active_support_core_extensions.textile +++ b/railties/guides/source/active_support_core_extensions.textile @@ -2660,13 +2660,13 @@ Active Support defines +Date.current+ to be today in the current time zone. That h5. Named dates -h6. +last_year+, +next_year+ +h6. +prev_year+, +next_year+ -The methods +last_year+ and +next_year+ return a date with the same day/month in the last or next year: +In Ruby 1.9 +prev_year+ and +next_year+ return a date with the same day/month in the last or next year: <ruby> d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.last_year # => Fri, 08 May 2009 +d.prev_year # => Fri, 08 May 2009 d.next_year # => Sun, 08 May 2011 </ruby> @@ -2674,29 +2674,33 @@ If date is the 29th of February of a leap year, you obtain the 28th: <ruby> d = Date.new(2000, 2, 29) # => Tue, 29 Feb 2000 -d.last_year # => Sun, 28 Feb 1999 +d.prev_year # => Sun, 28 Feb 1999 d.next_year # => Wed, 28 Feb 2001 </ruby> -h6. +last_month+, +next_month+ +Active Support defines these methods as well for Ruby 1.8. -The methods +last_month+ and +next_month+ return the date with the same day in the last or next month: +h6. +prev_month+, +next_month+ + +In Ruby 1.9 +prev_month+ and +next_month+ return the date with the same day in the last or next month: <ruby> d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.last_month # => Thu, 08 Apr 2010 +d.prev_month # => Thu, 08 Apr 2010 d.next_month # => Tue, 08 Jun 2010 </ruby> If such a day does not exist, the last day of the corresponding month is returned: <ruby> -Date.new(2000, 5, 31).last_month # => Sun, 30 Apr 2000 -Date.new(2000, 3, 31).last_month # => Tue, 29 Feb 2000 +Date.new(2000, 5, 31).prev_month # => Sun, 30 Apr 2000 +Date.new(2000, 3, 31).prev_month # => Tue, 29 Feb 2000 Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000 Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000 </ruby> +Active Support defines these methods as well for Ruby 1.8. + h6. +beginning_of_week+, +end_of_week+ The methods +beginning_of_week+ and +end_of_week+ return the dates for the beginning and end of week, assuming weeks start on Monday: diff --git a/railties/guides/source/initialization.textile b/railties/guides/source/initialization.textile index 9ce27fa331..96d6998e1c 100644 --- a/railties/guides/source/initialization.textile +++ b/railties/guides/source/initialization.textile @@ -2379,7 +2379,6 @@ Now that we've referenced that class, it will be required for us. You'll notice * initialize_subscriber * set_clear_dependencies_hook * initialize_dependency_mechanism -* bootstrap_load_path These are all defined using the +initializer+ method: @@ -2930,7 +2929,6 @@ With +@@autoloads+ being * initialize_subscriber * set_clear_dependencies_hook * initialize_dependency_mechanism -* bootstrap_load_path h4. Active Support Initializers diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index d39f9a2ae9..a3b3a56bc8 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -12,7 +12,7 @@ module Rails # points to it. # # In other words, Rails::Application is Singleton and whenever you are accessing - # Rails::Application.config or YourApplication::Application.config, you are actually + # Rails::Application.config or YourApplication::Application.config, you are actually # accessing YourApplication::Application.instance.config. # # == Initialization @@ -40,7 +40,7 @@ module Rails # # The Application is also responsible for building the middleware stack and setting up # both application and engines metals. - # + # class Application < Engine autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configurable, 'rails/application/configurable' @@ -69,6 +69,7 @@ module Rails raise "You cannot have more than one Rails::Application" if Rails.application super Rails.application = base.instance + ActiveSupport.run_load_hooks(:before_configuration, base.instance) end def respond_to?(*args) @@ -82,7 +83,7 @@ module Rails end end - delegate :metal_loader, :to => :config + delegate :middleware, :metal_loader, :to => :config def require_environment! environment = paths.config.environment.to_a.first @@ -125,7 +126,7 @@ module Rails end def app - @app ||= middleware.build(routes) + @app ||= config.middleware.build(routes) end def call(env) diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 022e1a91d8..e62eed8a87 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -10,7 +10,8 @@ module Rails require environment if environment end - initializer :load_all_active_support do + initializer :load_active_support do + require 'active_support/dependencies' require "active_support/all" unless config.active_support.bare end @@ -18,7 +19,6 @@ module Rails # Used by Passenger to ensure everything's loaded before forking and # to avoid autoload race conditions in JRuby. initializer :preload_frameworks do - require 'active_support/dependencies' ActiveSupport::Autoload.eager_autoload! if config.preload_frameworks end @@ -66,8 +66,8 @@ module Rails ActiveSupport::Dependencies.mechanism = config.cache_classes ? :require : :load end - initializer :bootstrap_load_path do - # This is just an initializer used as hook so all load paths are loaded together + initializer :bootstrap_hook do |app| + ActiveSupport.run_load_hooks(:before_initialize, app) end end end diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 1ad77fdfec..9353fbefef 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -33,7 +33,7 @@ module Rails end def middleware - @middleware ||= default_middleware_stack + @middleware ||= app_middleware.merge_into(default_middleware_stack) end def metal_loader @@ -150,10 +150,10 @@ module Rails middleware.use('::ActionDispatch::Cookies') middleware.use(lambda { session_store }, lambda { session_options }) middleware.use('::ActionDispatch::Flash', :if => lambda { session_store }) - middleware.use(lambda { metal_loader.build_middleware(metals) }, :if => lambda { metal_loader.metals.any? }) - middleware.use('ActionDispatch::ParamsParser') + middleware.use('::ActionDispatch::ParamsParser') middleware.use('::Rack::MethodOverride') middleware.use('::ActionDispatch::Head') + middleware.use(lambda { metal_loader.build_middleware(metals) }, :if => lambda { metal_loader.metals.any? }) end end end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 94507bb387..fbab4d5515 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -35,12 +35,17 @@ module Rails app end - initializer :after_initialize do - config.after_initialize_blocks.each do |block| - block.call(self) + initializer :eager_load! do + if config.cache_classes && !$rails_rake_task + ActiveSupport.run_load_hooks(:before_eager_load, self) + railties.all(&:eager_load!) end end + initializer :finisher_hook do + ActiveSupport.run_load_hooks(:after_initialize, self) + end + # Disable dependency loading during request cycle initializer :disable_dependency_loading do if config.cache_classes && !config.dependency_loading diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index bd404f4a14..ee0fca6592 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -5,6 +5,37 @@ require 'rails/rack' module Rails module Configuration + class MiddlewareStackProxy #:nodoc: + def initialize + @operations = [] + end + + def insert_before(*args, &block) + @operations << [:insert_before, args, block] + end + + alias :insert :insert_before + + def insert_after(*args, &block) + @operations << [:insert_after, args, block] + end + + def swap(*args, &block) + @operations << [:swap, args, block] + end + + def use(*args, &block) + @operations << [:use, args, block] + end + + def merge_into(other) + @operations.each do |operation, args, block| + other.send(operation, *args, &block) + end + other + end + end + class Generators #:nodoc: attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index ab0ead65a9..b44755820c 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -45,7 +45,7 @@ module Rails # app.middleware.use MyEngine::Middleware # end # end - # + # # == Paths # # Since Rails 3.0, both your Application and Engines do not have hardcoded paths. @@ -125,15 +125,24 @@ module Rails end end - delegate :middleware, :paths, :root, :to => :config + delegate :paths, :root, :to => :config def load_tasks super config.paths.lib.tasks.to_a.sort.each { |ext| load(ext) } end + def eager_load! + config.eager_load_paths.each do |load_path| + matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ + Dir.glob("#{load_path}/**/*.rb").sort.each do |file| + require_dependency file.sub(matcher, '\1') + end + end + end + # Add configured load paths to ruby load paths and remove duplicates. - initializer :set_load_path, :before => :bootstrap_load_path do + initializer :set_load_path, :before => :bootstrap_hook do config.load_paths.reverse_each do |path| $LOAD_PATH.unshift(path) if File.directory?(path) end @@ -142,7 +151,10 @@ module Rails # Set the paths from which Rails will automatically load source files, # and the load_once paths. - initializer :set_autoload_paths, :before => :bootstrap_load_path do |app| + # + # This needs to be an initializer, since it needs to run once + # per engine and get the engine as a block parameter + initializer :set_autoload_paths, :before => :bootstrap_hook do |app| ActiveSupport::Dependencies.load_paths.unshift(*config.load_paths) if reloadable?(app) @@ -200,17 +212,9 @@ module Rails end end - initializer :load_app_classes do |app| - next if $rails_rake_task - - if app.config.cache_classes - config.eager_load_paths.each do |load_path| - matcher = /\A#{Regexp.escape(load_path)}\/(.*)\.rb\Z/ - Dir.glob("#{load_path}/**/*.rb").sort.each do |file| - require_dependency file.sub(matcher, '\1') - end - end - end + initializer :engines_blank_point do + # We need this initializer so all extra initializers added in engines are + # consistently executed after all the initializers above across all engines. end protected diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index a31932906d..7af329bbf0 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -54,7 +54,8 @@ module Rails name, version = args # Deal with deprecated options - { :env => :only, :lib => :require_as }.each do |old, new| + { :env => :group, :only => :group, + :lib => :require, :require_as => :require }.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" diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index e962308585..f01934f946 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -9,12 +9,13 @@ module Rails def field_type @field_type ||= case type - when :integer, :float, :decimal then :text_field - when :datetime, :timestamp, :time then :datetime_select - when :date then :date_select - when :string then :text_field - when :text then :text_area - when :boolean then :check_box + when :integer, :float, :decimal then :text_field + when :time then :time_select + when :datetime, :timestamp then :datetime_select + when :date then :date_select + when :string then :text_field + when :text then :text_area + when :boolean then :check_box else :text_field end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 0a0b033738..ee44acc2fa 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -149,7 +149,7 @@ module Rails # can change in Ruby 1.8.7 when we FileUtils.cd. RAILS_DEV_PATH = File.expand_path("../../../../../..", File.dirname(__FILE__)) - RESERVED_NAMES = %w[generate console server dbconsole + RESERVED_NAMES = %w[generate g console c server s dbconsole db application destroy benchmarker profiler plugin runner test] diff --git a/railties/lib/rails/generators/test_case.rb b/railties/lib/rails/generators/test_case.rb index 6b97c69d8d..952400e049 100644 --- a/railties/lib/rails/generators/test_case.rb +++ b/railties/lib/rails/generators/test_case.rb @@ -189,6 +189,18 @@ module Rails end alias :assert_method :assert_instance_method + # Asserts the given field name gets translated to an attribute type + # properly. + # + # assert_field_type :date, :date_select + # + def assert_field_type(name, attribute_type) + assert_equal( + Rails::Generators::GeneratedAttribute.new('test', name.to_s).field_type, + attribute_type + ) + end + # Runs the generator configured for this class. The first argument is an array like # command line arguments: # diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 5a496f6536..e9c3ebe685 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -35,20 +35,6 @@ module Rails end end - def edge_rails_revision(info = git_info) - info[/commit ([a-z0-9-]+)/, 1] || freeze_edge_version - end - - def freeze_edge_version - if File.exist?(rails_vendor_root) - begin - Dir[File.join(rails_vendor_root, 'REVISION_*')].first.scan(/_(\d+)$/).first.first - rescue - Dir[File.join(rails_vendor_root, 'TAG_*')].first.scan(/_(.+)$/).first.first rescue 'unknown' - end - end - end - def to_s column_width = properties.names.map {|name| name.length}.max info = properties.map do |name, value| @@ -75,20 +61,6 @@ module Rails table << '</table>' end end - - protected - def rails_vendor_root - @rails_vendor_root ||= "#{Rails.root}/vendor/rails" - end - - def git_info - env_lang, ENV['LC_ALL'] = ENV['LC_ALL'], 'C' - Dir.chdir(rails_vendor_root) do - silence_stderr { `git log -n 1` } - end - ensure - ENV['LC_ALL'] = env_lang - end end # The Ruby version and platform, e.g. "1.8.2 (powerpc-darwin8.2.0)". @@ -120,11 +92,6 @@ module Rails Rails.configuration.middleware.active.map(&:inspect) end - # The Rails Git revision, if it's checked out into vendor/rails. - property 'Edge Rails revision' do - edge_rails_revision - end - # The application's location on the filesystem. property 'Application root' do File.expand_path(Rails.root) diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index b6b57bc5b5..1dba6e1538 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -197,6 +197,9 @@ module Rails end end + def eager_load! + end + def rake_tasks self.class.rake_tasks end diff --git a/railties/lib/rails/railtie/configuration.rb b/railties/lib/rails/railtie/configuration.rb index 16eccaccc4..4e6f94c534 100644 --- a/railties/lib/rails/railtie/configuration.rb +++ b/railties/lib/rails/railtie/configuration.rb @@ -7,6 +7,15 @@ module Rails @@options ||= {} end + # This allows you to modify the application's middlewares from Engines. + # + # All operations you run on the app_middleware will be replayed on the + # application once it is defined and the default_middlewares are + # created + def app_middleware + @@app_middleware ||= Rails::Configuration::MiddlewareStackProxy.new + end + # Holds generators configuration: # # config.generators do |g| @@ -28,12 +37,20 @@ module Rails end end - def after_initialize_blocks - @@after_initialize_blocks ||= [] + def before_configuration(&block) + ActiveSupport.on_load(:before_configuration, :yield => true, &block) + end + + def before_eager_load(&block) + ActiveSupport.on_load(:before_eager_load, :yield => true, &block) + end + + def before_initialize(&block) + ActiveSupport.on_load(:before_initialize, :yield => true, &block) end - def after_initialize(&blk) - after_initialize_blocks << blk if blk + def after_initialize(&block) + ActiveSupport.on_load(:after_initialize, :yield => true, &block) end def to_prepare_blocks diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 738f7f5301..063a393bfc 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -30,6 +30,28 @@ namespace :rails do generator.apply template, :verbose => false end + namespace :templates do + desc "Copy all the templates from rails to the application directory for customization. Already existing local copies will be overwritten" + task :copy do + generators_lib = File.expand_path("../../generators", __FILE__) + project_templates = "#{Rails.root}/lib/templates" + + default_templates = { "erb" => %w{controller mailer scaffold}, + "rails" => %w{controller helper metal scaffold_controller stylesheets} } + + default_templates.each do |type, names| + local_template_type_dir = File.join(project_templates, type) + FileUtils.mkdir_p local_template_type_dir + + names.each do |name| + dst_name = File.join(local_template_type_dir, name) + src_name = File.join(generators_lib, type, name, "templates") + FileUtils.cp_r src_name, dst_name + end + end + end + end + namespace :update do def invoke_from_app_generator(method) app_generator.invoke(method) diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 42e01d5e51..41619bc1f8 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -3,7 +3,11 @@ task :routes => :environment do Rails::Application.reload_routes! all_routes = ENV['CONTROLLER'] ? Rails.application.routes.routes.select { |route| route.defaults[:controller] == ENV['CONTROLLER'] } : Rails.application.routes.routes routes = all_routes.collect do |route| - name = Rails.application.routes.named_routes.routes.index(route).to_s + # TODO: The :index method is deprecated in 1.9 in favor of :key + # but we don't have :key in 1.8.7. We can remove this check when + # stop supporting 1.8.x + key_method = Hash.method_defined?('key') ? 'key' : 'index' + name = Rails.application.routes.named_routes.routes.send(key_method, route).to_s reqs = route.requirements.empty? ? "" : route.requirements.inspect {:name => name, :verb => route.verb.to_s, :path => route.path, :reqs => reqs} end diff --git a/railties/test/application/initializers/initializers_test.rb b/railties/test/application/initializers/hooks_test.rb index 2e6a707175..198abbe861 100644 --- a/railties/test/application/initializers/initializers_test.rb +++ b/railties/test/application/initializers/hooks_test.rb @@ -16,29 +16,34 @@ module ApplicationTests assert $foo end - test "after_initialize block works correctly" do + test "hooks block works correctly without cache classes (before_eager_load is not called)" do add_to_config <<-RUBY + $initialization_callbacks = [] config.root = "#{app_path}" - config.after_initialize { $test_after_initialize_block1 = "success" } - config.after_initialize { $test_after_initialize_block2 = "congratulations" } + config.cache_classes = false + config.before_configuration { $initialization_callbacks << 1 } + config.before_initialize { $initialization_callbacks << 2 } + config.before_eager_load { Boom } + config.after_initialize { $initialization_callbacks << 3 } RUBY - require "#{app_path}/config/environment" - assert_equal "success", $test_after_initialize_block1 - assert_equal "congratulations", $test_after_initialize_block2 + require "#{app_path}/config/environment" + assert_equal [1,2,3], $initialization_callbacks end - test "after_initialize block works correctly when no block is passed" do + test "hooks block works correctly with cache classes" do add_to_config <<-RUBY + $initialization_callbacks = [] 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" } + config.cache_classes = true + config.before_configuration { $initialization_callbacks << 1 } + config.before_initialize { $initialization_callbacks << 2 } + config.before_eager_load { $initialization_callbacks << 3 } + config.after_initialize { $initialization_callbacks << 4 } RUBY - require "#{app_path}/config/environment" - assert_equal "success", $test_after_initialize_block1 - assert_equal "congratulations", $test_after_initialize_block2 + require "#{app_path}/config/environment" + assert_equal [1,2,3,4], $initialization_callbacks end test "after_initialize runs after frameworks have been initialized" do @@ -61,7 +66,7 @@ module ApplicationTests RUBY require "#{app_path}/config/environment" - assert [:to_prepare, :after_initialize], $order + assert_equal [:to_prepare, :after_initialize], $order end test "after_initialize happens after to_prepare in production" do @@ -75,7 +80,7 @@ module ApplicationTests require "#{app_path}/config/application" Rails.env.replace "production" require "#{app_path}/config/environment" - assert [:to_prepare, :after_initialize], $order + assert_equal [:to_prepare, :after_initialize], $order end end end diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index e6fab93a87..65fbf61902 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -86,8 +86,13 @@ class ActionsTest < Rails::Generators::TestCase 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_deprecated do + action :gem, 'thoughtbot-factory_girl', :require_as => 'factory_girl', :source => 'http://gems.github.com' + end + + assert_file 'Gemfile', /gem "mislav\-will\-paginate", :require => "will\-paginate"/ assert_file 'Gemfile', /source "http:\/\/gems\.github\.com"/ + assert_file 'Gemfile', /gem "thoughtbot-factory_girl", :require => "factory_girl"/ end def test_gem_with_env_should_include_all_dependencies_in_gemfile @@ -97,7 +102,12 @@ class ActionsTest < Rails::Generators::TestCase action :gem, 'rspec', :env => %w(development test) end - assert_file 'Gemfile', /gem "rspec", :only => \["development", "test"\]/ + assert_deprecated do + action :gem, 'rspec-rails', :only => %w(development test) + end + + assert_file 'Gemfile', /gem "rspec", :group => \["development", "test"\]/ + assert_file 'Gemfile', /gem "rspec-rails", :group => \["development", "test"\]/ end def test_gem_with_version_should_include_version_in_gemfile diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 8743defe82..3975a39ab1 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -70,8 +70,13 @@ class AppGeneratorTest < Rails::Generators::TestCase end def test_name_collision_raises_an_error - content = capture(:stderr){ run_generator [File.join(destination_root, "generate")] } - assert_equal "Invalid application name generate. Please give a name which does not match one of the reserved rails words.\n", content + reserved_words = %w[generate g console c server s dbconsole db + application destroy benchmarker profiler + plugin runner test] + reserved_words.each do |reserved| + content = capture(:stderr){ run_generator [File.join(destination_root, reserved)] } + assert_equal "Invalid application name #{reserved}. Please give a name which does not match one of the reserved rails words.\n", content + end end def test_invalid_database_option_raises_an_error diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb new file mode 100644 index 0000000000..dacb06fb13 --- /dev/null +++ b/railties/test/generators/generated_attribute_test.rb @@ -0,0 +1,40 @@ +require 'generators/generators_test_helper' +require 'rails/generators/generated_attribute' + +class GeneratedAttributeTest < Rails::Generators::TestCase + include GeneratorsTestHelper + + def test_field_type_returns_text_field + %w(integer float decimal string).each do |name| + assert_field_type name, :text_field + end + end + + def test_field_type_returns_datetime_select + %w(datetime timestamp).each do |name| + assert_field_type name, :datetime_select + end + end + + def test_field_type_returns_time_select + assert_field_type 'time', :time_select + end + + def test_field_type_returns_date_select + assert_field_type 'date', :date_select + end + + def test_field_type_returns_text_area + assert_field_type 'text', :text_area + end + + def test_field_type_returns_check_box + assert_field_type 'boolean', :check_box + end + + def test_field_type_with_unknown_type_returns_text_field + %w(foo bar baz).each do |name| + assert_field_type name, :text_field + end + end +end diff --git a/railties/test/generators_test.rb b/railties/test/generators_test.rb index 16f8f43b99..74a09d4bde 100644 --- a/railties/test/generators_test.rb +++ b/railties/test/generators_test.rb @@ -108,21 +108,10 @@ class GeneratorsTest < Rails::Generators::TestCase assert_match /^ fixjour$/, output end - def test_rails_generators_does_not_show_activerecord_info_if_its_the_default - output = capture(:stdout){ Rails::Generators.help } - assert_no_match /ActiveRecord:/, output - assert_no_match /^ active_record:model$/, output - assert_no_match /^ active_record:fixjour$/, output - end - - def test_rails_generators_shows_activerecord_info_if_its_not_the_default - Rails::Generators.options[:rails][:orm] = :data_mapper + def test_rails_generators_does_not_show_activerecord_hooks output = capture(:stdout){ Rails::Generators.help } assert_match /ActiveRecord:/, output - assert_match /^ active_record:model$/, output assert_match /^ active_record:fixjour$/, output - ensure - Rails::Generators.options[:rails][:orm] = :active_record end def test_no_color_sets_proper_shell diff --git a/railties/test/rails_info_test.rb b/railties/test/rails_info_test.rb index fc28d7e912..1da66062aa 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -14,27 +14,6 @@ class InfoTest < ActiveSupport::TestCase silence_warnings { load 'rails/info.rb' } end - def test_edge_rails_revision_not_set_when_svn_info_is_empty - Rails::Info.property 'Test that this will not be defined' do - Rails::Info.edge_rails_revision '' - end - assert !property_defined?('Test that this will not be defined') - end - - def test_edge_rails_revision_extracted_from_svn_info - Rails::Info.property 'Test Edge Rails revision' do - Rails::Info.edge_rails_revision <<-EOS - commit 420c4b3d8878156d04f45e47050ddc62ae00c68c - Author: David Heinemeier Hansson <david@loudthinking.com> - Date: Sun Apr 13 17:33:27 2008 -0500 - - Added Rails.public_path to control where HTML and assets are expected to be loaded from -EOS - end - - assert_property 'Test Edge Rails revision', '420c4b3d8878156d04f45e47050ddc62ae00c68c' - end - def test_property_with_block_swallows_exceptions_and_ignores_property assert_nothing_raised do Rails::Info.module_eval do diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 40ac11fa03..b3f65fd00d 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -28,5 +28,31 @@ module RailtiesTest boot_rails assert !Rails::Engine.respond_to?(:config) end + + test "initializers are executed after application configuration initializers" do + @plugin.write "lib/bukkits.rb", <<-RUBY + class Bukkits + class Engine < ::Rails::Engine + initializer "dummy_initializer" do + end + end + end + RUBY + + boot_rails + + initializers = Rails.application.initializers + index = initializers.index { |i| i.name == "dummy_initializer" } + selection = initializers[(index-3)..(index)].map(&:name).map(&:to_s) + + assert_equal %w( + load_config_initializers + load_config_initializers + engines_blank_point + dummy_initializer + ), selection + + assert index < initializers.index { |i| i.name == :build_middleware_stack } + end end end |