diff options
132 files changed, 1263 insertions, 974 deletions
@@ -8,7 +8,7 @@ else gem 'arel' end -gem 'minitest', '~> 3.1.0' +gem 'minitest', '~> 3.2.0' gem 'mocha', '>= 0.11.2' gem 'rack-test', github: "brynary/rack-test" gem 'bcrypt-ruby', '~> 3.0.0' diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index a822412090..4d8f739403 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -2,6 +2,8 @@ * Raise an `ActionView::MissingTemplate` exception when no implicit template could be found. *Damien Mathieu* +* Asynchronously send messages via the Rails Queue *Brian Cardarella* + ## Rails 3.2.5 (Jun 1, 2012) ## * No changes. diff --git a/actionmailer/lib/action_mailer/async.rb b/actionmailer/lib/action_mailer/async.rb new file mode 100644 index 0000000000..a364342745 --- /dev/null +++ b/actionmailer/lib/action_mailer/async.rb @@ -0,0 +1,41 @@ +require 'delegate' + +module ActionMailer + module Async + def method_missing(method_name, *args) + if action_methods.include?(method_name.to_s) + QueuedMessage.new(queue, self, method_name, *args) + else + super + end + end + + def queue + Rails.queue + end + + class QueuedMessage < ::Delegator + attr_reader :queue + + def initialize(queue, mailer_class, method_name, *args) + @queue = queue + @mailer_class = mailer_class + @method_name = method_name + @args = args + end + + def __getobj__ + @actual_message ||= @mailer_class.send(:new, @method_name, *@args).message + end + + def run + __getobj__.deliver + end + + # Will push the message onto the Queue to be processed + def deliver + @queue << self + end + end + end +end
\ No newline at end of file diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 739f9a52a9..f31e1e007b 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -456,6 +456,19 @@ module ActionMailer #:nodoc: super || action_methods.include?(method.to_s) end + # Will force ActionMailer to push new messages to the queue defined + # in the ActionMailer class when set to true. + # + # class WelcomeMailer < ActionMailer::Base + # self.async = true + # end + def async=(truth) + if truth + require 'action_mailer/async' + extend ActionMailer::Async + end + end + protected def set_payload_for_mail(payload, mail) #:nodoc: @@ -773,5 +786,4 @@ module ActionMailer #:nodoc: ActiveSupport.run_load_hooks(:action_mailer, self) end -end - +end
\ No newline at end of file diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 1b2e39b3f7..144a6bfe39 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -7,6 +7,8 @@ require 'active_support/time' require 'mailers/base_mailer' require 'mailers/proc_mailer' require 'mailers/asset_mailer' +require 'mailers/async_mailer' +require 'rails/queueing' class BaseTest < ActiveSupport::TestCase def teardown @@ -419,6 +421,26 @@ class BaseTest < ActiveSupport::TestCase assert_equal(1, BaseMailer.deliveries.length) end + def stub_queue(klass, queue) + Class.new(klass) { + extend Module.new { + define_method :queue do + queue + end + } + } + end + + test "delivering message asynchronously" do + testing_queue = Rails::Queueing::TestQueue.new + AsyncMailer.delivery_method = :test + AsyncMailer.deliveries.clear + stub_queue(AsyncMailer, testing_queue).welcome.deliver + assert_equal(0, AsyncMailer.deliveries.length) + testing_queue.drain + assert_equal(1, AsyncMailer.deliveries.length) + end + test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do mail = Mail::Message.new mail.expects(:do_delivery).once @@ -434,6 +456,7 @@ class BaseTest < ActiveSupport::TestCase end test "should raise if missing template in implicit render" do + BaseMailer.deliveries.clear assert_raises ActionView::MissingTemplate do BaseMailer.implicit_different_template('missing_template').deliver end diff --git a/actionmailer/test/fixtures/async_mailer/welcome.erb b/actionmailer/test/fixtures/async_mailer/welcome.erb new file mode 100644 index 0000000000..01f3f00c63 --- /dev/null +++ b/actionmailer/test/fixtures/async_mailer/welcome.erb @@ -0,0 +1 @@ +Welcome
\ No newline at end of file diff --git a/actionmailer/test/mailers/async_mailer.rb b/actionmailer/test/mailers/async_mailer.rb new file mode 100644 index 0000000000..ce601e7343 --- /dev/null +++ b/actionmailer/test/mailers/async_mailer.rb @@ -0,0 +1,3 @@ +class AsyncMailer < BaseMailer + self.async = true +end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 7d73c6af8d..3da2834af0 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -49,9 +49,19 @@ module AbstractController module ClassMethods def view_context_class @view_context_class ||= begin - routes = _routes if respond_to?(:_routes) - helpers = _helpers if respond_to?(:_helpers) - ActionView::Base.prepare(routes, helpers) + routes = respond_to?(:_routes) && _routes + helpers = respond_to?(:_helpers) && _helpers + + Class.new(ActionView::Base) do + if routes + include routes.url_helpers + include routes.mounted_helpers + end + + if helpers + include helpers + end + end end end end diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index cc1fa23935..39da15e26a 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -72,6 +72,12 @@ module ActionController #:nodoc: self.controller = nil end + def around(controller) + before(controller) + yield + after(controller) + end + protected # gets the action cache path for the given options. def action_path_for(options) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 028a8d3fba..a1f29ea1bc 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -450,7 +450,7 @@ module ActionController Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }] when Array hash_or_array_or_value.map {|i| paramify_values(i)} - when Rack::Test::UploadedFile + when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile hash_or_array_or_value else hash_or_array_or_value.to_param @@ -576,7 +576,7 @@ module ActionController # # The exception is stored in the exception accessor for further inspection. module RaiseActionExceptions - def self.included(base) + def self.included(base) #:nodoc: unless base.method_defined?(:exception) && base.method_defined?(:exception=) base.class_eval do attr_accessor :exception diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 94242ad962..53a4afecb3 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1316,22 +1316,6 @@ module ActionDispatch parent_resource.instance_of?(Resource) && @scope[:shallow] end - def draw(name) - path = @draw_paths.find do |_path| - File.exists? "#{_path}/#{name}.rb" - end - - unless path - msg = "Your router tried to #draw the external file #{name}.rb,\n" \ - "but the file was not found in:\n\n" - msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n") - raise ArgumentError, msg - end - - route_path = "#{path}/#{name}.rb" - instance_eval(File.read(route_path), route_path.to_s) - end - # match 'path' => 'controller#action' # match 'path', to: 'controller#action' # match 'path', 'otherpath', on: :member, via: :get @@ -1581,7 +1565,6 @@ module ActionDispatch def initialize(set) #:nodoc: @set = set - @draw_paths = set.draw_paths @scope = { :path_names => @set.resources_path_names } end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 64b1d58ae9..1d6ca0c78d 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -237,7 +237,6 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names attr_accessor :default_url_options, :request_class, :valid_conditions - attr_accessor :draw_paths alias :routes :set @@ -249,7 +248,6 @@ module ActionDispatch self.named_routes = NamedRouteCollection.new self.resources_path_names = self.class.default_resources_path_names.dup self.default_url_options = {} - self.draw_paths = [] self.request_class = request_class @valid_conditions = { :controller => true, :action => true } diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index f98648d930..7bfbc1f0aa 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -148,7 +148,6 @@ module ActionView #:nodoc: cattr_accessor :prefix_partial_path_with_controller_namespace @@prefix_partial_path_with_controller_namespace = true - class_attribute :helpers class_attribute :_routes class_attribute :logger @@ -166,22 +165,6 @@ module ActionView #:nodoc: def xss_safe? #:nodoc: true end - - # This method receives routes and helpers from the controller - # and return a subclass ready to be used as view context. - def prepare(routes, helpers) #:nodoc: - Class.new(self) do - if routes - include routes.url_helpers - include routes.mounted_helpers - end - - if helpers - include helpers - self.helpers = helpers - end - end - end end attr_accessor :view_renderer diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 8f97d1f014..9720e90429 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -59,7 +59,7 @@ module ActionView return unless number options = options.symbolize_keys - parse_float(number, true) if options[:raise] + parse_float(number, true) if options.delete(:raise) ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options)) end @@ -109,7 +109,9 @@ module ActionView return unless number options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_currency(number, options) } + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.number_to_currency(number, options) + } end # Formats a +number+ as a percentage string (e.g., 65%). You can @@ -152,7 +154,9 @@ module ActionView return unless number options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_percentage(number, options) } + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.number_to_percentage(number, options) + } end # Formats a +number+ with grouped thousands using +delimiter+ @@ -187,7 +191,9 @@ module ActionView def number_with_delimiter(number, options = {}) options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_delimited(number, options) } + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.number_to_delimited(number, options) + } end # Formats a +number+ with the specified level of @@ -234,7 +240,9 @@ module ActionView def number_with_precision(number, options = {}) options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_rounded(number, options) } + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.number_to_rounded(number, options) + } end @@ -288,7 +296,9 @@ module ActionView def number_to_human_size(number, options = {}) options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human_size(number, options) } + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.number_to_human_size(number, options) + } end # Pretty prints (formats and approximates) a number in a way it @@ -392,7 +402,9 @@ module ActionView def number_to_human(number, options = {}) options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human(number, options) } + wrap_with_output_safety_handling(number, options.delete(:raise)) { + ActiveSupport::NumberHelper.number_to_human(number, options) + } end private diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index b7945a23be..00989ec405 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -24,7 +24,7 @@ module ActionView Accessors.send :define_method, :"default_#{name}", &block Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{name} - @details[:#{name}] + @details.fetch(:#{name}, []) end def #{name}=(value) diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 72616b7463..e3d8e9d508 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -1,7 +1,6 @@ module ActionView class AbstractRenderer #:nodoc: - delegate :find_template, :template_exists?, :with_fallbacks, :update_details, - :with_layout_format, :formats, :to => :@lookup_context + delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context def initialize(lookup_context) @lookup_context = lookup_context diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 82892593f8..3c1b11396a 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -18,10 +18,10 @@ module ActionView # Determine the template to be rendered using the given options. def determine_template(options) #:nodoc: - keys = options[:locals].try(:keys) || [] + keys = options.fetch(:locals, {}).keys if options.key?(:text) - Template::Text.new(options[:text], formats.try(:first)) + Template::Text.new(options[:text], formats.first) elsif options.key?(:file) with_fallbacks { find_template(options[:file], nil, false, keys, @details) } elsif options.key?(:inline) diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 4261c3b5e2..3af76dfcdb 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,11 +1,11 @@ module ActionView #:nodoc: # = Action View Text Template class Template - class Text < String #:nodoc: + class Text #:nodoc: attr_accessor :mime_type def initialize(string, mime_type = nil) - super(string.to_s) + @string = string.to_s @mime_type = Mime[mime_type] || mime_type if mime_type @mime_type ||= Mime::TEXT end @@ -18,8 +18,12 @@ module ActionView #:nodoc: 'text template' end + def to_str + @string + end + def render(*args) - to_s + to_str end def formats diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index ef7fbca675..b9cb93f0f4 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -326,6 +326,12 @@ class FilterTest < ActionController::TestCase controller.instance_variable_set(:"@after_ran", true) controller.class.execution_log << " after aroundfilter " if controller.respond_to? :execution_log end + + def around(controller) + before(controller) + yield + after(controller) + end end class AppendedAroundFilter @@ -336,6 +342,12 @@ class FilterTest < ActionController::TestCase def after(controller) controller.class.execution_log << " after appended aroundfilter " end + + def around(controller) + before(controller) + yield + after(controller) + end end class AuditController < ActionController::Base diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 0d6d303b51..49137946fe 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -813,6 +813,13 @@ XML assert_equal '159528', @response.body end + def test_action_dispatch_uploaded_file_upload + filename = 'mona_lisa.jpg' + path = "#{FILES_DIR}/#{filename}" + post :test_file_upload, :file => ActionDispatch::Http::UploadedFile.new(:filename => path, :type => "image/jpg", :tempfile => File.open(path)) + assert_equal '159528', @response.body + end + def test_test_uploaded_file_exception_when_file_doesnt_exist assert_raise(RuntimeError) { Rack::Test::UploadedFile.new('non_existent_file') } end diff --git a/actionpack/test/dispatch/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb index bd078d2b21..8070bdec8a 100644 --- a/actionpack/test/dispatch/mapper_test.rb +++ b/actionpack/test/dispatch/mapper_test.rb @@ -4,12 +4,11 @@ module ActionDispatch module Routing class MapperTest < ActiveSupport::TestCase class FakeSet - attr_reader :routes, :draw_paths + attr_reader :routes alias :set :routes def initialize @routes = [] - @draw_paths = [] end def resources_path_names diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index f15dd7214b..fed26d89f8 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -2324,55 +2324,6 @@ class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest end end -class TestDrawExternalFile < ActionDispatch::IntegrationTest - class ExternalController < ActionController::Base - def index - render :text => "external#index" - end - end - - DRAW_PATH = File.expand_path('../../fixtures/routes', __FILE__) - - DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new.tap do |app| - app.draw_paths << DRAW_PATH - end - - def app - DefaultScopeRoutes - end - - def test_draw_external_file - DefaultScopeRoutes.draw do - scope :module => 'test_draw_external_file' do - draw :external - end - end - - get '/external' - assert_equal "external#index", @response.body - end - - def test_draw_nonexistent_file - exception = assert_raise ArgumentError do - DefaultScopeRoutes.draw do - draw :nonexistent - end - end - assert_match 'Your router tried to #draw the external file nonexistent.rb', exception.message - assert_match DRAW_PATH.to_s, exception.message - end - - def test_draw_bogus_file - exception = assert_raise NoMethodError do - DefaultScopeRoutes.draw do - draw :bogus - end - end - assert_match "undefined method `wrong'", exception.message - assert_match 'test/fixtures/routes/bogus.rb:1', exception.backtrace.first - end -end - class TestDefaultScope < ActionDispatch::IntegrationTest module ::Blog class PostsController < ActionController::Base diff --git a/actionpack/test/fixtures/routes/bogus.rb b/actionpack/test/fixtures/routes/bogus.rb deleted file mode 100644 index 41fbf0cd64..0000000000 --- a/actionpack/test/fixtures/routes/bogus.rb +++ /dev/null @@ -1 +0,0 @@ -wrong :route diff --git a/actionpack/test/fixtures/routes/external.rb b/actionpack/test/fixtures/routes/external.rb deleted file mode 100644 index d103c39f53..0000000000 --- a/actionpack/test/fixtures/routes/external.rb +++ /dev/null @@ -1 +0,0 @@ -get '/external' => 'external#index' diff --git a/actionpack/test/template/number_helper_i18n_test.rb b/actionpack/test/template/number_helper_i18n_test.rb deleted file mode 100644 index d6e9de9555..0000000000 --- a/actionpack/test/template/number_helper_i18n_test.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'abstract_unit' - -class NumberHelperTest < ActionView::TestCase - tests ActionView::Helpers::NumberHelper - - def setup - I18n.backend.store_translations 'ts', - :number => { - :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false }, - :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } }, - :human => { - :format => { - :precision => 2, - :significant => true, - :strip_insignificant_zeros => true - }, - :storage_units => { - :format => "%n %u", - :units => { - :byte => "b", - :kb => "k" - } - }, - :decimal_units => { - :format => "%n %u", - :units => { - :deci => {:one => "Tenth", :other => "Tenths"}, - :unit => "u", - :ten => {:one => "Ten", :other => "Tens"}, - :thousand => "t", - :million => "m" , - :billion =>"b" , - :trillion =>"t" , - :quadrillion =>"q" - } - } - }, - :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} }, - :precision => { :format => {:delimiter => '', :significant => true} } - }, - :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - end - - def test_number_to_i18n_currency - assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts')) - assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts')) - assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u")) - end - - def test_number_to_currency_with_clean_i18n_settings - clean_i18n do - assert_equal("$10.00", number_to_currency(10)) - assert_equal("-$10.00", number_to_currency(-10)) - end - end - - def test_number_to_currency_without_currency_negative_format - clean_i18n do - I18n.backend.store_translations 'ts', :number => { :currency => { :format => { :unit => '@', :format => '%n %u' } } } - assert_equal("-10.00 @", number_to_currency(-10, :locale => 'ts')) - end - end - - def test_number_with_i18n_precision - #Delimiter was set to "" - assert_equal("10000", number_with_precision(10000, :locale => 'ts')) - - #Precision inherited and significant was set - assert_equal("1.00", number_with_precision(1.0, :locale => 'ts')) - - end - - def test_number_with_i18n_delimiter - #Delimiter "," and separator "." - assert_equal("1,000,000.234", number_with_delimiter(1000000.234, :locale => 'ts')) - end - - def test_number_to_i18n_percentage - # to see if strip_insignificant_zeros is true - assert_equal("1%", number_to_percentage(1, :locale => 'ts')) - # precision is 2, significant should be inherited - assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts')) - # no delimiter - assert_equal("12434%", number_to_percentage(12434, :locale => 'ts')) - end - - def test_number_to_i18n_human_size - #b for bytes and k for kbytes - assert_equal("2 k", number_to_human_size(2048, :locale => 'ts')) - assert_equal("42 b", number_to_human_size(42, :locale => 'ts')) - end - - def test_number_to_human_with_default_translation_scope - #Using t for thousand - assert_equal "2 t", number_to_human(2000, :locale => 'ts') - #Significant was set to true with precision 2, using b for billion - assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts') - #Using pluralization (Ten/Tens and Tenth/Tenths) - assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts') - assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts') - assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts') - assert_equal "1 Ten", number_to_human(10, :locale => 'ts') - assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts') - assert_equal "2 Tens", number_to_human(20, :locale => 'ts') - end - - def test_number_to_human_with_custom_translation_scope - #Significant was set to true with precision 2, with custom translated units - assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human) - end - - private - def clean_i18n - load_path = I18n.load_path.dup - I18n.load_path.clear - I18n.reload! - yield - ensure - I18n.load_path = load_path - I18n.reload! - end -end diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 057cb47f53..d8fffe75ed 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -361,69 +361,39 @@ class NumberHelperTest < ActionView::TestCase end def test_number_helpers_should_raise_error_if_invalid_when_specified - assert_raise InvalidNumberError do + exception = assert_raise InvalidNumberError do number_to_human("x", :raise => true) end - begin - number_to_human("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number - end + assert_equal "x", exception.number - assert_raise InvalidNumberError do - number_to_human_size("x", :raise => true) - end - begin + exception = assert_raise InvalidNumberError do number_to_human_size("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number end + assert_equal "x", exception.number - assert_raise InvalidNumberError do + exception = assert_raise InvalidNumberError do number_with_precision("x", :raise => true) end - begin - number_with_precision("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number - end + assert_equal "x", exception.number - assert_raise InvalidNumberError do + exception = assert_raise InvalidNumberError do number_to_currency("x", :raise => true) end - begin - number_with_precision("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number - end + assert_equal "x", exception.number - assert_raise InvalidNumberError do + exception = assert_raise InvalidNumberError do number_to_percentage("x", :raise => true) end - begin - number_to_percentage("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number - end + assert_equal "x", exception.number - assert_raise InvalidNumberError do - number_with_delimiter("x", :raise => true) - end - begin + exception = assert_raise InvalidNumberError do number_with_delimiter("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number end + assert_equal "x", exception.number - assert_raise InvalidNumberError do + exception = assert_raise InvalidNumberError do number_to_phone("x", :raise => true) end - begin - number_to_phone("x", :raise => true) - rescue InvalidNumberError => e - assert_equal "x", e.number - end - + assert_equal "x", exception.number end - end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index f30d00b355..eb06250060 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/class/attribute' require 'active_support/deprecation' @@ -98,7 +97,7 @@ module ActiveModel # person.clear_name # person.name # => nil def attribute_method_prefix(*prefixes) - self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix } + self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix } undefine_attribute_methods end @@ -133,7 +132,7 @@ module ActiveModel # person.name # => "Bob" # person.name_short? # => true def attribute_method_suffix(*suffixes) - self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix } + self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix } undefine_attribute_methods end @@ -169,7 +168,7 @@ module ActiveModel # person.reset_name_to_default! # person.name # => 'Gemma' def attribute_method_affix(*affixes) - self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] } + self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] } undefine_attribute_methods end @@ -336,15 +335,13 @@ module ActiveModel end def attribute_method_matcher(method_name) #:nodoc: - if attribute_method_matchers_cache.key?(method_name) - attribute_method_matchers_cache[method_name] - else + attribute_method_matchers_cache.fetch(method_name) do |name| # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix # will match every time. matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1) match = nil - matchers.detect { |method| match = method.match(method_name) } - attribute_method_matchers_cache[method_name] = match + matchers.detect { |method| match = method.match(name) } + attribute_method_matchers_cache[name] = match end end @@ -352,18 +349,18 @@ module ActiveModel # using the given `extra` args. This fallbacks `define_method` # and `send` if the given names cannot be compiled. def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc: - if name =~ NAME_COMPILABLE_REGEXP - defn = "def #{name}(*args)" + defn = if name =~ NAME_COMPILABLE_REGEXP + "def #{name}(*args)" else - defn = "define_method(:'#{name}') do |*args|" + "define_method(:'#{name}') do |*args|" end - extra = (extra.map(&:inspect) << "*args").join(", ") + extra = (extra.map!(&:inspect) << "*args").join(", ") - if send =~ CALL_COMPILABLE_REGEXP - target = "#{"self." unless include_private}#{send}(#{extra})" + target = if send =~ CALL_COMPILABLE_REGEXP + "#{"self." unless include_private}#{send}(#{extra})" else - target = "send(:'#{send}', #{extra})" + "send(:'#{send}', #{extra})" end mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1 @@ -379,8 +376,6 @@ module ActiveModel AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name) def initialize(options = {}) - options.symbolize_keys! - if options[:prefix] == '' || options[:suffix] == '' ActiveSupport::Deprecation.warn( "Specifying an empty prefix/suffix for an attribute method is no longer " \ @@ -390,7 +385,7 @@ module ActiveModel ) end - @prefix, @suffix = options[:prefix] || '', options[:suffix] || '' + @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '') @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ @method_missing_target = "#{@prefix}attribute#{@suffix}" @method_name = "#{prefix}%s#{suffix}" @@ -399,8 +394,6 @@ module ActiveModel def match(method_name) if @regex =~ method_name AttributeMethodMatch.new(method_missing_target, $1, method_name) - else - nil end end @@ -459,7 +452,7 @@ module ActiveModel end protected - def attribute_method?(attr_name) + def attribute_method?(attr_name) #:nodoc: respond_to_without_attributes?(:attributes) && attributes.include?(attr_name) end @@ -468,7 +461,7 @@ module ActiveModel # The struct's attributes are prefix, base and suffix. def match_attribute_method?(method_name) match = self.class.send(:attribute_method_matcher, method_name) - match && attribute_method?(match.attr_name) ? match : nil + match if match && attribute_method?(match.attr_name) end def missing_attribute(attr_name, stack) diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index b8ba1a0a52..e669113001 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -46,7 +46,7 @@ module ActiveModel # Would only create the +after_create+ and +before_create+ callback methods in # your class. module Callbacks - def self.extended(base) + def self.extended(base) #:nodoc: base.class_eval do include ActiveSupport::Callbacks end @@ -89,11 +89,11 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - :terminator => "result == false", - :skip_after_callbacks_if_terminated => true, - :scope => [:kind, :name], - :only => [:before, :around, :after] - }.merge(options) + :terminator => "result == false", + :skip_after_callbacks_if_terminated => true, + :scope => [:kind, :name], + :only => [:before, :around, :after] + }.merge!(options) types = Array(options.delete(:only)) @@ -106,6 +106,8 @@ module ActiveModel end end + private + def _define_before_model_callback(klass, callback) #:nodoc: klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1 def self.before_#{callback}(*args, &block) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index b022a875ec..4ed3462e7e 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -76,7 +76,7 @@ module ActiveModel @messages = {} end - def initialize_dup(other) + def initialize_dup(other) #:nodoc: @messages = other.messages.dup super end @@ -96,8 +96,8 @@ module ActiveModel # person.errors.messages # => { :name => ["can not be nil"] } # person.errors.include?(:name) # => true # person.errors.include?(:age) # => false - def include?(error) - (v = messages[error]) && v.any? + def include?(attribute) + (v = messages[attribute]) && v.any? end # aliases include? alias :has_key? :include? diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb index 415ab0ad17..f104d0306c 100644 --- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb +++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb @@ -2,7 +2,7 @@ require 'set' module ActiveModel module MassAssignmentSecurity - class PermissionSet < Set + class PermissionSet < Set #:nodoc: def +(values) super(values.compact.map(&:to_s)) @@ -23,14 +23,14 @@ module ActiveModel end end - class WhiteList < PermissionSet + class WhiteList < PermissionSet #:nodoc: def deny?(key) !include?(key) end end - class BlackList < PermissionSet + class BlackList < PermissionSet #:nodoc: def deny?(key) include?(key) diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index 1b2f0e6049..33a530e6bd 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -56,7 +56,7 @@ module ActiveModel # refer to the specific modules included in <tt>ActiveModel::Model</tt> # (see below). module Model - def self.included(base) + def self.included(base) #:nodoc: base.class_eval do extend ActiveModel::Naming extend ActiveModel::Translation diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index cd596e37d2..55ea6be796 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -82,8 +82,7 @@ module ActiveModel # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_each(*attr_names, &block) - options = attr_names.extract_options!.symbolize_keys - validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block + validates_with BlockValidator, _merge_attributes(attr_names), &block end # Adds a validation method or block to the class. This is useful when diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 38abd0c1fa..43651094cf 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -2,9 +2,9 @@ module ActiveModel # == Active Model Acceptance Validator module Validations - class AcceptanceValidator < EachValidator + class AcceptanceValidator < EachValidator #:nodoc: def initialize(options) - super(options.reverse_merge(:allow_nil => true, :accept => "1")) + super({ :allow_nil => true, :accept => "1" }.merge!(options)) end def validate_each(record, attribute, value) @@ -58,7 +58,7 @@ module ActiveModel # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). # The method, proc or string should return or evaluate to a true or # false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information. def validates_acceptance_of(*attr_names) validates_with AcceptanceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index ede34d15bc..b6cf82fb19 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -2,7 +2,7 @@ module ActiveModel # == Active Model Confirmation Validator module Validations - class ConfirmationValidator < EachValidator + class ConfirmationValidator < EachValidator #:nodoc: def validate_each(record, attribute, value) if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed) human_attribute_name = record.class.human_attribute_name(attribute) @@ -59,7 +59,7 @@ module ActiveModel # <tt>:unless => :skip_validation</tt>, or # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information. def validates_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index edd42d85f2..c8d7057606 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -4,7 +4,7 @@ module ActiveModel # == Active Model Exclusion Validator module Validations - class ExclusionValidator < EachValidator + class ExclusionValidator < EachValidator #:nodoc: include Clusivity def validate_each(record, attribute, value) diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index ffdf842d94..d48987c482 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -2,7 +2,7 @@ module ActiveModel # == Active Model Format Validator module Validations - class FormatValidator < EachValidator + class FormatValidator < EachValidator #:nodoc: def validate_each(record, attribute, value) if options[:with] regexp = option_call(record, :with) @@ -32,12 +32,12 @@ module ActiveModel def record_error(record, attribute, name, value) record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value)) end - + def regexp_using_multiline_anchors?(regexp) regexp.source.start_with?("^") || (regexp.source.end_with?("$") && !regexp.source.end_with?("\\$")) end - + def check_options_validity(options, name) option = options[name] if option && !option.is_a?(Regexp) && !option.respond_to?(:call) @@ -110,7 +110,7 @@ module ActiveModel # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information. # * <tt>:multiline</tt> - Set to true if your regular expression contains # anchors that match the beginning or end of lines as opposed to the diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 8810f2a3c1..154db5aedc 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -4,7 +4,7 @@ module ActiveModel # == Active Model Inclusion Validator module Validations - class InclusionValidator < EachValidator + class InclusionValidator < EachValidator #:nodoc: include Clusivity def validate_each(record, attribute, value) diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 64b4fe2d74..40ebe0cd2e 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -2,7 +2,7 @@ module ActiveModel # == Active Model Length Validator module Validations - class LengthValidator < EachValidator + class LengthValidator < EachValidator #:nodoc: MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 40b5b92b84..1069ed3906 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -2,7 +2,7 @@ module ActiveModel # == Active Model Numericality Validator module Validations - class NumericalityValidator < EachValidator + class NumericalityValidator < EachValidator #:nodoc: CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=, :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=, :odd => :odd?, :even => :even?, :other_than => :!= }.freeze diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index 018ef1e733..a7dcdbba3d 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -4,7 +4,7 @@ module ActiveModel # == Active Model Presence Validator module Validations - class PresenceValidator < EachValidator + class PresenceValidator < EachValidator #:nodoc: def validate(record) record.errors.add_on_blank(attributes, options) end @@ -40,7 +40,7 @@ module ActiveModel # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>, # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method, # proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information. def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 66cc9daa2c..3c516f8b22 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -3,12 +3,14 @@ module ActiveModel module HelperMethods private def _merge_attributes(attr_names) - options = attr_names.extract_options! - options.merge(:attributes => attr_names.flatten) + options = attr_names.extract_options!.symbolize_keys + attr_names.flatten! + options[:attributes] = attr_names + options end end - class WithValidator < EachValidator + class WithValidator < EachValidator #:nodoc: def validate_each(record, attr, val) method_name = options[:with] @@ -61,7 +63,7 @@ module ActiveModel # (e.g. <tt>:unless => :skip_validation</tt>, or # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information. # # If you pass any additional configuration options, they will be passed diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index ed5f29cfe4..aeb3773c99 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,40 @@ ## Rails 4.0.0 (unreleased) ## +* `ActiveRelation#inspect` no longer calls `#to_a` + + *Brian Cardarella* + +* Add `collate` and `ctype` support to PostgreSQL. These are available for PostgreSQL 8.4 or later. + Example: + + development: + adapter: postgresql + host: localhost + database: rails_development + username: foo + password: bar + encoding: UTF8 + collate: ja_JP.UTF8 + ctype: ja_JP.UTF8 + + *kennyj* + +* `FinderMethods#exists?` now returns `false` with the `false` argument. + + *Egor Lynko* + +* Added support for specifying the precision of a timestamp in the postgresql + adapter. So, instead of having to incorrectly specify the precision using the + `:limit` option, you may use `:precision`, as intended. For example, in a migration: + + def change + create_table :foobars do |t| + t.timestamps :precision => 0 + end + end + + *Tony Schneider* + * Allow ActiveRecord::Relation#pluck to accept multiple columns. Returns an array of arrays containing the typecasted values: diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index ab6d253ef8..5b41f72e52 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -158,11 +158,12 @@ module ActiveRecord begin send(name + "=", read_value_from_parameter(name, values_with_empty_parameters)) rescue => ex - errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name) + errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name) end end unless errors.empty? - raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes" + error_descriptions = errors.map { |ex| ex.message }.join(",") + raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]" end end @@ -180,15 +181,27 @@ module ActiveRecord end def read_time_parameter_value(name, values_hash_from_param) - # If Date bits were not provided, error - raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)} - max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6) - # If Date bits were provided but blank, then return nil - return nil if (1..3).any? {|position| values_hash_from_param[position].blank?} + # If column is a :time (and not :date or :timestamp) there is no need to validate if + # there are year/month/day fields + if column_for_attribute(name).type == :time + # if the column is a time set the values to their defaults as January 1, 1970, but only if they're nil + {1 => 1970, 2 => 1, 3 => 1}.each do |key,value| + values_hash_from_param[key] ||= value + end + else + # else column is a timestamp, so if Date bits were not provided, error + if missing_parameter = [1,2,3].detect{ |position| !values_hash_from_param.has_key?(position) } + raise ArgumentError.new("Missing Parameter - #{name}(#{missing_parameter}i)") + end + + # If Date bits were provided but blank, then return nil + return nil if (1..3).any? { |position| values_hash_from_param[position].blank? } + end - set_values = (1..max_position).collect{|position| values_hash_from_param[position] } + max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6) + set_values = (1..max_position).collect{ |position| values_hash_from_param[position] } # If Time bits are not there, then default to 0 - (3..5).each {|i| set_values[i] = set_values[i].blank? ? 0 : set_values[i]} + (3..5).each { |i| set_values[i] = set_values[i].blank? ? 0 : set_values[i] } instantiate_time_object(name, set_values) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 17377bad96..3b4537aab4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -2,7 +2,7 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module QueryCache class << self - def included(base) + def included(base) #:nodoc: dirties_query_cache base, :insert, :update, :delete end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 921278d145..df4a9d5afc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -72,6 +72,8 @@ module ActiveRecord when /^mediumint/i; 3 when /^smallint/i; 2 when /^tinyint/i; 1 + when /^enum\((.+)\)/i + $1.split(',').map{|enum| enum.strip.length - 2}.max else super end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 8491d42b86..dd40351a38 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,3 +1,5 @@ +require 'uri' + module ActiveRecord module ConnectionAdapters class ConnectionSpecification #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 43e243581c..507e937c3e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -187,6 +187,7 @@ module ActiveRecord case sql_type when /^bigint/i; 8 when /^smallint/i; 2 + when /^timestamp/i; nil else super end end @@ -201,6 +202,8 @@ module ActiveRecord def extract_precision(sql_type) if sql_type == 'money' self.class.money_precision + elsif sql_type =~ /timestamp/i + $1.to_i if sql_type =~ /\((\d+)\)/ else super end @@ -913,7 +916,8 @@ module ActiveRecord end # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>, - # <tt>:encoding</tt>, <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses + # <tt>:encoding</tt>, <tt>:collate</tt>, <tt>:ctype</tt>, + # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>). # # Example: @@ -930,6 +934,10 @@ module ActiveRecord " TEMPLATE = \"#{value}\"" when :encoding " ENCODING = '#{value}'" + when :collate + " LC_COLLATE = '#{value}'" + when :ctype + " LC_CTYPE = '#{value}'" when :tablespace " TABLESPACE = \"#{value}\"" when :connection_limit @@ -1056,6 +1064,20 @@ module ActiveRecord end_sql end + # Returns the current database collate. + def collate + query(<<-end_sql, 'SCHEMA')[0][0] + SELECT pg_database.datcollate FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' + end_sql + end + + # Returns the current database ctype. + def ctype + query(<<-end_sql, 'SCHEMA')[0][0] + SELECT pg_database.datctype FROM pg_database WHERE pg_database.datname LIKE '#{current_database}' + end_sql + end + # Returns an array of schema names. def schema_names query(<<-SQL, 'SCHEMA').flatten @@ -1285,6 +1307,13 @@ module ActiveRecord when 5..8; 'bigint' else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with precision 0 instead.") end + when 'datetime' + return super unless precision + + case precision + when 0..6; "timestamp(#{precision})" + else raise(ActiveRecordError, "No timestamp type has precision of #{precision}. The allowed range of precision is from 0 to 6") + end else super end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index a0c7e559ce..57aa47ab61 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -191,7 +191,7 @@ module ActiveRecord :decimal => { :name => "decimal" }, :datetime => { :name => "datetime" }, :timestamp => { :name => "datetime" }, - :time => { :name => "datetime" }, + :time => { :name => "time" }, :date => { :name => "date" }, :binary => { :name => "blob" }, :boolean => { :name => "boolean" } diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 858b667e22..5f157fde6d 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -106,13 +106,11 @@ module ActiveRecord attr_reader :record, :attempted_action def initialize(record, attempted_action) + super("Attempted to #{attempted_action} a stale object: #{record.class.name}") @record = record @attempted_action = attempted_action end - def message - "Attempted to #{attempted_action} a stale object: #{record.class.name}" - end end # Raised when association is being configured improperly or @@ -168,9 +166,9 @@ module ActiveRecord class AttributeAssignmentError < ActiveRecordError attr_reader :exception, :attribute def initialize(message, exception, attribute) + super(message) @exception = exception @attribute = attribute - @message = message end end @@ -189,12 +187,10 @@ module ActiveRecord attr_reader :model def initialize(model) + super("Unknown primary key for table #{model.table_name} in model #{model}.") @model = model end - def message - "Unknown primary key for table #{model.table_name} in model #{model}." - end end class ImmutableRelation < ActiveRecordError diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 8199b5c2e0..78ecb1cdc5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -35,7 +35,7 @@ db_namespace = namespace :db do ActiveRecord::Tasks::DatabaseTasks.drop_current end - desc "Migrate the database (options: VERSION=x, VERBOSE=false)." + desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." task :migrate => [:environment, :load_config] do ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration| @@ -148,13 +148,10 @@ db_namespace = namespace :db do # desc "Retrieves the collation for the current environment's database" task :collation => [:environment, :load_config] do - config = ActiveRecord::Base.configurations[Rails.env || 'development'] - case config['adapter'] - when /mysql/ - ActiveRecord::Base.establish_connection(config) - puts ActiveRecord::Base.connection.collation - else - $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch' + begin + puts ActiveRecord::Tasks::DatabaseTasks.collation_current + rescue NoMethodError + $stderr.puts 'Sorry, your database adapter is not supported yet, feel free to submit a patch' end end @@ -270,6 +267,15 @@ db_namespace = namespace :db do end namespace :structure do + def set_firebird_env(config) + ENV['ISC_USER'] = config['username'].to_s if config['username'] + ENV['ISC_PASSWORD'] = config['password'].to_s if config['password'] + end + + def firebird_db_string(config) + FireRuby::Database.db_string_for(config.symbolize_keys) + end + desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql' task :dump => [:environment, :load_config] do abcs = ActiveRecord::Base.configurations @@ -304,7 +310,7 @@ db_namespace = namespace :db do filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql") case abcs[env]['adapter'] when /mysql/, /postgresql/, /sqlite/ - ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[Rails.env], filename) + ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[env], filename) when 'sqlserver' `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}` when 'oci', 'oracle' @@ -338,6 +344,13 @@ db_namespace = namespace :db do end end + # desc "Recreate the test database from an existent schema.rb file" + task :load_schema => 'db:test:purge' do + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + db_namespace["schema:load"].invoke + end + # desc "Recreate the test database from an existent structure.sql file" task :load_structure => 'db:test:purge' do begin @@ -348,15 +361,18 @@ db_namespace = namespace :db do end end - # desc "Recreate the test database from an existent schema.rb file" - task :load_schema => 'db:test:purge' do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) - ActiveRecord::Schema.verbose = false - db_namespace["schema:load"].invoke + # desc "Recreate the test database from a fresh schema" + task :clone do + case ActiveRecord::Base.schema_format + when :ruby + db_namespace["test:clone_schema"].invoke + when :sql + db_namespace["test:clone_structure"].invoke + end end # desc "Recreate the test database from a fresh schema.rb file" - task :clone => %w(db:schema:dump db:test:load_schema) + task :clone_schema => ["db:schema:dump", "db:test:load_schema"] # desc "Recreate the test database from a fresh structure.sql file" task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ] @@ -389,7 +405,7 @@ db_namespace = namespace :db do # desc 'Check for pending migrations and load the test schema' task :prepare => 'db:abort_if_pending_migrations' do unless ActiveRecord::Base.configurations.blank? - db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke + db_namespace['test:load'].invoke end end end @@ -405,7 +421,7 @@ db_namespace = namespace :db do # desc "Clear the sessions table" task :clear => [:environment, :load_config] do - ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}" + ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::SessionStore::Session.table_name}" end end end @@ -416,7 +432,7 @@ namespace :railties do task :migrations => :'db:load_config' do to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip } railties = {} - Rails.application.railties.all do |railtie| + Rails.application.railties.each do |railtie| next unless to_load == :all || to_load.include?(railtie.railtie_name) if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first) @@ -440,15 +456,3 @@ end task 'test:prepare' => 'db:test:prepare' -def session_table_name - ActiveRecord::SessionStore::Session.table_name -end - -def set_firebird_env(config) - ENV['ISC_USER'] = config['username'].to_s if config['username'] - ENV['ISC_PASSWORD'] = config['password'].to_s if config['password'] -end - -def firebird_db_string(config) - FireRuby::Database.db_string_for(config.symbolize_keys) -end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fe3aa00a74..e268d451e0 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -492,10 +492,6 @@ module ActiveRecord end end - def inspect - to_a.inspect - end - def pretty_print(q) q.pp(self.to_a) end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 86eb8f35b5..e40b958b54 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -262,10 +262,16 @@ module ActiveRecord end def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: - group_attr = group_values - association = @klass.reflect_on_association(group_attr.first.to_sym) - associated = group_attr.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations - group_fields = Array(associated ? association.foreign_key : group_attr) + group_attrs = group_values + + if group_attrs.first.respond_to?(:to_sym) + association = @klass.reflect_on_association(group_attrs.first.to_sym) + associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations + group_fields = Array(associated ? association.foreign_key : group_attrs) + else + group_fields = group_attrs + end + group_aliases = group_fields.map { |field| column_alias_for(field) } group_columns = group_aliases.zip(group_fields).map { |aliaz,field| [aliaz, column_for(field)] @@ -288,10 +294,14 @@ module ActiveRecord select_values += select_values unless having_values.empty? select_values.concat group_fields.zip(group_aliases).map { |field,aliaz| - "#{field} AS #{aliaz}" + if field.respond_to?(:as) + field.as(aliaz) + else + "#{field} AS #{aliaz}" + end } - relation = except(:group).group(group.join(',')) + relation = except(:group).group(group) relation.select_values = select_values calculated_data = @klass.connection.select_all(relation, nil, bind_values) @@ -303,10 +313,10 @@ module ActiveRecord end Hash[calculated_data.map do |row| - key = group_columns.map { |aliaz, column| + key = group_columns.map { |aliaz, column| type_cast_calculated_value(row[aliaz], column) } - key = key.first if key.size == 1 + key = key.first if key.size == 1 key = key_records[key] if associated [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)] end] @@ -321,6 +331,7 @@ module ActiveRecord # column_alias_for("count(*)") # => "count_all" # column_alias_for("count", "id") # => "count_id" def column_alias_for(*keys) + keys.map! {|k| k.respond_to?(:to_sql) ? k.to_sql : k} table_name = keys.join(' ') table_name.downcase! table_name.gsub!(/\*/, 'all') @@ -332,7 +343,7 @@ module ActiveRecord end def column_for(field) - field_name = field.to_s.split('.').last + field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last @klass.columns.detect { |c| c.name.to_s == field_name } end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index eb901727de..974cd326ef 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -172,19 +172,19 @@ module ActiveRecord # Person.exists?(name: 'David') # Person.exists?(false) # Person.exists? - def exists?(id = false) - id = id.id if ActiveRecord::Model === id - return false if id.nil? + def exists?(conditions = :none) + conditions = conditions.id if ActiveRecord::Model === conditions + return false if !conditions join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) relation = relation.except(:select, :order).select("1 AS one").limit(1) - case id + case conditions when Array, Hash - relation = relation.where(id) + relation = relation.where(conditions) else - relation = relation.where(table[primary_key].eq(id)) if id + relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none end connection.select_value(relation, "#{name} Exists", relation.bind_values) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5e5aca0396..6f49548aab 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -83,7 +83,9 @@ module ActiveRecord end def references!(*args) - self.references_values = (references_values + args.flatten.map(&:to_s)).uniq + args.flatten! + + self.references_values = (references_values + args.map!(&:to_s)).uniq self end @@ -134,7 +136,9 @@ module ActiveRecord end def group!(*args) - self.group_values += args.flatten + args.flatten! + + self.group_values += args self end @@ -143,11 +147,10 @@ module ActiveRecord end def order!(*args) - args = args.flatten + args.flatten! references = args.reject { |arg| Arel::Node === arg } - .map { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 } - .compact + references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! references!(references) if references.any? self.order_values += args @@ -168,8 +171,10 @@ module ActiveRecord end def reorder!(*args) + args.flatten! + self.reordering_value = true - self.order_values = args.flatten + self.order_values = args self end @@ -327,7 +332,7 @@ module ActiveRecord # # Should be used with order. # - # User.offset(10).order("name ASC") + # User.offset(10).order("name ASC") def offset(value) spawn.offset!(value) end @@ -410,10 +415,10 @@ module ActiveRecord # # Can accept other relation objects. For example: # - # Topic.select('title').from(Topics.approved) + # Topic.select('title').from(Topic.approved) # # => SELECT title FROM (SELECT * FROM topics WHERE approved = 't') subquery # - # Topics.select('a.title').from(Topics.approved, :a) + # Topic.select('a.title').from(Topic.approved, :a) # # => SELECT a.title FROM (SELECT * FROM topics WHERE approved = 't') a # def from(value, subquery_name = nil) diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 999b2ebc85..f1241502f5 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -56,6 +56,15 @@ module ActiveRecord class_for_adapter(configuration['adapter']).new(*arguments).charset end + def collation_current(environment = Rails.env) + collation ActiveRecord::Base.configurations[environment] + end + + def collation(*arguments) + configuration = arguments.first + class_for_adapter(configuration['adapter']).new(*arguments).collation + end + def purge(configuration) class_for_adapter(configuration['adapter']).new(configuration).purge end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index b39cd2282f..bf62dfd5b5 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -44,6 +44,10 @@ module ActiveRecord connection.charset end + def collation + connection.collation + end + def structure_dump(filename) establish_connection configuration File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump } diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index a210392e53..4139460273 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -29,6 +29,10 @@ module ActiveRecord connection.encoding end + def collation + connection.collate + end + def purge clear_active_connections! drop diff --git a/activerecord/lib/rails/generators/active_record/model/templates/model.rb b/activerecord/lib/rails/generators/active_record/model/templates/model.rb index d56f9f57a4..2cca17b94f 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/model.rb +++ b/activerecord/lib/rails/generators/active_record/model/templates/model.rb @@ -1,7 +1,7 @@ <% module_namespacing do -%> class <%= class_name %> < <%= parent_class_name.classify %> <% attributes.select {|attr| attr.reference? }.each do |attribute| -%> - belongs_to :<%= attribute.name %> + belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %> <% end -%> <% if !accessible_attributes.empty? -%> attr_accessible <%= accessible_attributes.map {|a| ":#{a.name}" }.sort.join(', ') %> diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb new file mode 100644 index 0000000000..40af317ad1 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/enum_test.rb @@ -0,0 +1,10 @@ +require "cases/helper" + +class MysqlEnumTest < ActiveRecord::TestCase + class EnumTest < ActiveRecord::Base + end + + def test_enum_limit + assert_equal 5, EnumTest.columns.first.limit + end +end diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb new file mode 100644 index 0000000000..f3a05e48ad --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -0,0 +1,10 @@ +require "cases/helper" + +class Mysql2EnumTest < ActiveRecord::TestCase + class EnumTest < ActiveRecord::Base + end + + def test_enum_limit + assert_equal 5, EnumTest.columns.first.limit + end +end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 447d729e52..148584214e 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -21,6 +21,10 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1) end + def test_create_database_with_collate_and_ctype + assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'UTF8' LC_COLLATE = 'ja_JP.UTF8' LC_CTYPE = 'ja_JP.UTF8'), create_database(:aimonetti, :encoding => :"UTF8", :collate => :"ja_JP.UTF8", :ctype => :"ja_JP.UTF8") + end + def test_add_index # add_index calls index_name_exists? which can't work since execute is stubbed ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*| diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index f6e168caf2..9b5c5eb72f 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -21,6 +21,14 @@ module ActiveRecord assert_not_nil @connection.encoding end + def test_collate + assert_not_nil @connection.collate + end + + def test_ctype + assert_not_nil @connection.ctype + end + def test_default_client_min_messages assert_equal "warning", @connection.client_min_messages end diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 337f43c421..26507ad654 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -27,4 +27,69 @@ class TimestampTest < ActiveRecord::TestCase d = Developer.create!(:name => 'aaron', :updated_at => -1.0 / 0.0) assert_equal(-1.0 / 0.0, d.updated_at) end + + def test_default_datetime_precision + ActiveRecord::Base.connection.create_table(:foos) + ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime + ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime + assert_nil activerecord_column_option('foos', 'created_at', 'precision') + end + + def test_timestamp_data_type_with_precision + ActiveRecord::Base.connection.create_table(:foos) + ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, :precision => 0 + ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, :precision => 5 + assert_equal 0, activerecord_column_option('foos', 'created_at', 'precision') + assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision') + end + + def test_timestamps_helper_with_custom_precision + ActiveRecord::Base.connection.create_table(:foos) do |t| + t.timestamps :precision => 4 + end + assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') + assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') + end + + def test_passing_precision_to_timestamp_does_not_set_limit + ActiveRecord::Base.connection.create_table(:foos) do |t| + t.timestamps :precision => 4 + end + assert_nil activerecord_column_option("foos", "created_at", "limit") + assert_nil activerecord_column_option("foos", "updated_at", "limit") + end + + def test_invalid_timestamp_precision_raises_error + assert_raises ActiveRecord::ActiveRecordError do + ActiveRecord::Base.connection.create_table(:foos) do |t| + t.timestamps :precision => 7 + end + end + end + + def test_postgres_agrees_with_activerecord_about_precision + ActiveRecord::Base.connection.create_table(:foos) do |t| + t.timestamps :precision => 4 + end + assert_equal '4', pg_datetime_precision('foos', 'created_at') + assert_equal '4', pg_datetime_precision('foos', 'updated_at') + end + + private + + def pg_datetime_precision(table_name, column_name) + results = ActiveRecord::Base.connection.execute("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name ='#{table_name}'") + result = results.find do |result_hash| + result_hash["column_name"] == column_name + end + result && result["datetime_precision"] + end + + def activerecord_column_option(tablename, column_name, option) + result = ActiveRecord::Base.connection.columns(tablename).find do |column| + column.name == column_name + end + result && result.send(option) + end + end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 5e947799cc..4e26c5dda1 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -24,7 +24,7 @@ module ActiveRecord @conn.extend(LogIntercepter) @conn.intercepted = true end - + def teardown @conn.intercepted = false @conn.logged = [] @@ -43,11 +43,6 @@ module ActiveRecord assert(!result.rows.first.include?("blob"), "should not store blobs") end - def test_time_column - owner = Owner.create!(:eats_at => Time.utc(1995,1,1,6,0)) - assert_match(/1995-01-01/, owner.reload.eats_at.to_s) - end - def test_exec_insert column = @conn.columns('items').find { |col| col.name == 'number' } vals = [[column, 10]] diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index b8481175b2..3ea6201d60 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -193,7 +193,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_no_sql_should_be_fired_if_association_already_loaded Car.create(:name => 'honda') bulbs = Car.first.bulbs - bulbs.inspect # to load all instances of bulbs + bulbs.to_a # to load all instances of bulbs assert_no_queries do bulbs.first() diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 1093fedea1..fe385feb4a 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -792,6 +792,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end private + def cached_columns Topic.columns.find_all { |column| !Topic.serialized_attributes.include? column.name diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 2fee553cef..e34f505a02 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -81,6 +81,12 @@ end class BasicsTest < ActiveRecord::TestCase fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories, :posts + def setup + ActiveRecord::Base.time_zone_aware_attributes = false + ActiveRecord::Base.default_timezone = :local + Time.zone = nil + end + def test_generated_methods_modules modules = Computer.ancestors assert modules.include?(Computer::GeneratedFeatureMethods) @@ -504,7 +510,7 @@ class BasicsTest < ActiveRecord::TestCase end # Oracle, and Sybase do not have a TIME datatype. - unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) + unless current_adapter?(:OracleAdapter, :SybaseAdapter) def test_utc_as_time_zone Topic.default_timezone = :utc attributes = { "bonus_time" => "5:42:00AM" } @@ -686,7 +692,7 @@ class BasicsTest < ActiveRecord::TestCase } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on + assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end def test_multiparameter_attributes_on_time_with_no_date @@ -746,9 +752,6 @@ class BasicsTest < ActiveRecord::TestCase end def test_multiparameter_attributes_on_time_will_ignore_hour_if_missing - ActiveRecord::Base.time_zone_aware_attributes = false - ActiveRecord::Base.default_timezone = :local - Time.zone = nil attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "12", "written_on(3i)" => "12", "written_on(5i)" => "12", "written_on(6i)" => "02" @@ -796,8 +799,6 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.find(1) topic.attributes = attributes assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on - ensure - ActiveRecord::Base.default_timezone = :local end def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes @@ -813,14 +814,9 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.utc(2004, 6, 24, 23, 24, 0), topic.written_on assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on.time assert_equal Time.zone, topic.written_on.time_zone - ensure - ActiveRecord::Base.time_zone_aware_attributes = false - ActiveRecord::Base.default_timezone = :local - Time.zone = nil end def test_multiparameter_attributes_on_time_with_time_zone_aware_attributes_false - ActiveRecord::Base.time_zone_aware_attributes = false Time.zone = ActiveSupport::TimeZone[-28800] attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", @@ -830,8 +826,6 @@ class BasicsTest < ActiveRecord::TestCase topic.attributes = attributes assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on assert_equal false, topic.written_on.respond_to?(:time_zone) - ensure - Time.zone = nil end def test_multiparameter_attributes_on_time_with_skip_time_zone_conversion_for_attributes @@ -848,14 +842,11 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.utc(2004, 6, 24, 16, 24, 0), topic.written_on assert_equal false, topic.written_on.respond_to?(:time_zone) ensure - ActiveRecord::Base.time_zone_aware_attributes = false - ActiveRecord::Base.default_timezone = :local - Time.zone = nil Topic.skip_time_zone_conversion_for_attributes = [] end # Oracle, and Sybase do not have a TIME datatype. - unless current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) + unless current_adapter?(:OracleAdapter, :SybaseAdapter) def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion ActiveRecord::Base.time_zone_aware_attributes = true ActiveRecord::Base.default_timezone = :utc @@ -868,17 +859,10 @@ class BasicsTest < ActiveRecord::TestCase topic.attributes = attributes assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time assert topic.bonus_time.utc? - ensure - ActiveRecord::Base.time_zone_aware_attributes = false - ActiveRecord::Base.default_timezone = :local - Time.zone = nil end end def test_multiparameter_attributes_on_time_with_empty_seconds - ActiveRecord::Base.time_zone_aware_attributes = false - ActiveRecord::Base.default_timezone = :local - Time.zone = nil attributes = { "written_on(1i)" => "2004", "written_on(2i)" => "6", "written_on(3i)" => "24", "written_on(4i)" => "16", "written_on(5i)" => "24", "written_on(6i)" => "" @@ -888,36 +872,51 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Time.local(2004, 6, 24, 16, 24, 0), topic.written_on end - def test_multiparameter_assignment_of_aggregation_with_missing_values - ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do - customer = Customer.new - address = Address.new("The Street", "The City", "The Country") - attributes = { "address(2)" => address.city, "address(3)" => address.country } - customer.attributes = attributes - end - assert_equal("address", ex.errors[0].attribute) + def test_multiparameter_attributes_setting_time_attribute + return skip "Oracle does not have TIME data type" if current_adapter? :OracleAdapter + + topic = Topic.new( "bonus_time(4i)"=> "01", "bonus_time(5i)" => "05" ) + assert_equal 1, topic.bonus_time.hour + assert_equal 5, topic.bonus_time.min end - def test_multiparameter_assignment_of_aggregation_with_large_index - ex = assert_raise(ActiveRecord::MultiparameterAssignmentErrors) do - customer = Customer.new - address = Address.new("The Street", "The City", "The Country") - attributes = { "address(1)" => "The Street", "address(2)" => address.city, "address(3000)" => address.country } - customer.attributes = attributes + def test_multiparameter_attributes_setting_date_attribute + topic = Topic.new( "written_on(1i)" => "1952", "written_on(2i)" => "3", "written_on(3i)" => "11" ) + assert_equal 1952, topic.written_on.year + assert_equal 3, topic.written_on.month + assert_equal 11, topic.written_on.day + end + + def test_multiparameter_attributes_setting_date_and_time_attribute + topic = Topic.new( + "written_on(1i)" => "1952", + "written_on(2i)" => "3", + "written_on(3i)" => "11", + "written_on(4i)" => "13", + "written_on(5i)" => "55") + assert_equal 1952, topic.written_on.year + assert_equal 3, topic.written_on.month + assert_equal 11, topic.written_on.day + assert_equal 13, topic.written_on.hour + assert_equal 55, topic.written_on.min + end + + def test_multiparameter_attributes_setting_time_but_not_date_on_date_field + assert_raise( ActiveRecord::MultiparameterAssignmentErrors ) do + Topic.new( "written_on(4i)" => "13", "written_on(5i)" => "55" ) end - assert_equal("address", ex.errors[0].attribute) end def test_attributes_on_dummy_time # Oracle, and Sybase do not have a TIME datatype. - return true if current_adapter?(:OracleAdapter, :SybaseAdapter, :SQLite3Adapter) + return true if current_adapter?(:OracleAdapter, :SybaseAdapter) attributes = { "bonus_time" => "5:42:00AM" } topic = Topic.find(1) topic.attributes = attributes - assert_equal Time.utc(2000, 1, 1, 5, 42, 0), topic.bonus_time + assert_equal Time.local(2000, 1, 1, 5, 42, 0), topic.bonus_time end def test_boolean @@ -1882,8 +1881,6 @@ class BasicsTest < ActiveRecord::TestCase est_key = Developer.first.cache_key assert_equal utc_key, est_key - ensure - ActiveRecord::Base.time_zone_aware_attributes = false end def test_cache_key_format_for_existing_record_with_updated_at diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 4df613488a..e1c1e449ef 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -58,7 +58,16 @@ class CalculationsTest < ActiveRecord::TestCase def test_should_group_by_field c = Account.group(:firm_id).sum(:credit_limit) - [1,6,2].each { |firm_id| assert c.keys.include?(firm_id) } + [1,6,2].each do |firm_id| + assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" + end + end + + def test_should_group_by_arel_attribute + c = Account.group(Account.arel_table[:firm_id]).sum(:credit_limit) + [1,6,2].each do |firm_id| + assert c.keys.include?(firm_id), "Group #{c.inspect} does not contain firm_id #{firm_id}" + end end def test_should_group_by_multiple_fields diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index e62d984ebd..576a455f09 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -55,6 +55,10 @@ class FinderTest < ActiveRecord::TestCase assert Topic.exists? end + def test_exists_returns_false_with_false_arg + assert !Topic.exists?(false) + end + # exists? should handle nil for id's that come from URLs and always return false # (example: Topic.exists?(params[:id])) where params[:id] is nil def test_exists_with_nil_arg diff --git a/activerecord/test/cases/inclusion_test.rb b/activerecord/test/cases/inclusion_test.rb index 8726ba5e51..9b9c09d2d8 100644 --- a/activerecord/test/cases/inclusion_test.rb +++ b/activerecord/test/cases/inclusion_test.rb @@ -4,7 +4,7 @@ require 'models/teapot' class BasicInclusionModelTest < ActiveRecord::TestCase def test_basic_model Teapot.create!(:name => "Ronnie Kemper") - assert_equal "Ronnie Kemper", Teapot.find(1).name + assert_equal "Ronnie Kemper", Teapot.first.name end def test_initialization diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index bac3376c9f..6631dce08f 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -305,6 +305,58 @@ class ReflectionTest < ActiveRecord::TestCase assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass end + def test_join_table + category = Struct.new(:table_name, :pluralize_table_names).new('categories', true) + product = Struct.new(:table_name, :pluralize_table_names).new('products', true) + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, {}, product) + reflection.stubs(:klass).returns(category) + assert_equal 'categories_products', reflection.join_table + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :products, {}, category) + reflection.stubs(:klass).returns(product) + assert_equal 'categories_products', reflection.join_table + end + + def test_join_table_with_common_prefix + category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true) + product = Struct.new(:table_name, :pluralize_table_names).new('catalog_products', true) + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, {}, product) + reflection.stubs(:klass).returns(category) + assert_equal 'catalog_categories_products', reflection.join_table + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :products, {}, category) + reflection.stubs(:klass).returns(product) + assert_equal 'catalog_categories_products', reflection.join_table + end + + def test_join_table_with_different_prefix + category = Struct.new(:table_name, :pluralize_table_names).new('catalog_categories', true) + page = Struct.new(:table_name, :pluralize_table_names).new('content_pages', true) + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, {}, page) + reflection.stubs(:klass).returns(category) + assert_equal 'catalog_categories_content_pages', reflection.join_table + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :pages, {}, category) + reflection.stubs(:klass).returns(page) + assert_equal 'catalog_categories_content_pages', reflection.join_table + end + + def test_join_table_can_be_overridden + category = Struct.new(:table_name, :pluralize_table_names).new('categories', true) + product = Struct.new(:table_name, :pluralize_table_names).new('products', true) + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :categories, { :join_table => 'product_categories' }, product) + reflection.stubs(:klass).returns(category) + assert_equal 'product_categories', reflection.join_table + + reflection = AssociationReflection.new(:has_and_belongs_to_many, :products, { :join_table => 'product_categories' }, category) + reflection.stubs(:klass).returns(product) + assert_equal 'product_categories', reflection.join_table + end + private def assert_reflection(klass, association, options) assert reflection = klass.reflect_on_association(association) diff --git a/activerecord/test/cases/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index 5f36b2c841..8c96a8aaa1 100644 --- a/activerecord/test/cases/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -251,6 +251,17 @@ module ActiveRecord end end + class DatabaseTasksCollationTest < ActiveRecord::TestCase + include DatabaseTasksSetupper + + ADAPTERS_TASKS.each do |k, v| + define_method("test_#{k}_collation") do + eval("@#{v}").expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation 'adapter' => k + end + end + end + class DatabaseTasksStructureDumpTest < ActiveRecord::TestCase include DatabaseTasksSetupper diff --git a/activerecord/test/cases/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index 42a11b0171..9a0eb423bd 100644 --- a/activerecord/test/cases/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -195,6 +195,24 @@ module ActiveRecord end end + class MysqlDBCollationTest < ActiveRecord::TestCase + def setup + @connection = stub(:create_database => true) + @configuration = { + 'adapter' => 'mysql', + 'database' => 'my-app-db' + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + @connection.expects(:collation) + ActiveRecord::Tasks::DatabaseTasks.collation @configuration + end + end + class MySQLStructureDumpTest < ActiveRecord::TestCase def setup @connection = stub(:structure_dump => true) diff --git a/activerecord/test/cases/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index e8769bd4df..df1075d816 100644 --- a/activerecord/test/cases/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -38,6 +38,14 @@ module ActiveRecord merge('encoding' => 'latin') end + def test_creates_database_with_given_collate_and_ctype + @connection.expects(:create_database). + with('my-app-db', @configuration.merge('encoding' => 'utf8', 'collate' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8')) + + ActiveRecord::Tasks::DatabaseTasks.create @configuration. + merge('collate' => 'ja_JP.UTF8', 'ctype' => 'ja_JP.UTF8') + end + def test_establishes_connection_to_new_database ActiveRecord::Base.expects(:establish_connection).with(@configuration) @@ -151,6 +159,24 @@ module ActiveRecord end end + class PostgreSQLDBCollationTest < ActiveRecord::TestCase + def setup + @connection = stub(:create_database => true) + @configuration = { + 'adapter' => 'postgresql', + 'database' => 'my-app-db' + } + + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + @connection.expects(:collate) + ActiveRecord::Tasks::DatabaseTasks.collation @configuration + end + end + class PostgreSQLStructureDumpTest < ActiveRecord::TestCase def setup @connection = stub(:structure_dump => true) diff --git a/activerecord/test/cases/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb index b5557fc953..06a1d0ffc2 100644 --- a/activerecord/test/cases/sqlite_rake_test.rb +++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb @@ -124,6 +124,27 @@ module ActiveRecord end end + class SqliteDBCollationTest < ActiveRecord::TestCase + def setup + @database = 'db_create.sqlite3' + @connection = stub :connection + @configuration = { + 'adapter' => 'sqlite3', + 'database' => @database + } + + File.stubs(:exist?).returns(false) + ActiveRecord::Base.stubs(:connection).returns(@connection) + ActiveRecord::Base.stubs(:establish_connection).returns(true) + end + + def test_db_retrieves_collation + assert_raise NoMethodError do + ActiveRecord::Tasks::DatabaseTasks.collation @configuration, '/rails/root' + end + end + end + class SqliteStructureDumpTest < ActiveRecord::TestCase def setup @database = "db_create.sqlite3" diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 9fefa9ad3a..43cde4ab73 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -64,7 +64,6 @@ class AuditLog < ActiveRecord::Base belongs_to :unvalidated_developer, :class_name => 'Developer' end -DeveloperSalary = Struct.new(:amount) class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base self.table_name = 'developers' has_and_belongs_to_many :projects, :join_table => 'developers_projects', :foreign_key => 'developer_id' diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 65b6f9f227..24a43d7ece 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -32,4 +32,13 @@ CREATE TABLE collation_tests ( ) CHARACTER SET utf8 COLLATE utf8_general_ci SQL + ActiveRecord::Base.connection.execute <<-SQL +DROP TABLE IF EXISTS enum_tests; +SQL + + ActiveRecord::Base.connection.execute <<-SQL +CREATE TABLE enum_tests ( + enum_column ENUM('true','false') +) +SQL end diff --git a/activerecord/test/schema/mysql_specific_schema.rb b/activerecord/test/schema/mysql_specific_schema.rb index 7d324f98c4..802c08b819 100644 --- a/activerecord/test/schema/mysql_specific_schema.rb +++ b/activerecord/test/schema/mysql_specific_schema.rb @@ -43,4 +43,14 @@ CREATE TABLE collation_tests ( ) CHARACTER SET utf8 COLLATE utf8_general_ci SQL + ActiveRecord::Base.connection.execute <<-SQL +DROP TABLE IF EXISTS enum_tests; +SQL + + ActiveRecord::Base.connection.execute <<-SQL +CREATE TABLE enum_tests ( + enum_column ENUM('true','false') +) +SQL + end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index ba01ef35f0..00688eab37 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -437,7 +437,6 @@ ActiveRecord::Schema.define do t.string :name t.column :updated_at, :datetime t.column :happy_at, :datetime - t.column :eats_at, :time t.string :essay_id end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 00fcfe2001..87b956e3fc 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,5 +1,7 @@ ## Rails 4.0.0 (unreleased) ## +* AS::Callbacks: deprecate usage of filter object with `#before` and `#after` methods as `around` callback. *Bogdan Gusiev* + * Add `Time#prev_quarter' and 'Time#next_quarter' short-hands for months_ago(3) and months_since(3). *SungHee Kang* * Remove obsolete and unused `require_association` method from dependencies. *fxn* diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 0aa3efbb63..6cc875c69a 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -283,7 +283,8 @@ module ActiveSupport filter.singleton_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{kind}(context, &block) filter(context, &block) end RUBY_EVAL - elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around + elsif filter.respond_to?(:before) && filter.respond_to?(:after) && kind == :around && !filter.respond_to?(:around) + ActiveSupport::Deprecation.warn("Filter object with #before and #after methods is deprecated. Define #around method instead.") def filter.around(context) should_continue = before(context) yield if should_continue diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index c94a8d99f4..b927b58a9a 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -56,7 +56,7 @@ module ActiveSupport # these from +Host+ directly including +Foo+ in +Bar+: # # module Bar - # include Foo + # include Foo # def self.included(base) # base.method_injected_by_foo # end @@ -94,9 +94,8 @@ module ActiveSupport # class Host # include Bar # works, Bar takes care now of its dependencies # end - # module Concern - def self.extended(base) + def self.extended(base) #:nodoc: base.instance_variable_set("@_dependencies", []) end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 4fb8c7af3f..307ae40398 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -98,15 +98,15 @@ module ActiveSupport names.each do |name| raise NameError.new('invalid config attribute name') unless name =~ /^[_A-Za-z]\w*$/ - reader, line = "def #{name}; config.#{name}; end", __LINE__ - writer, line = "def #{name}=(value); config.#{name} = value; end", __LINE__ + reader, reader_line = "def #{name}; config.#{name}; end", __LINE__ + writer, writer_line = "def #{name}=(value); config.#{name} = value; end", __LINE__ - singleton_class.class_eval reader, __FILE__, line - singleton_class.class_eval writer, __FILE__, line + singleton_class.class_eval reader, __FILE__, reader_line + singleton_class.class_eval writer, __FILE__, writer_line unless options[:instance_accessor] == false - class_eval reader, __FILE__, line unless options[:instance_reader] == false - class_eval writer, __FILE__, line unless options[:instance_writer] == false + class_eval reader, __FILE__, reader_line unless options[:instance_reader] == false + class_eval writer, __FILE__, writer_line unless options[:instance_writer] == false end end end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 1e0de651c7..d6ae031c0d 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,6 +1,5 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/reverse_merge' require 'active_support/core_ext/string/inflections' class Array @@ -62,14 +61,10 @@ class Array :last_word_connector => ', and ' } if defined?(I18n) - namespace = 'support.array.' - default_connectors.each_key do |name| - i18n_key = (namespace + name.to_s).to_sym - default_connectors[name] = I18n.translate i18n_key, :locale => options[:locale] - end + i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {}) + default_connectors.merge!(i18n_connectors) end - - options.reverse_merge! default_connectors + options = default_connectors.merge!(options) case length when 0 diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index 8e728691c6..e753e36124 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -7,7 +7,7 @@ class Hash # # => { "NAME" => "Rob", "AGE" => "28" } def transform_keys result = {} - keys.each do |key| + each_key do |key| result[yield(key)] = self[key] end result diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 28c8b53b78..e8623a9c54 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,5 +1,6 @@ require 'active_support/duration' require 'active_support/core_ext/time/conversions' +require 'active_support/time_with_zone' class Time COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 48c39d9370..9decbaef32 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -101,7 +101,9 @@ module ActiveSupport end def updated_at(paths) - @updated_at || paths.map { |path| File.mtime(path) }.max || Time.at(0) + time_now = Time.now + @updated_at || paths.map { |path| File.mtime(path) }. + reject { |time| time > time_now }.max || Time.at(0) end def compile_glob(hash) diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index c04c2ed15b..ca2d8cb270 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,3 +1,5 @@ +require 'active_support/inflector/inflections' + module ActiveSupport Inflector.inflections do |inflect| inflect.plural(/$/, 's') diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 600e353812..c9e50a9462 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -2,6 +2,8 @@ require 'active_support/core_ext/array/prepend_and_append' module ActiveSupport module Inflector + extend self + # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional # inflection rules. # diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 2acc6ddee5..c14a43de0d 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require 'active_support/inflector/inflections' +require 'active_support/inflections' module ActiveSupport # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without, diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index c736041066..99f6489adb 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -7,9 +7,14 @@ module ActiveSupport module NumberHelper extend self + DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, + -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } + DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",", :precision => 2, :significant => false, :strip_insignificant_zeros => false } + STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] + # Formats a +number+ into a US phone number (e.g., (555) # 123-9876). You can customize the format in the +options+ hash. # @@ -116,8 +121,7 @@ module ActiveSupport number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '') end - formatted_number = format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit) - formatted_number + format.gsub('%n', self.number_to_rounded(number, options)).gsub('%u', unit) end # Formats a +number+ as a percentage string (e.g., 65%). You can @@ -160,9 +164,7 @@ module ActiveSupport options = defaults.merge!(options) format = options[:format] || "%n%" - - formatted_number = format.gsub('%n', self.number_to_rounded(number, options)) - formatted_number + format.gsub('%n', self.number_to_rounded(number, options)) end # Formats a +number+ with grouped thousands using +delimiter+ @@ -242,10 +244,9 @@ module ActiveSupport # number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.') # # => 1.111,23 def number_to_rounded(number, options = {}) - options = options.symbolize_keys - return number unless valid_float?(number) - number = Float(number) + number = Float(number) + options = options.symbolize_keys defaults = format_translations('precision', options[:locale]) options = defaults.merge!(options) @@ -254,7 +255,7 @@ module ActiveSupport significant = options.delete :significant strip_insignificant_zeros = options.delete :strip_insignificant_zeros - if significant and precision > 0 + if significant && precision > 0 if number == 0 digits, rounded_number = 1, 0 else @@ -263,10 +264,10 @@ module ActiveSupport digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed end precision -= digits - precision = precision > 0 ? precision : 0 #don't let it be negative + precision = 0 if precision < 0 # don't let it be negative else rounded_number = BigDecimal.new(number.to_s).round(precision).to_f - rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros + rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros end formatted_number = self.number_to_delimited("%01.#{precision}f" % rounded_number, options) if strip_insignificant_zeros @@ -277,8 +278,6 @@ module ActiveSupport end end - STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze - # Formats the bytes in +number+ into a more understandable # representation (e.g., giving it 1500 yields 1.5 KB). This # method is useful for reporting file sizes to users. You can @@ -356,9 +355,6 @@ module ActiveSupport end end - DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, - -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze - # Pretty prints (formats and approximates) a number in a way it # is more readable by humans (eg.: 1200000000 becomes "1.2 # Billion"). This is useful for numbers that can get very large @@ -527,6 +523,5 @@ module ActiveSupport false end private_module_and_instance_method :valid_float? - end end diff --git a/activesupport/lib/active_support/testing/declarative.rb b/activesupport/lib/active_support/testing/declarative.rb index 1c05d45888..508e37254a 100644 --- a/activesupport/lib/active_support/testing/declarative.rb +++ b/activesupport/lib/active_support/testing/declarative.rb @@ -2,7 +2,7 @@ module ActiveSupport module Testing module Declarative - def self.extended(klass) + def self.extended(klass) #:nodoc: klass.class_eval do unless method_defined?(:describe) diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 50ec50ca52..27d444fd91 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -64,7 +64,7 @@ module ActiveSupport end end - def self.included klass + def self.included(klass) #:nodoc: klass.extend(Module.new { def test_methods ParallelEach.new super diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 58835c0ac5..9dfa2cbf11 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -90,6 +90,12 @@ class ArrayExtToSentenceTests < ActiveSupport::TestCase def test_one_non_string_element assert_equal '1', [1].to_sentence end + + def test_does_not_modify_given_hash + options = { words_connector: ' ' } + assert_equal "one two, and three", ['one', 'two', 'three'].to_sentence(options) + assert_equal({ words_connector: ' ' }, options) + end end class ArrayExtToSTests < ActiveSupport::TestCase diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb index 8adff5de8d..bd1df0f858 100644 --- a/activesupport/test/file_update_checker_test.rb +++ b/activesupport/test/file_update_checker_test.rb @@ -48,6 +48,21 @@ class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase assert_equal 1, i end + def test_should_be_robust_to_handle_files_with_wrong_modified_time + i = 0 + now = Time.now + time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future + File.utime time, time, FILES[2] + + checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } + + sleep(1) + FileUtils.touch(FILES[0..1]) + + assert checker.execute_if_updated + assert_equal 1, i + end + def test_should_cache_updated_result_until_execute i = 0 checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 } diff --git a/activesupport/test/i18n_test.rb b/activesupport/test/i18n_test.rb index 4f2027f4eb..ddbba444cf 100644 --- a/activesupport/test/i18n_test.rb +++ b/activesupport/test/i18n_test.rb @@ -97,4 +97,9 @@ class I18nTest < ActiveSupport::TestCase I18n.backend.store_translations 'en', :support => { :array => { :two_words_connector => default_two_words_connector } } I18n.backend.store_translations 'en', :support => { :array => { :last_word_connector => default_last_word_connector } } end + + def test_to_sentence_with_empty_i18n_store + I18n.backend.store_translations 'empty', {} + assert_equal 'a, b, and c', %w[a b c].to_sentence(locale: 'empty') + end end diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb new file mode 100644 index 0000000000..e07198027b --- /dev/null +++ b/activesupport/test/number_helper_i18n_test.rb @@ -0,0 +1,114 @@ +require 'abstract_unit' +require 'active_support/number_helper' + +module ActiveSupport + class NumberHelperI18nTest < ActiveSupport::TestCase + include ActiveSupport::NumberHelper + + def setup + I18n.backend.store_translations 'ts', + :number => { + :format => { :precision => 3, :delimiter => ',', :separator => '.', :significant => false, :strip_insignificant_zeros => false }, + :currency => { :format => { :unit => '&$', :format => '%u - %n', :negative_format => '(%u - %n)', :precision => 2 } }, + :human => { + :format => { + :precision => 2, + :significant => true, + :strip_insignificant_zeros => true + }, + :storage_units => { + :format => "%n %u", + :units => { + :byte => "b", + :kb => "k" + } + }, + :decimal_units => { + :format => "%n %u", + :units => { + :deci => {:one => "Tenth", :other => "Tenths"}, + :unit => "u", + :ten => {:one => "Ten", :other => "Tens"}, + :thousand => "t", + :million => "m", + :billion =>"b", + :trillion =>"t" , + :quadrillion =>"q" + } + } + }, + :percentage => { :format => {:delimiter => '', :precision => 2, :strip_insignificant_zeros => true} }, + :precision => { :format => {:delimiter => '', :significant => true} } + }, + :custom_units_for_number_to_human => {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} + end + + def test_number_to_i18n_currency + assert_equal("&$ - 10.00", number_to_currency(10, :locale => 'ts')) + assert_equal("(&$ - 10.00)", number_to_currency(-10, :locale => 'ts')) + assert_equal("-10.00 - &$", number_to_currency(-10, :locale => 'ts', :format => "%n - %u")) + end + + def test_number_to_currency_with_empty_i18n_store + I18n.backend.store_translations 'empty', {} + + assert_equal("$10.00", number_to_currency(10, :locale => 'empty')) + assert_equal("-$10.00", number_to_currency(-10, :locale => 'empty')) + end + + def test_number_to_currency_without_currency_negative_format + I18n.backend.store_translations 'no_negative_format', :number => { + :currency => { :format => { :unit => '@', :format => '%n %u' } } + } + + assert_equal("-10.00 @", number_to_currency(-10, :locale => 'no_negative_format')) + end + + def test_number_with_i18n_precision + #Delimiter was set to "" + assert_equal("10000", number_to_rounded(10000, :locale => 'ts')) + + #Precision inherited and significant was set + assert_equal("1.00", number_to_rounded(1.0, :locale => 'ts')) + end + + def test_number_with_i18n_delimiter + #Delimiter "," and separator "." + assert_equal("1,000,000.234", number_to_delimited(1000000.234, :locale => 'ts')) + end + + def test_number_to_i18n_percentage + # to see if strip_insignificant_zeros is true + assert_equal("1%", number_to_percentage(1, :locale => 'ts')) + # precision is 2, significant should be inherited + assert_equal("1.24%", number_to_percentage(1.2434, :locale => 'ts')) + # no delimiter + assert_equal("12434%", number_to_percentage(12434, :locale => 'ts')) + end + + def test_number_to_i18n_human_size + #b for bytes and k for kbytes + assert_equal("2 k", number_to_human_size(2048, :locale => 'ts')) + assert_equal("42 b", number_to_human_size(42, :locale => 'ts')) + end + + def test_number_to_human_with_default_translation_scope + #Using t for thousand + assert_equal "2 t", number_to_human(2000, :locale => 'ts') + #Significant was set to true with precision 2, using b for billion + assert_equal "1.2 b", number_to_human(1234567890, :locale => 'ts') + #Using pluralization (Ten/Tens and Tenth/Tenths) + assert_equal "1 Tenth", number_to_human(0.1, :locale => 'ts') + assert_equal "1.3 Tenth", number_to_human(0.134, :locale => 'ts') + assert_equal "2 Tenths", number_to_human(0.2, :locale => 'ts') + assert_equal "1 Ten", number_to_human(10, :locale => 'ts') + assert_equal "1.2 Ten", number_to_human(12, :locale => 'ts') + assert_equal "2 Tens", number_to_human(20, :locale => 'ts') + end + + def test_number_to_human_with_custom_translation_scope + #Significant was set to true with precision 2, with custom translated units + assert_equal "4.3 cm", number_to_human(0.0432, :locale => 'ts', :units => :custom_units_for_number_to_human) + end + end +end diff --git a/guides/source/4_0_release_notes.textile b/guides/source/4_0_release_notes.textile index 1ed6006b6b..23c5220c24 100644 --- a/guides/source/4_0_release_notes.textile +++ b/guides/source/4_0_release_notes.textile @@ -70,7 +70,7 @@ h3. Railties * Improved +rake routes+ output for redirects. -* Load all environments available in config.paths["config/environments"]. +* Load all environments available in <tt>config.paths["config/environments"]</tt>. * The application generator generates <tt>public/humans.txt</tt> with some basic data. @@ -92,12 +92,11 @@ console do end </ruby> -* Add convenience hide! method to Rails generators to hide current generator - namespace from showing when running rails generate. +* Add a convenience method <tt>hide!</tt> to Rails generators to hide the current generator namespace from showing when running <tt>rails generate</tt>. * Scaffold now uses +content_tag_for+ in <tt>index.html.erb</tt>. -* <tt>Rails::Plugin</tt> is removed. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. +* <tt>Rails::Plugin</tt> is removed. Instead of adding plugins to <tt>vendor/plugins</tt>, use gems or bundler with path or git dependencies. h4(#railties_deprecations). Deprecations @@ -117,15 +116,15 @@ h4. Action Controller * +respond_to+ and +respond_with+ now raise <tt>ActionController::UnknownFormat</tt> instead of directly returning head 406. The exception is rescued and converted to 406 in the exception handling middleware. -* JSONP now uses mimetype <tt>application/javascript</tt> instead of <tt>application/json</tt>. +* JSONP now uses <tt>application/javascript</tt> instead of <tt>application/json</tt> as the MIME type. * Session arguments passed to process calls in functional tests are now merged into the existing session, whereas previously they would replace the existing session. This change may break some existing tests if they are asserting the exact contents of the session but should not break existing tests that only assert individual keys. -* Forms of persisted records use always PATCH (via the _method hack). +* Forms of persisted records use always PATCH (via the +_method+ hack). -* For resources, both PATCH and PUT are routed to the update action. +* For resources, both PATCH and PUT are routed to the +update+ action. -* Don't ignore +force_ssl+ in development. This is a change of behavior - use a :if condition to recreate the old behavior. +* Don't ignore +force_ssl+ in development. This is a change of behavior - use an <tt>:if</tt> condition to recreate the old behavior. <ruby> class AccountsController < ApplicationController @@ -139,52 +138,46 @@ end h5(#actioncontroller_deprecations). Deprecations -* Deprecated ActionController::Integration in favour of ActionDispatch::Integration +* Deprecated <tt>ActionController::Integration</tt> in favour of <tt>ActionDispatch::Integration</tt>. -* Deprecated ActionController::IntegrationTest in favour of ActionDispatch::IntegrationTest +* Deprecated <tt>ActionController::IntegrationTest</tt> in favour of <tt>ActionDispatch::IntegrationTest</tt>. -* Deprecated ActionController::PerformanceTest in favour of ActionDispatch::PerformanceTest +* Deprecated <tt>ActionController::PerformanceTest</tt> in favour of <tt>ActionDispatch::PerformanceTest</tt>. -* Deprecated ActionController::AbstractRequest in favour of ActionDispatch::Request +* Deprecated <tt>ActionController::AbstractRequest</tt> in favour of <tt>ActionDispatch::Request</tt>. -* Deprecated ActionController::Request in favour of ActionDispatch::Request +* Deprecated <tt>ActionController::Request</tt> in favour of <tt>ActionDispatch::Request</tt>. -* Deprecated ActionController::AbstractResponse in favour of ActionDispatch::Response +* Deprecated <tt>ActionController::AbstractResponse</tt> in favour of <tt>ActionDispatch::Response</tt>. -* Deprecated ActionController::Response in favour of ActionDispatch::Response +* Deprecated <tt>ActionController::Response</tt> in favour of <tt>ActionDispatch::Response</tt>. -* Deprecated ActionController::Routing in favour of ActionDispatch::Routing +* Deprecated <tt>ActionController::Routing</tt> in favour of <tt>ActionDispatch::Routing</tt>. h4. Action Dispatch * Added <tt>ActionDispatch::SSL</tt> middleware that when included force all the requests to be under HTTPS protocol. -* Copy literal route constraints to defaults so that url generation know about them. The copied constraints are :protocol, :subdomain, :domain, :host and :port. +* Copy literal route constraints to defaults so that url generation know about them. The copied constraints are <tt>:protocol</tt>, <tt>:subdomain</tt>, <tt>:domain</tt>, <tt>:host</tt> and <tt>:port</tt>. * Allows +assert_redirected_to+ to match against a regular expression. -* Add backtrace to development routing error page. +* Adds a backtrace to the routing error page in development. * +assert_generates+, +assert_recognizes+, and +assert_routing+ all raise +Assertion+ instead of +RoutingError+. * Allows the route helper root to take a string argument. For example, <tt>root 'pages#main'</tt> as a shortcut for <tt>root to: 'pages#main'</tt>. -* Adds support for the PATCH verb: - Request objects respond to patch?. - Routes have a new patch method, and understand :patch in the - existing places where a verb is configured, like :via. - New method patch available in functional tests. - If :patch is the default verb for updates, edits are - tunneled as PATCH rather than as PUT, and routing acts accordingly. - New method patch_via_redirect available in integration tests. +* Adds support for the PATCH verb: Request objects respond to <tt>patch?</tt>. Routes now have a new +patch+ method, and understand +:patch+ in the existing places where a verb is configured, like <tt>:via</tt>. Functional tests have a new method +patch+ and integration tests have a new method +patch_via_redirect+. +If <tt>:patch</tt> is the default verb for updates, edits are tunneled as <tt>PATCH</tt> rather than as <tt>PUT</tt> and routing acts accordingly. * Integration tests support the OPTIONS method. -* +expires_in+ accepts a +must_revalidate+ flag. If true, "must-revalidate" is added to the Cache-Control header. +* +expires_in+ accepts a +must_revalidate+ flag. If true, "must-revalidate" is added to the <tt>Cache-Control</tt> header. -* Default responder will now always use your overridden block in respond_with to render your response. +* Default responder will now always use your overridden block in <tt>respond_with</tt> to render your response. -* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to check that info. +* Turn off verbose mode of <tt>rack-cache</tt>, we still have <tt>X-Rack-Cache</tt> to check that info. * Include mounted_helpers (helpers for accessing mounted engines) in <tt>ActionDispatch::IntegrationTest</tt> by default. @@ -192,9 +185,9 @@ h5(#actiondispatch_deprecations). Deprecations h4. Action View -* Make current object and counter (when it applies) variables accessible when rendering templates with :object / :collection. +* Make current object and counter (when it applies) variables accessible when rendering templates with <tt>:object</tt> or <tt>:collection</tt>. -* Allow to lazy load +default_form_builder+ by passing a String instead of a constant. +* Allow to lazy load +default_form_builder+ by passing a string instead of a constant. * Add index method to +FormBuilder+ class. @@ -204,9 +197,9 @@ h4. Action View * Remove <tt>:mouseover</tt> option from +image_tag+ helper. -* Templates without a handler extension now raises a deprecation warning but still defaults to ERb. In future releases, it will simply return the template content. +* Templates without a handler extension now raises a deprecation warning but still defaults to +ERb+. In future releases, it will simply return the template content. -* Add divider option to +grouped_options_for_select+ to generate a separator optgroup automatically, and deprecate prompt as third argument, in favor of using an options hash. +* Add a +divider+ option to +grouped_options_for_select+ to generate a separator optgroup automatically, and deprecate prompt as third argument, in favor of using an options hash. * Add +time_field+ and +time_field_tag+ helpers which render an <tt>input[type="time"]</tt> tag. @@ -237,9 +230,9 @@ h4. Action View * Remove +button_to_function+ and +link_to_function+ helpers. -* truncate now always returns an escaped HTML-safe string. The option :escape can be used as false to not escape the result. +* +truncate+ now always returns an escaped HTML-safe string. The option <tt>:escape</tt> can be used as +false+ to not escape the result. -* truncate now accepts a block to show extra content when the text is truncated. +* +truncate+ now accepts a block to show extra content when the text is truncated. * Add +week_field+, +week_field_tag+, +month_field+, +month_field_tag+, +datetime_local_field+, +datetime_local_field_tag+, +datetime_field+ and +datetime_field_tag+ helpers. @@ -253,7 +246,7 @@ h4. Action View * Adds +image_url+, +javascript_url+, +stylesheet_url+, +audio_url+, +video_url+, and +font_url+ to assets tag helper. These URL helpers will return the full path to your assets. This is useful when you are going to reference this asset from external host. -* Allow +value_method+ and +text_method+ arguments from +collection_select+ and +options_from_collection_for_select+ to receive an object that responds to :call, such as a proc, to evaluate the option in the current element context. This works the same way with +collection_radio_buttons+ and +collection_check_boxes+. +* Allow +value_method+ and +text_method+ arguments from +collection_select+ and +options_from_collection_for_select+ to receive an object that responds to <tt>:call</tt> such as a proc, to evaluate the option in the current element context. This works the same way with +collection_radio_buttons+ and +collection_check_boxes+. * Add +date_field+ and +date_field_tag+ helpers which render an <tt>input[type="date"]</tt> tag. @@ -271,7 +264,7 @@ collection_check_boxes :post, :author_ids, Author.all, :id, :name The label/check_box pairs can be customized with a block. -* Add +collection_radio_buttons+ form helper, similar to collection_select: +* Add +collection_radio_buttons+ form helper, similar to +collection_select+: <ruby> collection_radio_buttons :post, :author_id, Author.all, :id, :name @@ -284,15 +277,15 @@ collection_radio_buttons :post, :author_id, Author.all, :id, :name The label/radio_button pairs can be customized with a block. -* +check_box+ with :form HTML5 attribute will now replicate the :form attribute to the hidden field as well. +* +check_box+ with an HTML5 attribute +:form+ will now replicate the +:form+ attribute to the hidden field as well. -* label form helper accepts :for => nil to not generate the attribute. +* label form helper accepts <tt>:for => nil</tt> to not generate the attribute. -* Add :format option to +number_to_percentage+. +* Add <tt>:format</tt> option to +number_to_percentage+. -* Add <tt>config.action_view.logger</tt> to configure logger for Action View. +* Add <tt>config.action_view.logger</tt> to configure logger for +Action View+. -* +check_box+ helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form. This is a behavior change, previously the hidden tag had a value of the disabled checkbox. +* +check_box+ helper with <tt>:disabled => true</tt> will generate a +disabled+ hidden field to conform with the HTML convention where disabled fields are not submitted with the form. This is a behavior change, previously the hidden tag had a value of the disabled checkbox. * +favicon_link_tag+ helper will now use the favicon in <tt>app/assets</tt> by default. @@ -308,7 +301,7 @@ h3. Active Record * Allow blocks for count with <tt>ActiveRecord::Relation</tt>, to work similar as <tt>Array#count</tt>: <tt>Person.where("age > 26").count { |person| person.gender == 'female' }</tt> -* Added support to <tt>CollectionAssociation#delete</tt> for passing fixnum or string values as record ids. This finds the records responding to the id and executes delete on them. +* Added support to <tt>CollectionAssociation#delete</tt> for passing fixnum or string values as record ids. This finds the records responding to the ids and deletes them. <ruby> class Person < ActiveRecord::Base @@ -327,11 +320,11 @@ person.pets.delete(2, 3) # => [#<Pet id: 2>, #<Pet id: 3>] <ruby>store :settings, accessors: [ :color, :homepage ], coder: JSON</ruby> -* mysql and mysql2 connections will set SQL_MODE=STRICT_ALL_TABLES by default to avoid silent data loss. This can be disabled by specifying strict: false in your database.yml. +* +mysql+ and +mysql2+ connections will set <tt>SQL_MODE=STRICT_ALL_TABLES</tt> by default to avoid silent data loss. This can be disabled by specifying <tt>strict: false</tt> in <tt>config/database.yml</tt>. -* Added default order to first to assure consistent results among diferent database engines. Introduced take as a replacement to the old behavior of first. +* Added default order to <tt>ActiveRecord::Base#first</tt> to assure consistent results among diferent database engines. Introduced <tt>ActiveRecord::Base#take</tt> as a replacement to the old behavior. -* Added an :index option to automatically create indexes for references and belongs_to statements in migrations. The references and belongs_to methods now support an index option that receives either a boolean value or an options hash that is identical to options available to the add_index method: +* Added an <tt>:index</tt> option to automatically create indexes for +references+ and +belongs_to+ statements in migrations. This can be either a boolean or a hash that is identical to options available to the +add_index+ method: <ruby> create_table :messages do |t| @@ -352,7 +345,7 @@ Generators have also been updated to use the new syntax. * Added bang methods for mutating <tt>ActiveRecord::Relation</tt> objects. For example, while <tt>foo.where(:bar)</tt> will return a new object leaving foo unchanged, <tt>foo.where!(:bar)</tt> will mutate the foo object. -* Added #find_by and #find_by! to mirror the functionality provided by dynamic finders in a way that allows dynamic input more easily: +* Added <tt>#find_by</tt> and <tt>#find_by!</tt> to mirror the functionality provided by dynamic finders in a way that allows dynamic input more easily: <ruby> Post.find_by name: 'Spartacus', rating: 4 @@ -364,7 +357,7 @@ Post.find_by! name: 'Spartacus' * Remove IdentityMap - IdentityMap has never graduated to be an "enabled-by-default" feature, due to some inconsistencies with associations, as described in this commit: https://github.com/rails/rails/commit/302c912bf6bcd0fa200d964ec2dc4a44abe328a6. Hence the removal from the codebase, until such issues are fixed. -* Added a feature to dump/load internal state of SchemaCache instance because we want to boot rails more quickly when we have many models. +* Added a feature to dump/load internal state of +SchemaCache+ instance because we want to boot more quickly when we have many models. <ruby> # execute rake task. @@ -382,14 +375,14 @@ RAILS_ENV=production bundle exec rake db:schema:cache:clear => remove db/schema_cache.dump </ruby> -* Added support for partial indices to PostgreSQL adapter. +* Added support for partial indices to +PostgreSQL+ adapter. * The +add_index+ method now supports a +where+ option that receives a string with the partial index criteria. -* Implemented <tt>ActiveRecord::Relation#none</tt> method which returns a chainable relation with zero records (an instance of the NullRelation class). Any subsequent condition chained to the returned relation will continue generating an empty relation and will not fire any query to the database. - * Added the <tt>ActiveRecord::NullRelation</tt> class implementing the null object pattern for the Relation class. +* Implemented <tt>ActiveRecord::Relation#none</tt> method which returns a chainable relation with zero records (an instance of the +NullRelation+ class). Any subsequent condition chained to the returned relation will continue generating an empty relation and will not fire any query to the database. + * Added +create_join_table+ migration helper to create HABTM join tables. <ruby> @@ -401,7 +394,7 @@ create_join_table :products, :categories # end </ruby> -* The primary key is always initialized in the @attributes hash to nil (unless another value has been specified). +* The primary key is always initialized in the +@attributes+ hash to nil (unless another value has been specified). * In previous releases, the following would generate a single query with an OUTER JOIN comments, rather than two separate queries: diff --git a/guides/source/action_mailer_basics.textile b/guides/source/action_mailer_basics.textile index ebe774fbef..7c61cc4a8d 100644 --- a/guides/source/action_mailer_basics.textile +++ b/guides/source/action_mailer_basics.textile @@ -457,6 +457,7 @@ The following configuration options are best made in one of the environment file |+delivery_method+|Defines a delivery method. Possible values are <tt>:smtp</tt> (default), <tt>:sendmail</tt>, <tt>:file</tt> and <tt>:test</tt>.| |+perform_deliveries+|Determines whether deliveries are actually carried out when the +deliver+ method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing.| |+deliveries+|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| +|+async+|Setting this flag will turn on asynchronous message sending, message rendering and delivery will be pushed to <tt>Rails.queue</tt> for processing.| h4. Example Action Mailer Configuration @@ -514,3 +515,33 @@ end </ruby> In the test we send the email and store the returned object in the +email+ variable. We then ensure that it was sent (the first assert), then, in the second batch of assertions, we ensure that the email does indeed contain what we expect. + +h3. Asynchronous + +You can turn on application-wide asynchronous message sending by adding to your <tt>config/application.rb</tt> file: + +<ruby> +config.action_mailer.async = true +</ruby> + +Alternatively you can turn on async within specific mailers: + +<ruby> +class WelcomeMailer < ActionMailer::Base + self.async = true +end +</ruby> + +h4. Custom Queues + +If you need a different queue than <tt>Rails.queue</tt> for your mailer you can override <tt>ActionMailer::Base#queue</tt>: + +<ruby> +class WelcomeMailer < ActionMailer::Base + def queue + MyQueue.new + end +end +</ruby> + +Your custom queue should expect a job that responds to <tt>#run</tt>. diff --git a/guides/source/getting_started.textile b/guides/source/getting_started.textile index a489469a45..07419d11b4 100644 --- a/guides/source/getting_started.textile +++ b/guides/source/getting_started.textile @@ -20,13 +20,7 @@ application from scratch. It does not assume that you have any prior experience with Rails. However, to get the most out of it, you need to have some prerequisites installed: -* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.8.7 or higher - -TIP: Note that Ruby 1.8.7 p248 and p249 have marshaling bugs that crash Rails -3.0. Ruby Enterprise Edition have these fixed since release 1.8.7-2010.02 -though. On the 1.9 front, Ruby 1.9.1 is not usable because it outright segfaults -on Rails 3.0, so if you want to use Rails 3 with 1.9.x jump on 1.9.2 or -1.9.3 for smooth sailing. +* The "Ruby":http://www.ruby-lang.org/en/downloads language version 1.9.3 or higher * The "RubyGems":http://rubyforge.org/frs/?group_id=126 packaging system ** If you want to learn more about RubyGems, please read the "RubyGems User Guide":http://docs.rubygems.org/read/book/1 @@ -216,12 +210,12 @@ You need to do this because Rails will serve any static file in the +public+ dir Next, you have to tell Rails where your actual home page is located. -Open the file +config/routes.rb+ in your editor. +Open the file +config/routes.rb+ in your editor. <ruby> Blog::Application.routes.draw do get "welcome/index" - + # The priority is based upon order of creation: # first created -> highest priority. # ... diff --git a/guides/source/routing.textile b/guides/source/routing.textile index 70b602a243..cffbf9bec4 100644 --- a/guides/source/routing.textile +++ b/guides/source/routing.textile @@ -851,24 +851,6 @@ end This will create routing helpers such as +magazine_periodical_ads_url+ and +edit_magazine_periodical_ad_path+. -h3. Breaking Up a Large Route File - -If you have a large route file that you would like to break up into multiple files, you can use the +#draw+ method in your router: - -<ruby> -draw :admin -</ruby> - -Then, create a file called +config/routes/admin.rb+. Name the file the same as the symbol passed to the +draw+ method. You can then use the normal routing DSL inside that file: - -<ruby> -# in config/routes/admin.rb - -namespace :admin do - resources :posts -end -</ruby> - h3. Inspecting and Testing Routes Rails offers facilities for inspecting and testing your routes. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index e19aa68407..7223270210 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,15 @@ ## Rails 4.0.0 (unreleased) ## +* Allow scaffold/model/migration generators to accept a `polymorphic` modifier + for `references`/`belongs_to`, for instance + + rails g model Product supplier:references{polymorphic} + + will generate the model with `belongs_to :supplier, polymorphic: true` + association and appropriate migration. + + *Aleksey Magusev* + * Set `config.active_record.migration_error` to `:page_load` for development *Richard Schneeman* * Add runner to Rails::Railtie as a hook called just after runner starts. *José Valim & kennyj* @@ -39,6 +49,9 @@ * Rails::Plugin has gone. Instead of adding plugins to vendor/plugins use gems or bundler with path or git dependencies. *Santiago Pastorino* +* Set config.action_mailer.async = true to turn on asynchronous + message delivery *Brian Cardarella* + ## Rails 3.2.2 (March 1, 2012) ## diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 32797ee657..d5ec2cbfd9 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -53,7 +53,6 @@ module Rails autoload :Bootstrap, 'rails/application/bootstrap' autoload :Configuration, 'rails/application/configuration' autoload :Finisher, 'rails/application/finisher' - autoload :Railties, 'rails/application/railties' autoload :RoutesReloader, 'rails/application/routes_reloader' class << self @@ -80,9 +79,42 @@ module Rails @routes_reloader = nil @env_config = nil @ordered_railties = nil + @railties = nil @queue = nil end + # Returns true if the application is initialized. + def initialized? + @initialized + end + + # Implements call according to the Rack API. It simples + # dispatch the request to the underlying middleware stack. + def call(env) + env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) + super(env) + end + + # Reload application routes regardless if they changed or not. + def reload_routes! + routes_reloader.reload! + end + + # Stores some of the Rails initial environment parameters which + # will be used by middlewares and engines to configure themselves. + def env_config + @env_config ||= super.merge({ + "action_dispatch.parameter_filter" => config.filter_parameters, + "action_dispatch.secret_token" => config.secret_token, + "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, + "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, + "action_dispatch.logger" => Rails.logger, + "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner + }) + end + + ## Rails internal API + # This method is called just after an application inherits from Rails::Application, # allowing the developer to load classes in lib and use them during application # configuration. @@ -106,18 +138,14 @@ module Rails require environment if environment end - # Reload application routes regardless if they changed or not. - def reload_routes! - routes_reloader.reload! - end - def routes_reloader #:nodoc: @routes_reloader ||= RoutesReloader.new end - # Returns an array of file paths appended with a hash of directories-extensions - # suitable for ActiveSupport::FileUpdateChecker API. - def watchable_args + # Returns an array of file paths appended with a hash of + # directories-extensions suitable for ActiveSupport::FileUpdateChecker + # API. + def watchable_args #:nodoc: files, dirs = config.watchable_files.dup, config.watchable_dirs.dup ActiveSupport::Dependencies.autoload_paths.each do |path| @@ -138,45 +166,64 @@ module Rails self end - def initialized? - @initialized + def initializers #:nodoc: + Bootstrap.initializers_for(self) + + railties_initializers(super) + + Finisher.initializers_for(self) end - # Load the application and its railties tasks and invoke the registered hooks. - # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. - def load_tasks(app=self) - initialize_tasks - super + def config #:nodoc: + @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) + end + + def queue #:nodoc: + @queue ||= build_queue + end + + def build_queue #:nodoc: + config.queue.new + end + + def to_app #:nodoc: self end - # Load the application console and invoke the registered hooks. - # Check <tt>Rails::Railtie.console</tt> for more info. - def load_console(app=self) - initialize_console + def helpers_paths #:nodoc: + config.helpers_paths + end + + def railties #:nodoc: + @railties ||= Rails::Railtie.subclasses.map(&:instance) + + Rails::Engine.subclasses.map(&:instance) + end + + protected + + alias :build_middleware_stack :app + + def run_tasks_blocks(app) #:nodoc: + railties.each { |r| r.run_tasks_blocks(app) } + super + require "rails/tasks" + task :environment do + $rails_rake_task = true + require_environment! + end + end + + def run_generators_blocks(app) #:nodoc: + railties.each { |r| r.run_generators_blocks(app) } super - self end - # Load the application runner and invoke the registered hooks. - # Check <tt>Rails::Railtie.runner</tt> for more info. - def load_runner(app=self) - initialize_runner + def run_runner_blocks(app) #:nodoc: + railties.each { |r| r.run_runner_blocks(app) } super - self end - # Stores some of the Rails initial environment parameters which - # will be used by middlewares and engines to configure themselves. - def env_config - @env_config ||= super.merge({ - "action_dispatch.parameter_filter" => config.filter_parameters, - "action_dispatch.secret_token" => config.secret_token, - "action_dispatch.show_exceptions" => config.action_dispatch.show_exceptions, - "action_dispatch.show_detailed_exceptions" => config.consider_all_requests_local, - "action_dispatch.logger" => Rails.logger, - "action_dispatch.backtrace_cleaner" => Rails.backtrace_cleaner - }) + def run_console_blocks(app) #:nodoc: + railties.each { |r| r.run_console_blocks(app) } + super end # Returns the ordered railties for this application considering railties_order. @@ -192,7 +239,7 @@ module Rails end end - all = (railties.all - order) + all = (railties - order) all.push(self) unless (all + order).include?(self) order.push(:all) unless order.include?(:all) @@ -202,46 +249,23 @@ module Rails end end - def initializers #:nodoc: - Bootstrap.initializers_for(self) + - super + - Finisher.initializers_for(self) - end - - def config #:nodoc: - @config ||= Application::Configuration.new(find_root_with_flag("config.ru", Dir.pwd)) - end - - def queue #:nodoc: - @queue ||= build_queue - end - - def build_queue # :nodoc: - config.queue.new - end - - def to_app - self - end - - def helpers_paths #:nodoc: - config.helpers_paths - end - - def call(env) - env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) - super(env) + def railties_initializers(current) #:nodoc: + initializers = [] + ordered_railties.each do |r| + if r == self + initializers += current + else + initializers += r.initializers + end + end + initializers end - protected - - alias :build_middleware_stack :app - - def reload_dependencies? + def reload_dependencies? #:nodoc: config.reload_classes_only_on_change != true || reloaders.map(&:updated?).any? end - def default_middleware_stack + def default_middleware_stack #:nodoc: ActionDispatch::MiddlewareStack.new.tap do |middleware| if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache require "action_dispatch/http/rack_cache" @@ -296,26 +320,7 @@ module Rails end end - def initialize_tasks #:nodoc: - self.class.rake_tasks do - require "rails/tasks" - task :environment do - $rails_rake_task = true - require_environment! - end - end - end - - def initialize_console #:nodoc: - require "pp" - require "rails/console/app" - require "rails/console/helpers" - end - - def initialize_runner #:nodoc: - end - - def build_original_fullpath(env) + def build_original_fullpath(env) #:nodoc: path_info = env["PATH_INFO"] query_string = env["QUERY_STRING"] script_name = env["SCRIPT_NAME"] diff --git a/railties/lib/rails/application/railties.rb b/railties/lib/rails/application/railties.rb deleted file mode 100644 index f20a9689de..0000000000 --- a/railties/lib/rails/application/railties.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rails/engine/railties' - -module Rails - class Application < Engine - class Railties < Rails::Engine::Railties - def all(&block) - @all ||= railties + engines - @all.each(&block) if block - @all - end - end - end -end diff --git a/railties/lib/rails/application/route_inspector.rb b/railties/lib/rails/application/routes_inspector.rb index 942c4f4789..6b2caf8277 100644 --- a/railties/lib/rails/application/route_inspector.rb +++ b/railties/lib/rails/application/routes_inspector.rb @@ -62,7 +62,7 @@ module Rails ## # This class is just used for displaying route information when someone # executes `rake routes`. People should not use this class. - class RouteInspector # :nodoc: + class RoutesInspector # :nodoc: def initialize @engines = Hash.new end diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index 19f616ec50..6f9a200aa9 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -3,13 +3,12 @@ require "active_support/core_ext/module/delegation" module Rails class Application class RoutesReloader - attr_reader :route_sets, :paths, :external_routes + attr_reader :route_sets, :paths delegate :execute_if_updated, :execute, :updated?, :to => :updater def initialize - @paths = [] - @route_sets = [] - @external_routes = [] + @paths = [] + @route_sets = [] end def reload! @@ -24,11 +23,7 @@ module Rails def updater @updater ||= begin - dirs = @external_routes.each_with_object({}) do |dir, hash| - hash[dir.to_s] = %w(rb) - end - - updater = ActiveSupport::FileUpdateChecker.new(paths, dirs) { reload! } + updater = ActiveSupport::FileUpdateChecker.new(paths) { reload! } updater.execute updater end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 806b553b81..383c159d3d 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -2,7 +2,6 @@ require 'rails/railtie' require 'active_support/core_ext/module/delegation' require 'pathname' require 'rbconfig' -require 'rails/engine/railties' module Rails # <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of @@ -338,27 +337,6 @@ module Rails # config.railties_order = [Blog::Engine, :main_app, :all] class Engine < Railtie autoload :Configuration, "rails/engine/configuration" - autoload :Railties, "rails/engine/railties" - - def initialize - @_all_autoload_paths = nil - @_all_load_paths = nil - @app = nil - @config = nil - @env_config = nil - @helpers = nil - @railties = nil - @routes = nil - super - end - - def load_generators(app=self) - initialize_generators - railties.all { |r| r.load_generators(app) } - Rails::Generators.configure!(app.config.generators) - super - self - end class << self attr_accessor :called_from, :isolated @@ -408,7 +386,7 @@ module Rails end unless mod.respond_to?(:railtie_routes_url_helpers) - define_method(:railtie_routes_url_helpers) { railtie.routes_url_helpers } + define_method(:railtie_routes_url_helpers) { railtie.routes.url_helpers } end end end @@ -417,34 +395,65 @@ module Rails # Finds engine with given path def find(path) expanded_path = File.expand_path path - Rails::Engine::Railties.engines.find { |engine| - File.expand_path(engine.root) == expanded_path - } + Rails::Engine.subclasses.each do |klass| + engine = klass.instance + return engine if File.expand_path(engine.root) == expanded_path + end + nil end end delegate :middleware, :root, :paths, :to => :config delegate :engine_name, :isolated?, :to => "self.class" - def load_tasks(app=self) - railties.all { |r| r.load_tasks(app) } + def initialize + @_all_autoload_paths = nil + @_all_load_paths = nil + @app = nil + @config = nil + @env_config = nil + @helpers = nil + @routes = nil super - paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end + # Load console and invoke the registered hooks. + # Check <tt>Rails::Railtie.console</tt> for more info. def load_console(app=self) - railties.all { |r| r.load_console(app) } - super + require "pp" + require "rails/console/app" + require "rails/console/helpers" + run_console_blocks(app) + self end + # Load Rails runner and invoke the registered hooks. + # Check <tt>Rails::Railtie.runner</tt> for more info. def load_runner(app=self) - railties.all { |r| r.load_runner(app) } - super + run_runner_blocks(app) + self end - def eager_load! - railties.all(&:eager_load!) + # Load Rake, railties tasks and invoke the registered hooks. + # Check <tt>Rails::Railtie.rake_tasks</tt> for more info. + def load_tasks(app=self) + require "rake" + run_tasks_blocks(app) + self + end + # Load rails generators and invoke the registered hooks. + # Check <tt>Rails::Railtie.generators</tt> for more info. + def load_generators(app=self) + require "rails/generators" + run_generators_blocks(app) + Rails::Generators.configure!(app.config.generators) + self + end + + # Eager load the application by loading all ruby + # files inside eager_load paths. + 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| @@ -453,10 +462,7 @@ module Rails end end - def railties - @railties ||= self.class::Railties.new(config) - end - + # Returns a module with all the helpers defined for the engine. def helpers @helpers ||= begin helpers = Module.new @@ -468,14 +474,12 @@ module Rails end end + # Returns all registered helpers paths. def helpers_paths paths["app/helpers"].existent end - def routes_url_helpers - routes.url_helpers - end - + # Returns the underlying rack application for this engine. def app @app ||= begin config.middleware = config.middleware.merge_into(default_middleware_stack) @@ -483,45 +487,33 @@ module Rails end end + # Returns the endpoint for this engine. If none is registered, + # defaults to an ActionDispatch::Routing::RouteSet. def endpoint self.class.endpoint || routes end + # Define the Rack API for this engine. def call(env) app.call(env.merge!(env_config)) end + # Defines additional Rack env configuration that is added on each call. def env_config @env_config ||= { 'action_dispatch.routes' => routes } end + # Defines the routes for this engine. If a block is given to + # routes, it is appended to the engine. def routes - @routes ||= ActionDispatch::Routing::RouteSet.new.tap do |routes| - routes.draw_paths.concat paths["config/routes"].paths - end - + @routes ||= ActionDispatch::Routing::RouteSet.new @routes.append(&Proc.new) if block_given? @routes end - def ordered_railties - railties.all + [self] - end - - def initializers - initializers = [] - ordered_railties.each do |r| - if r == self - initializers += super - else - initializers += r.initializers - end - end - initializers - end - + # Define the configuration object for the engine. def config @config ||= Engine::Configuration.new(find_root_with_flag("lib")) end @@ -560,12 +552,10 @@ module Rails initializer :add_routing_paths do |app| paths = self.paths["config/routes.rb"].existent - external_paths = self.paths["config/routes"].paths if routes? || paths.any? app.routes_reloader.paths.unshift(*paths) app.routes_reloader.route_sets << routes - app.routes_reloader.external_routes.unshift(*external_paths) end end @@ -626,7 +616,6 @@ module Rails else Rake::Task["app:railties:install:migrations"].invoke end - end end end @@ -634,19 +623,20 @@ module Rails protected - def initialize_generators - require "rails/generators" + def run_tasks_blocks(*) #:nodoc: + super + paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end - def routes? + def routes? #:nodoc: @routes end - def has_migrations? + def has_migrations? #:nodoc: paths["db/migrate"].existent.any? end - def find_root_with_flag(flag, default=nil) + def find_root_with_flag(flag, default=nil) #:nodoc: root_path = self.class.called_from while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") @@ -660,19 +650,19 @@ module Rails Pathname.new File.realpath root end - def default_middleware_stack + def default_middleware_stack #:nodoc: ActionDispatch::MiddlewareStack.new end - def _all_autoload_once_paths + def _all_autoload_once_paths #:nodoc: config.autoload_once_paths end - def _all_autoload_paths + def _all_autoload_paths #:nodoc: @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq end - def _all_load_paths + def _all_load_paths #:nodoc: @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq end end diff --git a/railties/lib/rails/engine/configuration.rb b/railties/lib/rails/engine/configuration.rb index e31df807a6..6b18b1e249 100644 --- a/railties/lib/rails/engine/configuration.rb +++ b/railties/lib/rails/engine/configuration.rb @@ -53,7 +53,6 @@ module Rails paths.add "config/initializers", :glob => "**/*.rb" paths.add "config/locales", :glob => "*.{rb,yml}" paths.add "config/routes.rb" - paths.add "config/routes", :glob => "**/*.rb" paths.add "db" paths.add "db/migrate" paths.add "db/seeds.rb" diff --git a/railties/lib/rails/engine/railties.rb b/railties/lib/rails/engine/railties.rb deleted file mode 100644 index 033d9c4180..0000000000 --- a/railties/lib/rails/engine/railties.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Rails - class Engine < Railtie - class Railties - # TODO Write tests for this behavior extracted from Application - def initialize(config) - @config = config - end - - def all(&block) - @all ||= [] - @all.each(&block) if block - @all - end - - def self.railties - @railties ||= ::Rails::Railtie.subclasses.map(&:instance) - end - - def self.engines - @engines ||= ::Rails::Engine.subclasses.map(&:instance) - end - - delegate :railties, :engines, :to => "self.class" - end - end -end diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 25d0161e4c..d480fc12b5 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -35,7 +35,7 @@ module Rails private - # parse possible attribute options like :limit for string/text/binary/integer or :precision/:scale for decimals + # parse possible attribute options like :limit for string/text/binary/integer, :precision/:scale for decimals or :polymorphic for references/belongs_to # when declaring options curly brackets should be used def parse_type_and_options(type) case type @@ -43,6 +43,8 @@ module Rails return $1, :limit => $2.to_i when /decimal\{(\d+)[,.-](\d+)\}/ return :decimal, :precision => $1.to_i, :scale => $2.to_i + when /(references|belongs_to)\{polymorphic\}/ + return $1, :polymorphic => true else return type, {} end @@ -92,13 +94,21 @@ module Rails end def index_name - reference? ? "#{name}_id" : name + if reference? + polymorphic? ? %w(id type).map { |t| "#{name}_#{t}" } : "#{name}_id" + else + name + end end def reference? self.class.reference?(type) end + def polymorphic? + self.attr_options.has_key?(:polymorphic) + end + def has_index? @has_index end diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt index 3ddc86ae0a..6c0ef31725 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt @@ -1,5 +1,5 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :reset_session instead. - protect_from_forgery :with => :exception -end
\ No newline at end of file + protect_from_forgery with: :exception +end diff --git a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt index bba96a7431..e0539aa8bb 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/views/layouts/application.html.erb.tt @@ -2,7 +2,7 @@ <html> <head> <title><%= camelized %></title> - <%%= stylesheet_link_tag "application", :media => "all" %> + <%%= stylesheet_link_tag "application", media: "all" %> <%%= javascript_include_tag "application" %> <%%= csrf_meta_tags %> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index d816f973e6..5915d20010 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -13,7 +13,7 @@ require "action_mailer/railtie" if defined?(Bundler) # If you precompile assets before deploying to production, use this line. - Bundler.require(*Rails.groups(:assets => %w(development test))) + Bundler.require(*Rails.groups(assets: %w(development test))) # If you want your assets lazily compiled in production, use this line. # Bundler.require(:default, :assets, Rails.env) end @@ -67,5 +67,8 @@ module <%= app_const_base %> # Version of your assets, change this if you want to expire all your assets. config.assets.version = '1.0' <% end -%> + + # Enable app-wide asynchronous ActionMailer + # config.action_mailer.async = true end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 303e47877f..85acac6725 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,18 +1,18 @@ <%= app_const %>.routes.draw do # The priority is based upon order of creation: # first created -> highest priority. - + # You can have the root of your site routed with "root" # just remember to delete public/index.html. - # root :to => 'welcome#index' + # root to: 'welcome#index' # Sample of regular route: # get 'products/:id' => 'catalog#view' # Keep in mind you can assign values other than :controller and :action # Sample of named route: - # get 'products/:id/purchase' => 'catalog#purchase', :as => :purchase - # This route can be invoked with purchase_url(:id => product.id) + # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase + # This route can be invoked with purchase_url(id: product.id) # Sample resource route (maps HTTP verbs to controller actions automatically): # resources :products @@ -39,7 +39,7 @@ # resources :products do # resources :comments # resources :sales do - # get 'recent', :on => :collection + # get 'recent', on: :collection # end # end @@ -52,4 +52,4 @@ # See how all your routes lay out with "rake routes" -end
\ No newline at end of file +end diff --git a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb index 2a849b7f2b..9342a57b20 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb +++ b/railties/lib/rails/generators/rails/app/templates/test/performance/browsing_test.rb @@ -3,8 +3,8 @@ require 'rails/performance_test_help' class BrowsingTest < ActionDispatch::PerformanceTest # Refer to the documentation for all available options - # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory], - # :output => 'tmp/performance', :formats => [:flat] } + # self.profile_options = { runs: 5, metrics: [:wall_time, :memory], + # output: 'tmp/performance', formats: [:flat] } def test_homepage get '/' diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 5081074395..bacdcbf3aa 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -1,4 +1,4 @@ -require 'rails/application/route_inspector' +require 'rails/application/routes_inspector' class Rails::InfoController < ActionController::Base self.view_paths = File.join(File.dirname(__FILE__), 'templates') @@ -15,7 +15,7 @@ class Rails::InfoController < ActionController::Base end def routes - inspector = Rails::Application::RouteInspector.new + inspector = Rails::Application::RoutesInspector.new @info = inspector.format(_routes.routes).join("\n") end diff --git a/railties/lib/rails/initializable.rb b/railties/lib/rails/initializable.rb index a61673fab8..1a0b6d1e1a 100644 --- a/railties/lib/rails/initializable.rb +++ b/railties/lib/rails/initializable.rb @@ -2,7 +2,7 @@ require 'tsort' module Rails module Initializable - def self.included(base) + def self.included(base) #:nodoc: base.extend ClassMethods end diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 6cd9c7bc95..9b4a69f19f 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -114,7 +114,7 @@ module Rails class Path include Enumerable - attr_reader :path, :root + attr_reader :path attr_accessor :glob def initialize(root, current, paths, options = {}) @@ -180,14 +180,6 @@ module Rails @paths end - def paths - raise "You need to set a path root" unless @root.path - - map do |p| - File.join @root.path, p - end - end - # Expands all paths against the root and return all unique values. def expanded raise "You need to set a path root" unless @root.path diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index a06be59759..1cb99463cc 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -181,33 +181,35 @@ module Rails def eager_load! end - def load_console(app=self) + def railtie_namespace + @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) } + end + + protected + + def run_console_blocks(app) #:nodoc: self.class.console.each { |block| block.call(app) } end - def load_runner(app=self) + def run_generators_blocks(app) #:nodoc: + self.class.generators.each { |block| block.call(app) } + end + + def run_runner_blocks(app) #:nodoc: self.class.runner.each { |block| block.call(app) } end - def load_tasks(app=self) - require 'rake' + def run_tasks_blocks(app) #:nodoc: extend Rake::DSL - self.class.rake_tasks.each { |block| self.instance_exec(app, &block) } + self.class.rake_tasks.each { |block| instance_exec(app, &block) } - # load also tasks from all superclasses + # Load also tasks from all superclasses klass = self.class.superclass + while klass.respond_to?(:rake_tasks) - klass.rake_tasks.each { |t| self.instance_exec(app, &t) } + klass.rake_tasks.each { |t| instance_exec(app, &t) } klass = klass.superclass end end - - def load_generators(app=self) - self.class.generators.each { |block| block.call(app) } - end - - def railtie_namespace - @railtie_namespace ||= self.class.parents.detect { |n| n.respond_to?(:railtie_namespace) } - end end end diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 5778b22f18..4ade825616 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,7 +1,7 @@ desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' task :routes => :environment do all_routes = Rails.application.routes.routes - require 'rails/application/route_inspector' - inspector = Rails::Application::RouteInspector.new + require 'rails/application/routes_inspector' + inspector = Rails::Application::RoutesInspector.new puts inspector.format(all_routes, ENV['CONTROLLER']).join "\n" end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 207739c4bc..6d28947e83 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.rdoc_options << '--exclude' << '.' s.add_dependency('rake', '>= 0.8.7') - s.add_dependency('thor', '>= 0.15.3', '< 2.0') + s.add_dependency('thor', '>= 0.15.4', '< 2.0') s.add_dependency('rdoc', '~> 3.4') s.add_dependency('activesupport', version) s.add_dependency('actionpack', version) diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index c1aa98e481..c813defe93 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -69,11 +69,11 @@ module ApplicationTests Rails.env = "development" assert_equal [:default, "development"], Rails.groups - assert_equal [:default, "development", :assets], Rails.groups(:assets => [:development]) - assert_equal [:default, "development", :another, :assets], Rails.groups(:another, :assets => %w(development)) + assert_equal [:default, "development", :assets], Rails.groups(assets: [:development]) + assert_equal [:default, "development", :another, :assets], Rails.groups(:another, assets: %w(development)) Rails.env = "test" - assert_equal [:default, "test"], Rails.groups(:assets => [:development]) + assert_equal [:default, "test"], Rails.groups(assets: [:development]) ENV["RAILS_GROUPS"] = "javascripts,stylesheets" assert_equal [:default, "test", "javascripts", "stylesheets"], Rails.groups @@ -567,7 +567,7 @@ module ApplicationTests app_file 'app/controllers/application_controller.rb', <<-RUBY class ApplicationController < ActionController::Base - protect_from_forgery :with => :reset_session # as we are testing API here + protect_from_forgery with: :reset_session # as we are testing API here end RUBY diff --git a/railties/test/application/paths_test.rb b/railties/test/application/paths_test.rb index e0893f53be..4029984ce9 100644 --- a/railties/test/application/paths_test.rb +++ b/railties/test/application/paths_test.rb @@ -50,8 +50,6 @@ module ApplicationTests assert_path @paths["config/locales"], "config/locales/en.yml" assert_path @paths["config/environment"], "config/environment.rb" assert_path @paths["config/environments"], "config/environments/development.rb" - assert_path @paths["config/routes.rb"], "config/routes.rb" - assert_path @paths["config/routes"], "config/routes" assert_equal root("app", "controllers"), @paths["app/controllers"].expanded.first end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index e0ee349550..a450f90dbf 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -144,6 +144,24 @@ module ApplicationTests assert_no_match(/Errors running/, output) end + def test_db_test_clone_when_using_sql_format + add_to_config "config.active_record.schema_format = :sql" + output = Dir.chdir(app_path) do + `rails generate scaffold user username:string; + bundle exec rake db:migrate db:test:clone 2>&1 --trace` + end + assert_match(/Execute db:test:clone_structure/, output) + end + + def test_db_test_prepare_when_using_sql_format + add_to_config "config.active_record.schema_format = :sql" + output = Dir.chdir(app_path) do + `rails generate scaffold user username:string; + bundle exec rake db:migrate db:test:prepare 2>&1 --trace` + end + assert_match(/Execute db:test:load_structure/, output) + end + def test_rake_dump_structure_should_respect_db_structure_env_variable Dir.chdir(app_path) do # ensure we have a schema_migrations table to dump diff --git a/railties/test/application/route_inspect_test.rb b/railties/test/application/routes_inspect_test.rb index 3b8c874b5b..eacbf3992e 100644 --- a/railties/test/application/route_inspect_test.rb +++ b/railties/test/application/routes_inspect_test.rb @@ -1,5 +1,5 @@ require 'minitest/autorun' -require 'rails/application/route_inspector' +require 'rails/application/routes_inspector' require 'action_controller' require 'rails/engine' @@ -7,7 +7,7 @@ module ApplicationTests class RouteInspectTest < ActiveSupport::TestCase def setup @set = ActionDispatch::Routing::RouteSet.new - @inspector = Rails::Application::RouteInspector.new + @inspector = Rails::Application::RoutesInspector.new app = ActiveSupport::OrderedOptions.new app.config = ActiveSupport::OrderedOptions.new app.config.assets = ActiveSupport::OrderedOptions.new diff --git a/railties/test/application/routing_test.rb b/railties/test/application/routing_test.rb index d1373ba202..396b1849d8 100644 --- a/railties/test/application/routing_test.rb +++ b/railties/test/application/routing_test.rb @@ -178,90 +178,7 @@ module ApplicationTests assert_equal 'WIN', last_response.body end - test "routes drawing from config/routes" do - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do - draw :external - end - RUBY - - app_file 'config/routes/external.rb', <<-RUBY - get ':controller/:action' - RUBY - - controller :success, <<-RUBY - class SuccessController < ActionController::Base - def index - render :text => "success!" - end - end - RUBY - - app 'development' - get '/success/index' - assert_equal 'success!', last_response.body - end - {"development" => "baz", "production" => "bar"}.each do |mode, expected| - test "reloads routes when external configuration is changed in #{mode}" do - controller :foo, <<-RUBY - class FooController < ApplicationController - def bar - render :text => "bar" - end - - def baz - render :text => "baz" - end - end - RUBY - - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do - draw :external - end - RUBY - - app_file 'config/routes/external.rb', <<-RUBY - get 'foo', :to => 'foo#bar' - RUBY - - app(mode) - - get '/foo' - assert_equal 'bar', last_response.body - - app_file 'config/routes/external.rb', <<-RUBY - get 'foo', :to => 'foo#baz' - RUBY - - sleep 0.1 - - get '/foo' - assert_equal expected, last_response.body - - app_file 'config/routes.rb', <<-RUBY - AppTemplate::Application.routes.draw do - draw :external - draw :other_external - end - RUBY - - app_file 'config/routes/other_external.rb', <<-RUBY - get 'win', :to => 'foo#baz' - RUBY - - sleep 0.1 - - get '/win' - - if mode == "development" - assert_equal expected, last_response.body - else - assert_equal 404, last_response.status - end - end - test "reloads routes when configuration is changed in #{mode}" do controller :foo, <<-RUBY class FooController < ApplicationController diff --git a/railties/test/engine_test.rb b/railties/test/engine_test.rb index 68406dce4c..addf49cdb6 100644 --- a/railties/test/engine_test.rb +++ b/railties/test/engine_test.rb @@ -11,14 +11,4 @@ class EngineTest < ActiveSupport::TestCase assert !engine.routes? end - - it "does not add more paths to routes on each call" do - engine = Class.new(Rails::Engine) - - engine.routes - length = engine.routes.draw_paths.length - - engine.routes - assert_equal length, engine.routes.draw_paths.length - end end diff --git a/railties/test/generators/generated_attribute_test.rb b/railties/test/generators/generated_attribute_test.rb index 6e3fc84781..6ab1cd58c7 100644 --- a/railties/test/generators/generated_attribute_test.rb +++ b/railties/test/generators/generated_attribute_test.rb @@ -102,22 +102,28 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_reference_is_true %w(references belongs_to).each do |attribute_type| - assert_equal( - true, - create_generated_attribute(attribute_type).reference? - ) + assert create_generated_attribute(attribute_type).reference? end end def test_reference_is_false %w(foo bar baz).each do |attribute_type| - assert_equal( - false, - create_generated_attribute(attribute_type).reference? - ) + assert !create_generated_attribute(attribute_type).reference? end end + def test_polymorphic_reference_is_true + %w(references belongs_to).each do |attribute_type| + assert create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? + end + end + + def test_polymorphic_reference_is_false + %w(foo bar baz).each do |attribute_type| + assert !create_generated_attribute("#{attribute_type}{polymorphic}").polymorphic? + end + end + def test_blank_type_defaults_to_string_raises_exception assert_equal :string, create_generated_attribute(nil, 'title').type assert_equal :string, create_generated_attribute("", 'title').type @@ -126,5 +132,6 @@ class GeneratedAttributeTest < Rails::Generators::TestCase def test_handles_index_names_for_references assert_equal "post", create_generated_attribute('string', 'post').index_name assert_equal "post_id", create_generated_attribute('references', 'post').index_name + assert_equal ["post_id", "post_type"], create_generated_attribute('references{polymorphic}', 'post').index_name end end diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index fd3b8c8a17..ec33bd7c6b 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -166,7 +166,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase end def test_add_migration_with_attributes_index_declaration_and_attribute_options - run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq"] + run_generator ["product", "title:string{40}:index", "content:string{255}", "price:decimal{5,2}:index", "discount:decimal{5,2}:uniq", "supplier:references{polymorphic}"] assert_migration "db/migrate/create_products.rb" do |content| assert_method :change, content do |up| @@ -174,6 +174,7 @@ class ModelGeneratorTest < Rails::Generators::TestCase assert_match(/t.string :title, limit: 40/, up) assert_match(/t.string :content, limit: 255/, up) assert_match(/t.decimal :price, precision: 5, scale: 2/, up) + assert_match(/t.references :supplier, polymorphic: true/, up) end assert_match(/add_index :products, :title/, content) assert_match(/add_index :products, :price/, content) @@ -193,15 +194,25 @@ class ModelGeneratorTest < Rails::Generators::TestCase end def test_model_with_references_attribute_generates_belongs_to_associations - run_generator ["product", "name:string", "supplier_id:references"] + run_generator ["product", "name:string", "supplier:references"] assert_file "app/models/product.rb", /belongs_to :supplier/ end def test_model_with_belongs_to_attribute_generates_belongs_to_associations - run_generator ["product", "name:string", "supplier_id:belongs_to"] + run_generator ["product", "name:string", "supplier:belongs_to"] assert_file "app/models/product.rb", /belongs_to :supplier/ end + def test_model_with_polymorphic_references_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:references{polymorphic}"] + assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/ + end + + def test_model_with_polymorphic_belongs_to_attribute_generates_belongs_to_associations + run_generator ["product", "name:string", "supplier:belongs_to{polymorphic}"] + assert_file "app/models/product.rb", /belongs_to :supplier, polymorphic: true/ + end + def test_migration_with_timestamps run_generator assert_migration "db/migrate/create_accounts.rb", /t.timestamps/ diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 800b1c90f0..6071cd3f39 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -273,24 +273,18 @@ end # Create a scope and build a fixture rails app Module.new do extend TestHelpers::Paths + # Build a rails app - if File.exist?(app_template_path) - FileUtils.rm_rf(app_template_path) - end + FileUtils.rm_rf(app_template_path) FileUtils.mkdir(app_template_path) environment = File.expand_path('../../../../load_paths', __FILE__) - if File.exist?("#{environment}.rb") - require_environment = "-r #{environment}" - end + require_environment = "-r #{environment}" `#{Gem.ruby} #{require_environment} #{RAILS_FRAMEWORK_ROOT}/railties/bin/rails new #{app_template_path}` + File.open("#{app_template_path}/config/boot.rb", 'w') do |f| - if require_environment - f.puts "Dir.chdir('#{File.dirname(environment)}') do" - f.puts " require '#{environment}'" - f.puts "end" - end + f.puts "require '#{environment}'" f.puts "require 'rails/all'" end end unless defined?(RAILS_ISOLATED_ENGINE) diff --git a/railties/test/paths_test.rb b/railties/test/paths_test.rb index 5d6b6f9f72..d334034e7d 100644 --- a/railties/test/paths_test.rb +++ b/railties/test/paths_test.rb @@ -29,7 +29,6 @@ class PathsTest < ActiveSupport::TestCase test "creating a root level path" do @root.add "app" assert_equal ["/foo/bar/app"], @root["app"].to_a - assert_equal ["/foo/bar/app"], @root["app"].paths end test "creating a root level path with options" do @@ -192,7 +191,6 @@ class PathsTest < ActiveSupport::TestCase @root["app"] = "/app" @root["app"].glob = "*.rb" assert_equal "*.rb", @root["app"].glob - assert_equal ["/foo/bar/app"], @root["app"].paths end test "it should be possible to override a path's default glob without assignment" do diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 52c7fae6c6..63814f7a04 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -1098,7 +1098,7 @@ YAML assert_equal "// App's bar js\n;", last_response.body.strip # ensure that railties are not added twice - railties = Rails.application.ordered_railties.map(&:class) + railties = Rails.application.send(:ordered_railties).map(&:class) assert_equal railties, railties.uniq end |