diff options
144 files changed, 1253 insertions, 652 deletions
diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index d4a89a17ee..b66e5bd6a3 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -594,7 +594,7 @@ class BaseTest < ActiveSupport::TestCase test "you can register a preview interceptor to the mail object that gets passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptor(MyInterceptor) mail = BaseMailer.welcome - BaseMailerPreview.stubs(:welcome).returns(mail) + BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end @@ -602,7 +602,7 @@ class BaseTest < ActiveSupport::TestCase test "you can register a preview interceptor using its stringified name to the mail object that gets passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptor("BaseTest::MyInterceptor") mail = BaseMailer.welcome - BaseMailerPreview.stubs(:welcome).returns(mail) + BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end @@ -610,7 +610,7 @@ class BaseTest < ActiveSupport::TestCase test "you can register an interceptor using its symbolized underscored name to the mail object that gets passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptor(:"base_test/my_interceptor") mail = BaseMailer.welcome - BaseMailerPreview.stubs(:welcome).returns(mail) + BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) end @@ -618,7 +618,7 @@ class BaseTest < ActiveSupport::TestCase test "you can register multiple preview interceptors to the mail object that both get passed the mail object before previewing" do ActionMailer::Base.register_preview_interceptors("BaseTest::MyInterceptor", MySecondInterceptor) mail = BaseMailer.welcome - BaseMailerPreview.stubs(:welcome).returns(mail) + BaseMailerPreview.any_instance.stubs(:welcome).returns(mail) MyInterceptor.expects(:previewing_email).with(mail) MySecondInterceptor.expects(:previewing_email).with(mail) BaseMailerPreview.call(:welcome) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index c23577de9b..fe5b38d29f 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,18 @@ +* Swapped the parameters of assert_equal in `assert_select` so that the + proper values were printed correctly + + Fixes #14422. + + *Vishal Lal* + +* The method `shallow?` returns false if the parent resource is a singleton so + we need to check if we're not inside a nested scope before copying the :path + and :as options to their shallow equivalents. + + Fixes #14388. + + *Andrew White* + * Make logging of CSRF failures optional (but on by default) with the `log_warning_on_csrf_failure` configuration setting in `ActionController::RequestForgeryProtection`. diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 41b997a755..acf40b2e16 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -228,18 +228,19 @@ module ActionController begin super(name) rescue => e - unless @_response.committed? + if @_response.committed? + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end + else error = e end - begin - @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html - @_response.stream.call_on_error - rescue => exception - log_error(exception) - ensure - log_error(e) - @_response.stream.close - end ensure @_response.commit! end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index e9166d8747..df57efaa97 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -486,8 +486,8 @@ module ActionController # - +session+: A hash of parameters to store in the session. This may be +nil+. # - +flash+: A hash of parameters to store in the flash. This may be +nil+. # - # You can also simulate POST, PATCH, PUT, DELETE, HEAD, and OPTIONS requests with - # +post+, +patch+, +put+, +delete+, +head+, and +options+. + # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with + # +post+, +patch+, +put+, +delete+, and +head+. # # Note that the request method is not verified. The different methods are # available to make the tests more expressive. diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 1ebc189c28..3be0ce1860 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -51,7 +51,7 @@ module ActionDispatch # decode signed cookies generated by your app in external applications or # Javascript before upgrading. # - # Note that changing digest or secret invalidates all existing sessions! + # Note that changing the secret key will invalidate all existing sessions! class CookieStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 357829e59f..5de0a0cbc6 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -707,7 +707,7 @@ module ActionDispatch options[:path] = args.flatten.join('/') if args.any? options[:constraints] ||= {} - unless shallow? + unless nested_scope? options[:shallow_path] ||= options[:path] if options.key?(:path) options[:shallow_prefix] ||= options[:as] if options.key?(:as) end @@ -1547,6 +1547,10 @@ module ActionDispatch RESOURCE_METHOD_SCOPES.include? @scope[:scope_level] end + def nested_scope? #:nodoc: + @scope[:scope_level] == :nested + end + def with_exclusive_scope begin old_name_prefix, old_path = @scope[:as], @scope[:path] diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 3253a3d424..8a128427bf 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -291,7 +291,7 @@ module ActionDispatch # so is this custom message really needed? message = message || %(Expected #{count_description(min, max, count)} matching "#{selector.to_s}", found #{matches.size}.) if count - assert_equal matches.size, count, message + assert_equal count, matches.size, message else assert_operator matches.size, :>=, min, message if min assert_operator matches.size, :<=, max, message if max diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 591d0eccc3..58a86ce9af 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -164,6 +164,13 @@ class FunctionalCachingController < CachingController end end + def formatted_fragment_cached_with_variant + respond_to do |format| + format.html.phone + format.html + end + end + def fragment_cached_without_digest end end @@ -190,7 +197,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", - @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached", "html")}") + @store.read("views/test.host/functional_caching/fragment_cached/#{template_digest("functional_caching/fragment_cached")}") end def test_fragment_caching_in_partials @@ -199,7 +206,7 @@ CACHED assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", - @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial", "html")}")) + @store.read("views/test.host/functional_caching/html_fragment_cached_with_partial/#{template_digest("functional_caching/_partial")}")) end def test_skipping_fragment_cache_digesting @@ -217,7 +224,7 @@ CACHED assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", - @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached", "html")}")) + @store.read("views/test.host/functional_caching/inline_fragment_cached/#{template_digest("functional_caching/inline_fragment_cached")}")) end def test_html_formatted_fragment_caching @@ -228,7 +235,7 @@ CACHED assert_equal expected_body, @response.body assert_equal "<p>ERB</p>", - @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached", "html")}") + @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") end def test_xml_formatted_fragment_caching @@ -239,12 +246,26 @@ CACHED assert_equal expected_body, @response.body assert_equal " <p>Builder</p>\n", - @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached", "xml")}") + @store.read("views/test.host/functional_caching/formatted_fragment_cached/#{template_digest("functional_caching/formatted_fragment_cached")}") + end + + + def test_fragment_caching_with_variant + @request.variant = :phone + + get :formatted_fragment_cached_with_variant, :format => "html" + assert_response :success + expected_body = "<body>\n<p>PHONE</p>\n</body>\n" + + assert_equal expected_body, @response.body + + assert_equal "<p>PHONE</p>", + @store.read("views/test.host/functional_caching/formatted_fragment_cached_with_variant/#{template_digest("functional_caching/formatted_fragment_cached_with_variant")}") end private - def template_digest(name, format, variant = nil) - ActionView::Digestor.digest(name: name, format: format, variant: variant, finder: @controller.lookup_context) + def template_digest(name) + ActionView::Digestor.digest(name: name, finder: @controller.lookup_context) end end diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 2fd5c930ba..947f64176b 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -153,6 +153,11 @@ module ActionController render 'doesntexist' end + def exception_in_view_after_commit + response.stream.write "" + render 'doesntexist' + end + def exception_with_callback response.headers['Content-Type'] = 'text/event-stream' @@ -269,6 +274,13 @@ module ActionController assert_raises(ActionView::MissingTemplate) do get :exception_in_view end + + capture_log_output do |output| + get :exception_in_view_after_commit + assert_match %r((window\.location = "/500\.html"</script></html>)$), response.body + assert_match 'Missing template test/doesntexist', output.rewind && output.read + assert_stream_closed + end assert response.body assert_stream_closed end @@ -277,6 +289,13 @@ module ActionController assert_raises(ActionView::MissingTemplate) do get :exception_in_view, format: :json end + + capture_log_output do |output| + get :exception_in_view_after_commit, format: :json + assert_equal '', response.body + assert_match 'Missing template test/doesntexist', output.rewind && output.read + assert_stream_closed + end end def test_exception_callback_when_committed diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index df55fcc8bc..10fb04e230 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -36,29 +36,55 @@ module ActionDispatch assert_equal s, Session.find(env) end + def test_destroy + s = Session.create(store, {}, {}) + s['rails'] = 'ftw' + + s.destroy + + assert_empty s + end + def test_keys - env = {} - s = Session.create(store, env, {}) + s = Session.create(store, {}, {}) s['rails'] = 'ftw' s['adequate'] = 'awesome' assert_equal %w[rails adequate], s.keys end def test_values - env = {} - s = Session.create(store, env, {}) + s = Session.create(store, {}, {}) s['rails'] = 'ftw' s['adequate'] = 'awesome' assert_equal %w[ftw awesome], s.values end def test_clear - env = {} - s = Session.create(store, env, {}) + s = Session.create(store, {}, {}) s['rails'] = 'ftw' s['adequate'] = 'awesome' + s.clear - assert_equal([], s.values) + assert_empty(s.values) + end + + def test_update + s = Session.create(store, {}, {}) + s['rails'] = 'ftw' + + s.update(:rails => 'awesome') + + assert_equal(['rails'], s.keys) + assert_equal('awesome', s['rails']) + end + + def test_delete + s = Session.create(store, {}, {}) + s['rails'] = 'ftw' + + s.delete('rails') + + assert_empty(s.keys) end def test_fetch @@ -82,6 +108,7 @@ module ActionDispatch Class.new { def load_session(env); [1, {}]; end def session_exists?(env); true; end + def destroy_session(env, id, options); 123; end }.new end end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index a47050adce..ab2f0ec8de 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1958,6 +1958,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/comments/3/preview', preview_comment_path(:id => '3') end + def test_shallow_nested_resources_inside_resource + draw do + resource :membership, shallow: true do + resources :cards + end + end + + get '/membership/cards' + assert_equal 'cards#index', @response.body + assert_equal '/membership/cards', membership_cards_path + + get '/membership/cards/new' + assert_equal 'cards#new', @response.body + assert_equal '/membership/cards/new', new_membership_card_path + + post '/membership/cards' + assert_equal 'cards#create', @response.body + + get '/cards/1' + assert_equal 'cards#show', @response.body + assert_equal '/cards/1', card_path('1') + + get '/cards/1/edit' + assert_equal 'cards#edit', @response.body + assert_equal '/cards/1/edit', edit_card_path('1') + + put '/cards/1' + assert_equal 'cards#update', @response.body + + patch '/cards/1' + assert_equal 'cards#update', @response.body + + delete '/cards/1' + assert_equal 'cards#destroy', @response.body + end + def test_shallow_nested_resources_within_scope draw do scope '/hello' do diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index e53ce4195b..92544230b2 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'securerandom' # You need to start a memcached server inorder to run these tests class MemCacheStoreTest < ActionDispatch::IntegrationTest @@ -172,7 +173,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest end @app = self.class.build_app(set) do |middleware| - middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id' + middleware.use ActionDispatch::Session::MemCacheStore, :key => '_session_id', :namespace => "mem_cache_store_test:#{SecureRandom.hex(10)}" middleware.delete "ActionDispatch::ShowExceptions" end diff --git a/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb new file mode 100644 index 0000000000..e523b74ae3 --- /dev/null +++ b/actionpack/test/fixtures/functional_caching/formatted_fragment_cached_with_variant.html+phone.erb @@ -0,0 +1,3 @@ +<body> +<%= cache do %><p>PHONE</p><% end %> +</body> diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index e46f55a875..8c6db33be7 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,18 @@ +* `date_select` helper with option `with_css_classes: true` does not overwrite other classes. + + *Izumi Wong-Horiuchi* + +* `number_to_percentage` does not crash with `Float::NAN` or `Float::INFINITY` + as input. + + Fixes #14405. + + *Yves Senn* + +* Add `include_hidden` option to `collection_check_boxes` helper. + + *Vasiliy Ermolovich* + * Fixed a problem where the default options for the `button_tag` helper is not applied correctly. diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index da43fef2e3..72d79735ae 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -12,22 +12,13 @@ module ActionView # Supported options: # # * <tt>name</tt> - Template name - # * <tt>format</tt> - Template format - # * <tt>variant</tt> - Variant of +format+ (optional) # * <tt>finder</tt> - An instance of ActionView::LookupContext # * <tt>dependencies</tt> - An array of dependent views # * <tt>partial</tt> - Specifies whether the template is a partial - def digest(*args) - options = _setup_options(*args) + def digest(options) + options.assert_valid_keys(:name, :finder, :dependencies, :partial) - name = options[:name] - format = options[:format] - variant = options[:variant] - finder = options[:finder] - - details_key = finder.details_key.hash - dependencies = Array.wrap(options[:dependencies]) - cache_key = ([name, details_key, format, variant].compact + dependencies).join('.') + cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.') # this is a correctly done double-checked locking idiom # (ThreadSafe::Cache's lookups have volatile semantics) @@ -38,63 +29,41 @@ module ActionView end end - def _setup_options(*args) - unless args.first.is_a?(Hash) - ActiveSupport::Deprecation.warn("Arguments to ActionView::Digestor should be provided as a hash. The support for regular arguments will be removed in Rails 5.0 or later") - - { - name: args.first, - format: args.second, - finder: args.third, - }.merge(args.fourth || {}) - else - options = args.first - options.assert_valid_keys(:name, :format, :variant, :finder, :dependencies, :partial) - - options - end - end - private + def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock + klass = if options[:partial] || options[:name].include?("/_") + # Prevent re-entry or else recursive templates will blow the stack. + # There is no need to worry about other threads seeing the +false+ value, + # as they will then have to wait for this thread to let go of the @@digest_monitor lock. + pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion + PartialDigestor + else + Digestor + end - def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock - klass = if options[:partial] || options[:name].include?("/_") - # Prevent re-entry or else recursive templates will blow the stack. - # There is no need to worry about other threads seeing the +false+ value, - # as they will then have to wait for this thread to let go of the @@digest_monitor lock. - pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion - PartialDigestor - else - Digestor + digest = klass.new(options).digest + # Store the actual digest if config.cache_template_loading is true + @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching? + digest + ensure + # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache + @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest end - - digest = klass.new(options).digest - # Store the actual digest if config.cache_template_loading is true - @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching? - digest - ensure - # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache - @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest - end end - attr_reader :name, :format, :variant, :finder, :options + attr_reader :name, :finder, :options - def initialize(*args) - @options = self.class._setup_options(*args) - - @name = @options.delete(:name) - @format = @options.delete(:format) - @variant = @options.delete(:variant) - @finder = @options.delete(:finder) + def initialize(options) + @name, @finder = options.values_at(:name, :finder) + @options = options.except(:name, :finder) end def digest Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest| - logger.try :info, "Cache digest for #{name}.#{format}: #{digest}" + logger.try :info, " Cache digest for #{template.inspect}: #{digest}" end rescue ActionView::MissingTemplate - logger.try :error, "Couldn't find template for digesting: #{name}.#{format}" + logger.try :error, " Couldn't find template for digesting: #{name}" '' end @@ -106,13 +75,12 @@ module ActionView def nested_dependencies dependencies.collect do |dependency| - dependencies = PartialDigestor.new(name: dependency, format: format, finder: finder).nested_dependencies + dependencies = PartialDigestor.new(name: dependency, finder: finder).nested_dependencies dependencies.any? ? { dependency => dependencies } : dependency end end private - def logger ActionView::Base.logger end @@ -126,7 +94,7 @@ module ActionView end def template - @template ||= finder.find(logical_name, [], partial?, formats: [ format ], variants: [ variant ]) + @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) } end def source @@ -135,7 +103,7 @@ module ActionView def dependency_digest template_digests = dependencies.collect do |template_name| - Digestor.digest(name: template_name, format: format, finder: finder, partial: true) + Digestor.digest(name: template_name, finder: finder, partial: true) end (template_digests + injected_dependencies).join("-") diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 30e4e5e329..d1c268ec40 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -165,10 +165,10 @@ module ActionView def fragment_name_with_digest(name) #:nodoc: if @virtual_path - [ - *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), - Digestor.digest(name: @virtual_path, format: formats.last.to_sym, variant: request.variant, finder: lookup_context, dependencies: view_cache_dependencies) - ] + names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name) + digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies + + [ *names, digest ] else name end diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 1738df9cac..2efb9612ac 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -965,7 +965,7 @@ module ActionView :name => input_name_from_type(type) }.merge!(@html_options) select_options[:disabled] = 'disabled' if @options[:disabled] - select_options[:class] = type if @options[:with_css_classes] + select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes] select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 9b77ebeb1b..8b28e4fc33 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -27,10 +27,14 @@ module ActionView # Append a hidden field to make sure something will be sent back to the # server if all check boxes are unchecked. - hidden_name = @html_options[:name] || "#{tag_name}[]" - hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil) + if @options.fetch(:include_hidden, true) + hidden_name = @html_options[:name] || "#{tag_name}[]" + hidden = @template_object.hidden_field_tag(hidden_name, "", :id => nil) - rendered_collection + hidden + rendered_collection + hidden + else + rendered_collection + end end private diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 51379d433f..89c196e578 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -389,15 +389,7 @@ module ActionView # # If not... # # => <a href="/accounts/signup">Reply</a> def link_to_unless(condition, name, options = {}, html_options = {}, &block) - if condition - if block_given? - block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) - else - ERB::Util.html_escape(name) - end - else - link_to(name, options, html_options) - end + link_to_if !condition, name, options, html_options, &block end # Creates a link tag of the given +name+ using a URL created by the set of @@ -421,7 +413,15 @@ module ActionView # # If they are logged in... # # => <a href="/accounts/show/3">my_username</a> def link_to_if(condition, name, options = {}, html_options = {}, &block) - link_to_unless !condition, name, options, html_options, &block + if condition + link_to(name, options, html_options) + else + if block_given? + block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block) + else + ERB::Util.html_escape(name) + end + end end # Creates a mailto link tag to the specified +email_address+, which is diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 76c9890776..855fed0190 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -159,7 +159,14 @@ module ActionView def detail_args_for(options) return @details, details_key if options.empty? # most common path. user_details = @details.merge(options) - [user_details, DetailsKey.get(user_details)] + + if @cache + details_key = DetailsKey.get(user_details) + else + details_key = nil + end + + [user_details, details_key] end # Support legacy foo.erb names even though we now ignore .erb diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 961a969b6e..9d39d02a37 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -97,7 +97,7 @@ module ActionView extend Template::Handlers - attr_accessor :locals, :formats, :virtual_path + attr_accessor :locals, :formats, :variants, :virtual_path attr_reader :source, :identifier, :handler, :original_encoding, :updated_at @@ -123,6 +123,7 @@ module ActionView @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } + @variants = [details[:variant]] @compile_mutex = Mutex.new end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3a3b74cdd5..403824bd8e 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -154,7 +154,8 @@ module ActionView cached = nil templates.each do |t| t.locals = locals - t.formats = details[:formats] || [:html] if t.formats.empty? + t.formats = details[:formats] || [:html] if t.formats.empty? + t.variants = details[:variants] || [] if t.variants.empty? t.virtual_path ||= (cached ||= build_path(*path_info)) end end @@ -189,13 +190,15 @@ module ActionView } template_paths.map { |template| - handler, format = extract_handler_and_format(template, formats) - contents = File.binread template + handler, format, variant = extract_handler_and_format_and_variant(template, formats) + contents = File.binread(template) Template.new(contents, File.expand_path(template), handler, :virtual_path => path.virtual, :format => format, - :updated_at => mtime(template)) + :variant => variant, + :updated_at => mtime(template) + ) } end @@ -228,7 +231,7 @@ module ActionView # Extract handler and formats from path. If a format cannot be a found neither # from the path, or the handler, we should return the array of formats given # to the resolver. - def extract_handler_and_format(path, default_formats) + def extract_handler_and_format_and_variant(path, default_formats) pieces = File.basename(path).split(".") pieces.shift @@ -240,10 +243,10 @@ module ActionView end handler = Template.handler_for_extension(extension) - format = pieces.last && pieces.last.split(EXTENSIONS[:variants], 2).first # remove variant from format + format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last format &&= Template::Types[format] - [handler, format] + [handler, format, variant] end end diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index af53ad3b25..dfb7d463b4 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -30,9 +30,13 @@ module ActionView #:nodoc: @hash.each do |_path, array| source, updated_at = array next unless _path =~ query - handler, format = extract_handler_and_format(_path, formats) + handler, format, variant = extract_handler_and_format_and_variant(_path, formats) templates << Template.new(source, _path, handler, - :virtual_path => path.virtual, :format => format, :updated_at => updated_at) + :virtual_path => path.virtual, + :format => format, + :variant => variant, + :updated_at => updated_at + ) end templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } @@ -41,8 +45,8 @@ module ActionView #:nodoc: class NullResolver < PathResolver def query(path, exts, formats) - handler, format = extract_handler_and_format(path, formats) - [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)] + handler, format, variant = extract_handler_and_format_and_variant(path, formats) + [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)] end end diff --git a/actionview/test/fixtures/test/hello_world.html+phone.erb b/actionview/test/fixtures/test/hello_world.html+phone.erb new file mode 100644 index 0000000000..b4f236f878 --- /dev/null +++ b/actionview/test/fixtures/test/hello_world.html+phone.erb @@ -0,0 +1 @@ +Hello phone!
\ No newline at end of file diff --git a/actionview/test/fixtures/test/hello_world.text+phone.erb b/actionview/test/fixtures/test/hello_world.text+phone.erb new file mode 100644 index 0000000000..611e2ee442 --- /dev/null +++ b/actionview/test/fixtures/test/hello_world.text+phone.erb @@ -0,0 +1 @@ +Hello texty phone!
\ No newline at end of file diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 6f77c3c99d..b86ae910c4 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -1040,6 +1040,22 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}) end + def test_select_date_with_css_classes_option_and_html_class_option + expected = %(<select id="date_first_year" name="date[first][year]" class="datetime optional year">\n) + expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_month" name="date[first][month]" class="datetime optional month">\n) + expected << %(<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_day" name="date[first][day]" class="datetime optional day">\n) + expected << %(<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), {:start_year => 2003, :end_year => 2005, :prefix => "date[first]", :with_css_classes => true}, { class: 'datetime optional' }) + end + def test_select_datetime expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index 2406a64310..47e1f6a6e5 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -15,23 +15,31 @@ end class FixtureFinder FIXTURES_DIR = "#{File.dirname(__FILE__)}/../fixtures/digestor" - attr_reader :details + attr_reader :details + attr_accessor :formats + attr_accessor :variants def initialize - @details = {} + @details = {} + @formats = [] + @variants = [] end def details_key details.hash end - def find(logical_name, keys, partial, options) - partial_name = partial ? logical_name.gsub(%r|/([^/]+)$|, '/_\1') : logical_name - format = options[:formats].first.to_s - format += "+#{options[:variants].first}" if options[:variants].any? + def find(name, prefixes = [], partial = false, keys = [], options = {}) + partial_name = partial ? name.gsub(%r|/([^/]+)$|, '/_\1') : name + format = @formats.first.to_s + format += "+#{@variants.first}" if @variants.any? FixtureTemplate.new("digestor/#{partial_name}.#{format}.erb") end + + def disable_cache(&block) + yield + end end class TemplateDigestorTest < ActionView::TestCase @@ -92,13 +100,13 @@ class TemplateDigestorTest < ActionView::TestCase end def test_logging_of_missing_template - assert_logged "Couldn't find template for digesting: messages/something_missing.html" do + assert_logged "Couldn't find template for digesting: messages/something_missing" do digest("messages/show") end end def test_logging_of_missing_template_ending_with_number - assert_logged "Couldn't find template for digesting: messages/something_missing_1.html" do + assert_logged "Couldn't find template for digesting: messages/something_missing_1" do digest("messages/show") end end @@ -199,7 +207,7 @@ class TemplateDigestorTest < ActionView::TestCase end def test_variants - assert_digest_difference("messages/new", false, variant: :iphone) do + assert_digest_difference("messages/new", false, variants: [:iphone]) do change_template("messages/new", :iphone) change_template("messages/_header", :iphone) end @@ -253,10 +261,6 @@ class TemplateDigestorTest < ActionView::TestCase ActionView::Resolver.caching = resolver_before end - def test_arguments_deprecation - assert_deprecated(/should be provided as a hash/) { ActionView::Digestor.digest('messages/show', :html, finder) } - assert_deprecated(/should be provided as a hash/) { ActionView::Digestor.new('messages/show', :html, finder) } - end private def assert_logged(message) @@ -286,7 +290,11 @@ class TemplateDigestorTest < ActionView::TestCase def digest(template_name, options = {}) options = options.dup - ActionView::Digestor.digest({ name: template_name, format: :html, finder: finder }.merge(options)) + + finder.formats = [:html] + finder.variants = options.delete(:variants) || [] + + ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options)) end def finder diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 18632465db..73fa3b6b4e 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -204,6 +204,13 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select "input[type=hidden][name='user[other_category_ids][]'][value=]", :count => 1 end + test 'collection check boxes does not generate a hidden field if include_hidden option is false' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name, include_hidden: false + + assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 0 + end + test 'collection check boxes accepts a collection and generate a series of checkboxes with labels for label method' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] with_collection_check_boxes :user, :category_ids, collection, :id, :name diff --git a/actionview/test/template/lookup_context_test.rb b/actionview/test/template/lookup_context_test.rb index ce9485e146..4f7823045e 100644 --- a/actionview/test/template/lookup_context_test.rb +++ b/actionview/test/template/lookup_context_test.rb @@ -93,6 +93,20 @@ class LookupContextTest < ActiveSupport::TestCase assert_equal "Hey verden", template.source end + test "find templates with given variants" do + @lookup_context.formats = [:html] + @lookup_context.variants = [:phone] + + template = @lookup_context.find("hello_world", %w(test)) + assert_equal "Hello phone!", template.source + + @lookup_context.variants = [:phone] + @lookup_context.formats = [:text] + + template = @lookup_context.find("hello_world", %w(test)) + assert_equal "Hello texty phone!", template.source + end + test "found templates respects given formats if one cannot be found from template or handler" do ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil) @lookup_context.formats = [:text] diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index 11bc978324..adb888319d 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -32,6 +32,9 @@ class NumberHelperTest < ActionView::TestCase assert_equal "100%", number_to_percentage(100, precision: 0) assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true) assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",") + assert_equal "98a%", number_to_percentage("98a") + assert_equal "NaN%", number_to_percentage(Float::NAN) + assert_equal "Inf%", number_to_percentage(Float::INFINITY) end def test_number_with_delimiter diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index d6044988f0..6e77493de9 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,20 +1,79 @@ +* Save `has_one` association even if the record doesn't changed. + + Fixes #14407. + + *Rafael Mendonça França* + +* Use singular table name in generated migrations when + `ActiveRecord::Base.pluralize_table_names` is `false`. + + Fixes #13426. + + *Kuldeep Aggarwal* + +* `touch` accepts many attributes to be touched at once. + + Example: + + # touches :signed_at, :sealed_at, and :updated_at/on attributes. + Photo.last.touch(:signed_at, :sealed_at) + + *James Pinto* + +* `rake db:structure:dump` only dumps schema information if the schema + migration table exists. + + Fixes #14217. + + *Yves Senn* + +* Reap connections that were checked out by now-dead threads, instead + of waiting until they disconnect by themselves. Before this change, + a suitably constructed series of short-lived threads could starve + the connection pool, without ever having more than a couple alive at + the same time. + + *Matthew Draper* + +* `pk_and_sequence_for` now ensures that only the pg_depend entries + pointing to pg_class, and thus only sequence objects, are considered. + + *Josh Williams* + +* `where.not` adds `references` for `includes` like normal `where` calls do. + + Fixes #14406. + + *Yves Senn* + +* Extend fixture `$LABEL` replacement to allow string interpolation. + + Example: + + martin: + email: $LABEL@email.com + + users(:martin).email # => martin@email.com + + *Eric Steele* + * Add support for `Relation` be passed as parameter on `QueryCache#select_all`. Fixes #14361. *arthurnn* -* Passing an Active Record object to `find` is now deprecated. Call `.id` - on the object first. +* Passing an Active Record object to `find` is now deprecated. Call `.id` + on the object first. -* Passing an Active Record object to `exists?` is now deprecated. Call `.id` - on the object first. +* Passing an Active Record object to `find` or `exists?` is now deprecated. + Call `.id` on the object first. -* Only use BINARY for mysql case sensitive uniqueness check when column has a case insensitive collation. +* Only use BINARY for MySQL case sensitive uniqueness check when column has a case insensitive collation. *Ryuta Kamizono* -* Support for Mysql 5.6 Fractional Seconds. +* Support for MySQL 5.6 fractional seconds. *arthurnn*, *Tatsuhiko Miyagawa* @@ -23,12 +82,6 @@ *Troy Kruthoff*, *Lachlan Sylvester* -* Only save has_one associations if record has changes. - Previously after save related callbacks, such as `#after_commit`, were triggered when the has_one - object did not get saved to the db. - - *Alan Kennedy* - * Allow strings to specify the `#order` value. Example: diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f725356cd9..860e76fa18 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1584,7 +1584,7 @@ module ActiveRecord hm_options[:through] = middle_reflection.name hm_options[:source] = join_model.right_reflection.name - [:before_add, :after_add, :before_remove, :after_remove, :autosave].each do |k| + [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate].each do |k| hm_options[k] = options[k] if options.key? k end diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 213b72b933..e9622ca0c1 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -377,16 +377,15 @@ module ActiveRecord def save_has_one_association(reflection) association = association_instance_get(reflection.name) record = association && association.load_target - if record && !record.destroyed? autosave = reflection.options[:autosave] if autosave && record.marked_for_destruction? record.destroy - elsif autosave != false + else key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id + if autosave != false && (autosave || new_record? || record_changed?(reflection, record, key)) - if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key) unless reflection.through_reflection record[reflection.foreign_key] = key end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index fd185de589..db80c0faee 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -58,13 +58,11 @@ module ActiveRecord # * +checkout_timeout+: number of seconds to block and wait for a connection # before giving up and raising a timeout error (default 5 seconds). # * +reaping_frequency+: frequency in seconds to periodically run the - # Reaper, which attempts to find and close dead connections, which can - # occur if a programmer forgets to close a connection at the end of a - # thread or a thread dies unexpectedly. (Default nil, which means don't - # run the Reaper). - # * +dead_connection_timeout+: number of seconds from last checkout - # after which the Reaper will consider a connection reapable. (default - # 5 seconds). + # Reaper, which attempts to find and recover connections from dead + # threads, which can occur if a programmer forgets to close a + # connection at the end of a thread or a thread dies unexpectedly. + # Regardless of this setting, the Reaper will be invoked before every + # blocking wait. (Default nil, which means don't schedule the Reaper). class ConnectionPool # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool # with which it shares a Monitor. But could be a generic Queue. @@ -222,7 +220,7 @@ module ActiveRecord include MonitorMixin - attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout + attr_accessor :automatic_reconnect, :checkout_timeout attr_reader :spec, :connections, :size, :reaper # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification @@ -237,7 +235,6 @@ module ActiveRecord @spec = spec @checkout_timeout = spec.config[:checkout_timeout] || 5 - @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5 @reaper = Reaper.new self, spec.config[:reaping_frequency] @reaper.run @@ -361,11 +358,13 @@ module ActiveRecord # calling +checkout+ on this pool. def checkin(conn) synchronize do + owner = conn.owner + conn.run_callbacks :checkin do conn.expire end - release conn + release conn, owner @available.add conn end @@ -378,22 +377,28 @@ module ActiveRecord @connections.delete conn @available.delete conn - # FIXME: we might want to store the key on the connection so that removing - # from the reserved hash will be a little easier. - release conn + release conn, conn.owner @available.add checkout_new_connection if @available.any_waiting? end end - # Removes dead connections from the pool. A dead connection can occur - # if a programmer forgets to close a connection at the end of a thread + # Recover lost connections for the pool. A lost connection can occur if + # a programmer forgets to checkin a connection at the end of a thread # or a thread dies unexpectedly. def reap - synchronize do - stale = Time.now - @dead_connection_timeout - connections.dup.each do |conn| - if conn.in_use? && stale > conn.last_use && !conn.active_threadsafe? + stale_connections = synchronize do + @connections.select do |conn| + conn.in_use? && !conn.owner.alive? + end + end + + stale_connections.each do |conn| + synchronize do + if conn.active? + conn.reset! + checkin conn + else remove conn end end @@ -415,20 +420,15 @@ module ActiveRecord elsif @connections.size < @size checkout_new_connection else + reap @available.poll(@checkout_timeout) end end - def release(conn) - thread_id = if @reserved_connections[current_connection_id] == conn - current_connection_id - else - @reserved_connections.keys.find { |k| - @reserved_connections[k] == conn - } - end + def release(conn, owner) + thread_id = owner.object_id - @reserved_connections.delete thread_id if thread_id + @reserved_connections.delete thread_id end def new_connection diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 496153af83..c7e5a18f02 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -71,8 +71,8 @@ module ActiveRecord define_callbacks :checkout, :checkin attr_accessor :visitor, :pool - attr_reader :schema_cache, :last_use, :in_use, :logger - alias :in_use? :in_use + attr_reader :schema_cache, :owner, :logger + alias :in_use? :owner def self.type_cast_config_to_integer(config) if config =~ SIMPLE_INT @@ -94,9 +94,8 @@ module ActiveRecord super() @connection = connection - @in_use = false + @owner = nil @instrumenter = ActiveSupport::Notifications.instrumenter - @last_use = false @logger = logger @pool = pool @schema_cache = SchemaCache.new self @@ -114,9 +113,8 @@ module ActiveRecord def lease synchronize do - unless in_use - @in_use = true - @last_use = Time.now + unless in_use? + @owner = Thread.current end end end @@ -127,7 +125,7 @@ module ActiveRecord end def expire - @in_use = false + @owner = nil end def unprepared_visitor @@ -262,12 +260,6 @@ module ActiveRecord def active? end - # Adapter should redefine this if it needs a threadsafe way to approximate - # if the connection is active - def active_threadsafe? - active? - end - # Disconnects from the database if already connected, and establishes a # new connection with the database. Implementors should call super if they # override the default implementation. diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 3f8b14bf67..9a133168f8 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -237,8 +237,8 @@ module ActiveRecord # hash and merges with the rest of the hash. # Connection details inside of the "url" key win any merge conflicts def resolve_hash_connection(spec) - if url = spec.delete("url") - connection_hash = resolve_string_connection(url) + if spec["url"] && spec["url"] !~ /^jdbc:/ + connection_hash = resolve_string_connection(spec.delete("url")) spec.merge!(connection_hash) end spec diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index ae8ede4b42..e0afa989cd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -327,6 +327,7 @@ module ActiveRecord AND attr.attrelid = cons.conrelid AND attr.attnum = cons.conkey[1] AND cons.contype = 'p' + AND dep.classid = 'pg_class'::regclass AND dep.refobjid = '#{quote_table_name(table)}'::regclass end_sql diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e5f7913c70..bcad9f30d7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -603,10 +603,6 @@ module ActiveRecord false end - def active_threadsafe? - @connection.connect_poll != PG::PGRES_POLLING_FAILED - end - # Close then reopen the connection. def reconnect! super @@ -616,7 +612,12 @@ module ActiveRecord def reset! clear_cache! - super + reset_transaction + unless @connection.transaction_status == ::PG::PQTRANS_IDLE + @connection.query 'ROLLBACK' + end + @connection.query 'DISCARD ALL' + configure_connection end # Disconnects from the database if already connected. Otherwise, this diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 59467636d7..6f134bbef8 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -361,6 +361,7 @@ module ActiveRecord # geeksomnia: # name: Geeksomnia's Account # subdomain: $LABEL + # email: $LABEL@email.com # # Also, sometimes (like when porting older join table fixtures) you'll need # to be able to get a hold of the identifier for a given label. ERB @@ -627,7 +628,7 @@ module ActiveRecord # interpolate the fixture label row.each do |key, value| - row[key] = label if "$LABEL" == value + row[key] = value.gsub("$LABEL", label) if value.is_a?(String) end # generate a primary key if necessary diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 905289f398..1a2581f579 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -405,15 +405,18 @@ module ActiveRecord end # Saves the record with the updated_at/on attributes set to the current time. - # Please note that no validation is performed and only the +after_touch+ - # callback is executed. - # If an attribute name is passed, that attribute is updated along with - # updated_at/on attributes. + # Please note that no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. # - # product.touch # updates updated_at/on - # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + # If attribute names are passed, they are updated along with updated_at/on + # attributes. # - # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on associated object. + # product.touch # updates updated_at/on + # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on + # product.touch(:started_at, :ended_at) # updates started_at, ended_at and updated_at/on attributes + # + # If used along with +belongs_to+ then +touch+ will invoke +touch+ method on + # associated object. # # class Brake < ActiveRecord::Base # belongs_to :car, touch: true @@ -432,11 +435,11 @@ module ActiveRecord # ball = Ball.new # ball.touch(:updated_at) # => raises ActiveRecordError # - def touch(name = nil) + def touch(*names) raise ActiveRecordError, "cannot touch on a new record object" unless persisted? attributes = timestamp_attributes_for_update_in_model - attributes << name if name + attributes.concat(names) unless attributes.empty? current_time = current_time_from_proper_timezone diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 1d5c80bc01..ff1f0f5911 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -268,7 +268,8 @@ db_namespace = namespace :db do current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) - if ActiveRecord::Base.connection.supports_migrations? + if ActiveRecord::Base.connection.supports_migrations? && + ActiveRecord::SchemaMigration.table_exists? File.open(filename, "a") do |f| f.puts ActiveRecord::Base.connection.dump_schema_information f.print "\n" diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8c005a7222..e41df0ea29 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -49,6 +49,8 @@ module ActiveRecord Arel::Nodes::Not.new(rel) end end + + @scope.references!(PredicateBuilder.references(opts)) if Hash === opts @scope.where_values += where_value @scope end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 3968acba64..d3c853cfea 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -23,16 +23,16 @@ module ActiveRecord case file_name when /^(add|remove)_.*_(?:to|from)_(.*)/ @migration_action = $1 - @table_name = $2.pluralize + @table_name = normalize_table_name($2) when /join_table/ if attributes.length == 2 @migration_action = 'join' - @join_tables = attributes.map(&:plural_name) + @join_tables = pluralize_table_names? ? attributes.map(&:plural_name) : attributes.map(&:singular_name) set_index_names end when /^create_(.+)/ - @table_name = $1.pluralize + @table_name = normalize_table_name($1) @migration_template = "create_table_migration.rb" end end @@ -61,6 +61,10 @@ module ActiveRecord raise IllegalMigrationNameError.new(file_name) end end + + def normalize_table_name(_table_name) + pluralize_table_names? ? _table_name.pluralize : _table_name.singularize + end end end end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 0eb1231c79..696b859b36 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -218,7 +218,7 @@ module ActiveRecord @connection = Klass.connection end - def teardown + teardown do Klass.remove_connection end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 0878925a6c..e77138eb1d 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -11,7 +11,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end - def teardown + teardown do ActiveRecord::Base.remove_connection ActiveRecord::Base.establish_connection(@connection) end diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 5cd5d8ac5f..42ffb91095 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -1,6 +1,9 @@ require "cases/helper" +require 'support/ddl_helper' class MysqlConnectionTest < ActiveRecord::TestCase + include DdlHelper + class Klass < ActiveRecord::Base end @@ -69,59 +72,50 @@ class MysqlConnectionTest < ActiveRecord::TestCase end def test_exec_no_binds - @connection.exec_query('drop table if exists ex') - @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, - `data` varchar(255)) - eosql - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 0, result.rows.length - assert_equal 2, result.columns.length - assert_equal %w{ id data }, result.columns + with_example_table do + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 0, result.rows.length + assert_equal 2, result.columns.length + assert_equal %w{ id data }, result.columns - @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - # if there are no bind parameters, it will return a string (due to - # the libmysql api) - result = @connection.exec_query('SELECT id, data FROM ex') - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + # if there are no bind parameters, it will return a string (due to + # the libmysql api) + result = @connection.exec_query('SELECT id, data FROM ex') + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [['1', 'foo']], result.rows + assert_equal [['1', 'foo']], result.rows + end end def test_exec_with_binds - @connection.exec_query('drop table if exists ex') - @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, - `data` varchar(255)) - eosql - @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) + with_example_table do + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[nil, 1]]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end def test_exec_typecasts_bind_vals - @connection.exec_query('drop table if exists ex') - @connection.exec_query(<<-eosql) - CREATE TABLE `ex` (`id` int(11) auto_increment PRIMARY KEY, - `data` varchar(255)) - eosql - @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') - column = @connection.columns('ex').find { |col| col.name == 'id' } + with_example_table do + @connection.exec_query('INSERT INTO ex (id, data) VALUES (1, "foo")') + column = @connection.columns('ex').find { |col| col.name == 'id' } - result = @connection.exec_query( - 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) + result = @connection.exec_query( + 'SELECT id, data FROM ex WHERE id = ?', nil, [[column, '1-fuu']]) - assert_equal 1, result.rows.length - assert_equal 2, result.columns.length + assert_equal 1, result.rows.length + assert_equal 2, result.columns.length - assert_equal [[1, 'foo']], result.rows + assert_equal [[1, 'foo']], result.rows + end end # Test that MySQL allows multiple results for stored procedures @@ -174,4 +168,12 @@ class MysqlConnectionTest < ActiveRecord::TestCase ActiveRecord::Base.establish_connection(original_connection) end end + + def with_example_table(&block) + definition ||= <<-SQL + `id` int(11) auto_increment PRIMARY KEY, + `data` varchar(255) + SQL + super(@connection, 'ex', definition, &block) + end end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 578f6301bd..1699380eb3 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -1,19 +1,15 @@ # encoding: utf-8 require "cases/helper" +require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class MysqlAdapterTest < ActiveRecord::TestCase + include DdlHelper + def setup @conn = ActiveRecord::Base.connection - @conn.exec_query('drop table if exists ex') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex` ( - `id` int(11) auto_increment PRIMARY KEY, - `number` integer, - `data` varchar(255)) - eosql end def test_bad_connection_mysql @@ -25,8 +21,10 @@ module ActiveRecord end def test_valid_column - column = @conn.columns('ex').find { |col| col.name == 'id' } - assert @conn.valid_type?(column.type) + with_example_table do + column = @conn.columns('ex').find { |col| col.name == 'id' } + assert @conn.valid_type?(column.type) + end end def test_invalid_column @@ -38,31 +36,35 @@ module ActiveRecord end def test_exec_insert_number - insert(@conn, 'number' => 10) + with_example_table do + insert(@conn, 'number' => 10) - result = @conn.exec_query('SELECT number FROM ex WHERE number = 10') + result = @conn.exec_query('SELECT number FROM ex WHERE number = 10') - assert_equal 1, result.rows.length - # if there are no bind parameters, it will return a string (due to - # the libmysql api) - assert_equal '10', result.rows.last.last + assert_equal 1, result.rows.length + # if there are no bind parameters, it will return a string (due to + # the libmysql api) + assert_equal '10', result.rows.last.last + end end def test_exec_insert_string - str = 'いただきます!' - insert(@conn, 'number' => 10, 'data' => str) + with_example_table do + str = 'いただきます!' + insert(@conn, 'number' => 10, 'data' => str) - result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10') + result = @conn.exec_query('SELECT number, data FROM ex WHERE number = 10') - value = result.rows.last.last + value = result.rows.last.last - # FIXME: this should probably be inside the mysql AR adapter? - value.force_encoding(@conn.client_encoding) + # FIXME: this should probably be inside the mysql AR adapter? + value.force_encoding(@conn.client_encoding) - # The strings in this file are utf-8, so transcode to utf-8 - value.encode!(Encoding::UTF_8) + # The strings in this file are utf-8, so transcode to utf-8 + value.encode!(Encoding::UTF_8) - assert_equal str, value + assert_equal str, value + end end def test_tables_quoting @@ -74,46 +76,37 @@ module ActiveRecord end def test_pk_and_sequence_for - pk, seq = @conn.pk_and_sequence_for('ex') - assert_equal 'id', pk - assert_equal @conn.default_sequence_name('ex', 'id'), seq + with_example_table do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end end def test_pk_and_sequence_for_with_non_standard_primary_key - @conn.exec_query('drop table if exists ex_with_non_standard_pk') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex_with_non_standard_pk` ( - `code` INT(11) auto_increment, - PRIMARY KEY (`code`)) - eosql - pk, seq = @conn.pk_and_sequence_for('ex_with_non_standard_pk') - assert_equal 'code', pk - assert_equal @conn.default_sequence_name('ex_with_non_standard_pk', 'code'), seq + with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'code', pk + assert_equal @conn.default_sequence_name('ex', 'code'), seq + end end def test_pk_and_sequence_for_with_custom_index_type_pk - @conn.exec_query('drop table if exists ex_with_custom_index_type_pk') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex_with_custom_index_type_pk` ( - `id` INT(11) auto_increment, - PRIMARY KEY USING BTREE (`id`)) - eosql - pk, seq = @conn.pk_and_sequence_for('ex_with_custom_index_type_pk') - assert_equal 'id', pk - assert_equal @conn.default_sequence_name('ex_with_custom_index_type_pk', 'id'), seq + with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do + pk, seq = @conn.pk_and_sequence_for('ex') + assert_equal 'id', pk + assert_equal @conn.default_sequence_name('ex', 'id'), seq + end end def test_tinyint_integer_typecasting - @conn.exec_query('drop table if exists ex_with_non_boolean_tinyint_column') - @conn.exec_query(<<-eosql) - CREATE TABLE `ex_with_non_boolean_tinyint_column` ( - `status` TINYINT(4)) - eosql - insert(@conn, { 'status' => 2 }, 'ex_with_non_boolean_tinyint_column') + with_example_table '`status` TINYINT(4)' do + insert(@conn, { 'status' => 2 }, 'ex') - result = @conn.exec_query('SELECT status FROM ex_with_non_boolean_tinyint_column') + result = @conn.exec_query('SELECT status FROM ex') - assert_equal 2, result.column_types['status'].type_cast(result.last['status']) + assert_equal 2, result.column_types['status'].type_cast(result.last['status']) + end end def test_supports_extensions @@ -140,6 +133,15 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end + + def with_example_table(definition = nil, &block) + definition ||= <<-SQL + `id` int(11) auto_increment PRIMARY KEY, + `number` integer, + `data` varchar(255) + SQL + super(@conn, 'ex', definition, &block) + end end end end diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index 8eb9565963..61ae0abfd1 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase 'distinct_select'=>'distinct_id int, select_id int' end - def teardown + teardown do drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 4ccf568406..7905edaf83 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -11,7 +11,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase end end - def teardown + teardown do ActiveRecord::Base.remove_connection ActiveRecord::Base.establish_connection(@connection) end diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 1a82308176..799d927ee4 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -37,7 +37,7 @@ class MysqlReservedWordTest < ActiveRecord::TestCase 'distinct_select'=>'distinct_id int, select_id int' end - def teardown + teardown do drop_tables_directly ['group', 'select', 'values', 'distinct', 'distinct_select', 'order'] end diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 22dd48e113..3808db5141 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -7,7 +7,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase end end - def teardown + teardown do ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do remove_method :execute end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 3090f4478f..f5f1c791e1 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -19,7 +19,7 @@ class PostgresqlArrayTest < ActiveRecord::TestCase @column = PgArray.columns.find { |c| c.name == 'tags' } end - def teardown + teardown do @connection.execute 'drop table if exists pg_arrays' end diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index b8dd35c4c5..c3394d7712 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -23,7 +23,7 @@ class PostgresqlByteaTest < ActiveRecord::TestCase assert(@column.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLColumn)) end - def teardown + teardown do @connection.execute 'drop table if exists bytea_data_type' end diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index ebb9ff98b1..148dc9d07c 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -25,7 +25,7 @@ class PostgresqlCitextTest < ActiveRecord::TestCase @column = Citext.columns_hash['cival'] end - def teardown + teardown do @connection.execute 'DROP TABLE IF EXISTS citexts;' @connection.execute 'DROP EXTENSION IF EXISTS citext CASCADE;' end diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 7202cce390..ed39204145 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -8,7 +8,7 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase self.table_name = "postgresql_composites" end - def teardown + teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_composites' @connection.execute 'DROP TYPE IF EXISTS full_address' end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 4715fa002d..aa4996133f 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -45,6 +45,37 @@ module ActiveRecord assert_equal 'off', expect end + def test_reset + @connection.query('ROLLBACK') + @connection.query('SET geqo TO off') + + # Verify the setting has been applied. + expect = @connection.query('show geqo').first.first + assert_equal 'off', expect + + @connection.reset! + + # Verify the setting has been cleared. + expect = @connection.query('show geqo').first.first + assert_equal 'on', expect + end + + def test_reset_with_transaction + @connection.query('ROLLBACK') + @connection.query('SET geqo TO off') + + # Verify the setting has been applied. + expect = @connection.query('show geqo').first.first + assert_equal 'off', expect + + @connection.query('BEGIN') + @connection.reset! + + # Verify the setting has been cleared. + expect = @connection.query('show geqo').first.first + assert_equal 'on', expect + end + def test_tables_logs_name @connection.tables('hello') assert_equal 'SCHEMA', @subscriber.logged[0][1] diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 158bc6ee89..e7dda1a1af 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -67,7 +67,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase @connection.execute("INSERT INTO postgresql_timestamp_with_zones (id, time) VALUES (1, '2010-01-01 10:00:00-1')") end - def teardown + teardown do [PostgresqlArray, PostgresqlTsvector, PostgresqlMoney, PostgresqlNumber, PostgresqlTime, PostgresqlNetworkAddress, PostgresqlBitString, PostgresqlOid, PostgresqlTimestampWithZone].each(&:delete_all) end diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index 62f84caf91..7208edef5f 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -8,7 +8,7 @@ class PostgresqlEnumTest < ActiveRecord::TestCase self.table_name = "postgresql_enums" end - def teardown + teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_enums' @connection.execute 'DROP TYPE IF EXISTS mood' end diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index f2502430de..90ec1524b5 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -31,7 +31,7 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase @column = Hstore.columns.find { |c| c.name == 'tags' } end - def teardown + teardown do @connection.execute 'drop table if exists hstores' end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 3daef399d8..10c0a6c395 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -26,7 +26,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase @column = JsonDataType.columns.find { |c| c.name == 'payload' } end - def teardown + teardown do @connection.execute 'drop table if exists json_data_type' end diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index 5d12ca75ca..e72fd4360d 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -19,7 +19,7 @@ class PostgresqlLtreeTest < ActiveRecord::TestCase skip "do not test on PG without ltree" end - def teardown + teardown do @connection.execute 'drop table if exists ltrees' end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 019406dd84..49d8ec238d 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -1,9 +1,12 @@ # encoding: utf-8 require "cases/helper" +require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapterTest < ActiveRecord::TestCase + include DdlHelper + def setup @connection = ActiveRecord::Base.connection end @@ -176,6 +179,51 @@ module ActiveRecord assert_nil @connection.pk_and_sequence_for('unobtainium') end + def test_pk_and_sequence_for_with_collision_pg_class_oid + @connection.exec_query('create table ex(id serial primary key)') + @connection.exec_query('create table ex2(id serial primary key)') + + correct_depend_record = [ + "'pg_class'::regclass", + "'ex_id_seq'::regclass", + '0', + "'pg_class'::regclass", + "'ex'::regclass", + '1', + "'a'" + ] + + collision_depend_record = [ + "'pg_attrdef'::regclass", + "'ex2_id_seq'::regclass", + '0', + "'pg_class'::regclass", + "'ex'::regclass", + '1', + "'a'" + ] + + @connection.exec_query( + "DELETE FROM pg_depend WHERE objid = 'ex_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" + ) + @connection.exec_query( + "INSERT INTO pg_depend VALUES(#{collision_depend_record.join(',')})" + ) + @connection.exec_query( + "INSERT INTO pg_depend VALUES(#{correct_depend_record.join(',')})" + ) + + seq = @connection.pk_and_sequence_for('ex').last + assert_equal 'ex_id_seq', seq + + @connection.exec_query( + "DELETE FROM pg_depend WHERE objid = 'ex2_id_seq'::regclass AND refobjid = 'ex'::regclass AND deptype = 'a'" + ) + ensure + @connection.exec_query('DROP TABLE IF EXISTS ex') + @connection.exec_query('DROP TABLE IF EXISTS ex2') + end + def test_exec_insert_number with_example_table do insert(@connection, 'number' => 10) @@ -324,12 +372,8 @@ module ActiveRecord ctx.exec_insert(sql, 'SQL', binds) end - def with_example_table(definition = nil) - definition ||= 'id serial primary key, number integer, data character varying(255)' - @connection.exec_query("create table ex(#{definition})") - yield - ensure - @connection.exec_query('drop table if exists ex') + def with_example_table(definition = 'id serial primary key, number integer, data character varying(255)', &block) + super(@connection, 'ex', definition, &block) end def connection_without_insert_returning diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index 4e84335e9b..a9d4312fb3 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -8,7 +8,7 @@ if ActiveRecord::Base.connection.supports_ranges? end class PostgresqlRangeTest < ActiveRecord::TestCase - def teardown + teardown do @connection.execute 'DROP TABLE IF EXISTS postgresql_ranges' @connection.execute 'DROP TYPE IF EXISTS floatrange' end diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index d5e1838543..99c26c4bf7 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -27,7 +27,7 @@ class SchemaAuthorizationTest < ActiveRecord::TestCase end end - def teardown + teardown do set_session_auth @connection.execute "RESET search_path" USERS.each do |u| diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 3f7009c1d1..5e5f653d17 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -71,7 +71,7 @@ class SchemaTest < ActiveRecord::TestCase @connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))" end - def teardown + teardown do @connection.execute "DROP SCHEMA #{SCHEMA2_NAME} CASCADE" @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 66e07b71a0..08071894c4 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -24,7 +24,7 @@ class ViewTest < ActiveRecord::TestCase @connection.execute "CREATE VIEW #{SCHEMA_NAME}.#{VIEW_NAME} AS SELECT id,name,email,moment FROM #{SCHEMA_NAME}.#{TABLE_NAME}" end - def teardown + teardown do @connection.execute "DROP SCHEMA #{SCHEMA_NAME} CASCADE" end diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index dd2a727afe..ae299697b1 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -23,7 +23,7 @@ class PostgresqlXMLTest < ActiveRecord::TestCase @column = XmlDataType.columns.find { |c| c.name == 'payload' } end - def teardown + teardown do @connection.execute 'drop table if exists xml_data_type' end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 500df52cd8..811695938e 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -10,7 +10,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::SchemaMigration.drop_table end - def teardown + teardown do @connection.drop_table :fruits rescue nil @connection.drop_table :nep_fruits rescue nil @connection.drop_table :nep_schema_migrations rescue nil diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 5c9ec3104f..7994f76d08 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -835,6 +835,17 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 0, comments(:greetings).reload.children_count end + def test_belongs_to_with_id_assigning + post = posts(:welcome) + comment = Comment.create! body: "foo", post: post + parent = comments(:greetings) + assert_equal 0, parent.reload.children_count + comment.parent_id = parent.id + + comment.save! + assert_equal 1, parent.reload.children_count + end + def test_polymorphic_with_custom_primary_key toy = Toy.create! sponsor = Sponsor.create!(:sponsorable => toy) diff --git a/activerecord/test/cases/associations/eager_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index 5ff117eaa0..0ff87d53ea 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -68,7 +68,7 @@ class EagerLoadPolyAssocsTest < ActiveRecord::TestCase generate_test_object_graphs end - def teardown + teardown do [Circle, Square, Triangle, PaintColor, PaintTexture, ShapeExpression, NonPolyOne, NonPolyTwo].each do |c| c.delete_all @@ -111,7 +111,7 @@ class EagerLoadNestedIncludeWithMissingDataTest < ActiveRecord::TestCase @first_categorization = @davey_mcdave.categorizations.create(:category => Category.first, :post => @first_post) end - def teardown + teardown do @davey_mcdave.destroy @first_post.destroy @first_comment.destroy diff --git a/activerecord/test/cases/associations/eager_singularization_test.rb b/activerecord/test/cases/associations/eager_singularization_test.rb index b12bc355e8..a61a070331 100644 --- a/activerecord/test/cases/associations/eager_singularization_test.rb +++ b/activerecord/test/cases/associations/eager_singularization_test.rb @@ -90,7 +90,7 @@ class EagerSingularizationTest < ActiveRecord::TestCase end end - def teardown + teardown do connection.drop_table :viri connection.drop_table :octopi connection.drop_table :passes diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 416a39ea4c..e59161fc5b 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1203,4 +1203,14 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_equal 5, author.posts.size } end + + test "including associations with where.not adds implicit references" do + author = assert_queries(2) { + Author.includes(:posts).where.not(posts: { title: 'Welcome to the weblog'} ).last + } + + assert_no_queries { + assert_equal 2, author.posts.size + } + end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index bac1cb8e2d..366472c6fd 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -11,6 +11,7 @@ require 'models/author' require 'models/tag' require 'models/tagging' require 'models/parrot' +require 'models/person' require 'models/pirate' require 'models/treasure' require 'models/price_estimate' @@ -795,4 +796,27 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end end + def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_create + rich_person = RichPerson.new + + treasure = Treasure.new + treasure.rich_people << rich_person + treasure.valid? + + assert_equal 1, treasure.rich_people.size + assert_nil rich_person.first_name, 'should not run associated person validation on create when validate: false' + end + + def test_association_with_validate_false_does_not_run_associated_validation_callbacks_on_update + rich_person = RichPerson.create! + person_first_name = rich_person.first_name + assert_not_nil person_first_name + + treasure = Treasure.new + treasure.rich_people << rich_person + treasure.valid? + + assert_equal 1, treasure.rich_people.size + assert_equal person_first_name, rich_person.first_name, 'should not run associated person validation on update when validate: false' + end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 173081cb28..952baaca36 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -22,7 +22,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase @target.table_name = 'topics' end - def teardown + teardown do ActiveRecord::Base.send(:attribute_method_matchers).clear ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 09892d50ba..f7584c3a51 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -683,23 +683,10 @@ class TestDestroyAsPartOfAutosaveAssociation < ActiveRecord::TestCase end end - @ship.pirate.catchphrase = "Changed Catchphrase" - assert_raise(RuntimeError) { assert !@pirate.save } assert_not_nil @pirate.reload.ship end - def test_should_save_changed_has_one_changed_object_if_child_is_saved - @pirate.ship.name = "NewName" - assert @pirate.save - assert_equal "NewName", @pirate.ship.reload.name - end - - def test_should_not_save_changed_has_one_unchanged_object_if_child_is_saved - @pirate.ship.expects(:save).never - assert @pirate.save - end - # belongs_to def test_should_destroy_a_parent_association_as_part_of_the_save_transaction_if_it_was_marked_for_destroyal assert !@ship.pirate.marked_for_destruction? diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb index 291751c435..24eb91bef9 100644 --- a/activerecord/test/cases/bind_parameter_test.rb +++ b/activerecord/test/cases/bind_parameter_test.rb @@ -25,7 +25,7 @@ module ActiveRecord ActiveSupport::Notifications.subscribe('sql.active_record', @listener) end - def teardown + teardown do ActiveSupport::Notifications.unsubscribe(@listener) end diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb index eb2fe5639b..deed226eab 100644 --- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb +++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb @@ -29,12 +29,6 @@ module ActiveRecord assert_not adapter.lease, 'should not lease adapter' end - def test_last_use - assert_not adapter.last_use - adapter.lease - assert adapter.last_use - end - def test_expire_mutates_in_use assert adapter.lease, 'lease adapter' assert adapter.in_use?, 'adapter is in use' diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 599e8c762c..2992ceb211 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -13,10 +13,16 @@ module ActiveRecord @previous_database_url = ENV.delete("DATABASE_URL") end - def teardown + teardown do ENV["DATABASE_URL"] = @previous_database_url end + def test_jdbc_url + config = { "production" => { "url" => "jdbc:postgres://localhost/foo" } } + actual = klass.new(config).resolve + assert_equal config, actual + end + def test_environment_does_not_exist_in_config_url_does_exist ENV['DATABASE_URL'] = "postgres://localhost/foo" config = { "not_production" => { "adapter" => "not_postgres", "database" => "not_foo" } } diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 1cf215017b..8d15a76735 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require 'active_support/concurrency/latch' module ActiveRecord module ConnectionAdapters @@ -22,8 +23,7 @@ module ActiveRecord end end - def teardown - super + teardown do @pool.disconnect! end @@ -89,10 +89,9 @@ module ActiveRecord end def test_full_pool_exception + @pool.size.times { @pool.checkout } assert_raises(ConnectionTimeoutError) do - (@pool.size + 1).times do - @pool.checkout - end + @pool.checkout end end @@ -125,7 +124,6 @@ module ActiveRecord @pool.checkout @pool.checkout @pool.checkout - @pool.dead_connection_timeout = 0 connections = @pool.connections.dup @@ -135,21 +133,25 @@ module ActiveRecord end def test_reap_inactive + ready = ActiveSupport::Concurrency::Latch.new @pool.checkout - @pool.checkout - @pool.checkout - @pool.dead_connection_timeout = 0 - - connections = @pool.connections.dup - connections.each do |conn| - conn.extend(Module.new { def active_threadsafe?; false; end; }) + child = Thread.new do + @pool.checkout + @pool.checkout + ready.release + Thread.stop end + ready.await + + assert_equal 3, active_connections(@pool).size + child.terminate + child.join @pool.reap - assert_equal 0, @pool.connections.length + assert_equal 1, active_connections(@pool).size ensure - connections.each(&:close) + @pool.connections.each(&:close) end def test_remove_connection diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 7e3d91e08c..7d438803a1 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -206,7 +206,7 @@ if current_adapter?(:PostgreSQLAdapter) assert_equal "some text", Default.new.text_col, "Default of text column was not correctly parse after updating default using '::text' since postgreSQL will add parens to the default in db" end - def teardown + teardown do @connection.schema_search_path = @old_search_path Default.reset_column_information end diff --git a/activerecord/test/cases/disconnected_test.rb b/activerecord/test/cases/disconnected_test.rb index 9e268dad74..94447addc1 100644 --- a/activerecord/test/cases/disconnected_test.rb +++ b/activerecord/test/cases/disconnected_test.rb @@ -10,7 +10,7 @@ class TestDisconnectedAdapter < ActiveRecord::TestCase @connection = ActiveRecord::Base.connection end - def teardown + teardown do return if in_memory_db? spec = ActiveRecord::Base.connection_config ActiveRecord::Base.establish_connection(spec) diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index b00e2744b9..8de2ddb10d 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -48,7 +48,7 @@ if ActiveRecord::Base.connection.supports_explain? assert queries.empty? end - def teardown + teardown do ActiveRecord::ExplainRegistry.reset end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 37c6af74da..1147418815 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -782,6 +782,10 @@ class FoxyFixturesTest < ActiveRecord::TestCase assert_equal("frederick", parrots(:frederick).name) end + def test_supports_label_string_interpolation + assert_equal("X marks the spot!", pirates(:mark).catchphrase) + end + def test_supports_polymorphic_belongs_to assert_equal(pirates(:redbeard), treasures(:sapphire).looter) assert_equal(parrots(:louis), treasures(:ruby).looter) diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index e2ff2aa451..f5f85f2412 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -339,7 +339,7 @@ class InheritanceComputeTypeTest < ActiveRecord::TestCase ActiveSupport::Dependencies.log_activity = true end - def teardown + teardown do ActiveSupport::Dependencies.log_activity = false self.class.const_remove :FirmOnTheFly rescue nil Firm.const_remove :FirmOnTheFly rescue nil diff --git a/activerecord/test/cases/invalid_connection_test.rb b/activerecord/test/cases/invalid_connection_test.rb index f6774d7ef4..8416c81f45 100644 --- a/activerecord/test/cases/invalid_connection_test.rb +++ b/activerecord/test/cases/invalid_connection_test.rb @@ -12,7 +12,7 @@ class TestAdapterWithInvalidConnection < ActiveRecord::TestCase Bird.establish_connection adapter: 'mysql', database: 'i_do_not_exist' end - def teardown + teardown do Bird.remove_connection end diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 2b1749cf70..285172d33e 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -122,7 +122,7 @@ module ActiveRecord end end - def teardown + teardown do %w[horses new_horses].each do |table| if ActiveRecord::Base.connection.table_exists?(table) ActiveRecord::Base.connection.drop_table(table) diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 294f2eb9fe..5418d913b0 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -11,8 +11,7 @@ module ActiveRecord @table_name = :testings end - def teardown - super + teardown do connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil end diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index c1d7cd5874..a6d506b04a 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -8,7 +8,7 @@ module ActiveRecord @connection = Minitest::Mock.new end - def teardown + teardown do assert @connection.verify end diff --git a/activerecord/test/cases/migration/column_positioning_test.rb b/activerecord/test/cases/migration/column_positioning_test.rb index 87e29e41ba..77a752f050 100644 --- a/activerecord/test/cases/migration/column_positioning_test.rb +++ b/activerecord/test/cases/migration/column_positioning_test.rb @@ -18,8 +18,7 @@ module ActiveRecord end end - def teardown - super + teardown do connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil end diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index efaec0f823..62b60f7f7b 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -10,8 +10,7 @@ module ActiveRecord @connection = ActiveRecord::Base.connection end - def teardown - super + teardown do %w(artists_musics musics_videos catalog).each do |table_name| connection.drop_table table_name if connection.tables.include?(table_name) end diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 8d1daa0a04..35af11f672 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -21,8 +21,7 @@ module ActiveRecord end end - def teardown - super + teardown do connection.drop_table :testings rescue nil ActiveRecord::Base.primary_key_prefix_type = nil end diff --git a/activerecord/test/cases/migration/logger_test.rb b/activerecord/test/cases/migration/logger_test.rb index 97efb94b66..84224e6e4c 100644 --- a/activerecord/test/cases/migration/logger_test.rb +++ b/activerecord/test/cases/migration/logger_test.rb @@ -19,8 +19,7 @@ module ActiveRecord ActiveRecord::SchemaMigration.delete_all end - def teardown - super + teardown do ActiveRecord::SchemaMigration.drop_table end diff --git a/activerecord/test/cases/migration/references_index_test.rb b/activerecord/test/cases/migration/references_index_test.rb index 19eb7d3c9e..4485701a4e 100644 --- a/activerecord/test/cases/migration/references_index_test.rb +++ b/activerecord/test/cases/migration/references_index_test.rb @@ -11,8 +11,7 @@ module ActiveRecord @table_name = :testings end - def teardown - super + teardown do connection.drop_table :testings rescue nil end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 1bda472d23..455ec78f68 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -33,7 +33,7 @@ class MigrationTest < ActiveRecord::TestCase ActiveRecord::Base.connection.schema_cache.clear! end - def teardown + teardown do ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" @@ -585,7 +585,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? Person.reset_sequence_name end - def teardown + teardown do Person.connection.drop_table(:delete_me) rescue nil end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index 3f9854200d..c77a818b93 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -26,8 +26,7 @@ module ActiveRecord ActiveRecord::SchemaMigration.delete_all rescue nil end - def teardown - super + teardown do ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = true end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 9124105e6d..f7db195521 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -18,7 +18,7 @@ class ModulesTest < ActiveRecord::TestCase ActiveRecord::Base.store_full_sti_class = false end - def teardown + teardown do # reinstate the constants that we undefined in the setup @undefined_consts.each do |constant, value| Object.send :const_set, constant, value unless value.nil? diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 2f89699df7..cf96c3fccf 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -11,24 +11,8 @@ require "models/owner" require "models/pet" require 'active_support/hash_with_indifferent_access' -module AssertRaiseWithMessage - def assert_raise_with_message(expected_exception, expected_message) - begin - error_raised = false - yield - rescue expected_exception => error - error_raised = true - actual_message = error.message - end - assert error_raised - assert_equal expected_message, actual_message - end -end - class TestNestedAttributesInGeneral < ActiveRecord::TestCase - include AssertRaiseWithMessage - - def teardown + teardown do Pirate.accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } end @@ -71,9 +55,10 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase end def test_should_raise_an_ArgumentError_for_non_existing_associations - assert_raise_with_message ArgumentError, "No association found for name `honesty'. Has it been defined yet?" do + exception = assert_raise ArgumentError do Pirate.accepts_nested_attributes_for :honesty end + assert_equal "No association found for name `honesty'. Has it been defined yet?", exception.message end def test_should_disable_allow_destroy_by_default @@ -213,17 +198,16 @@ class TestNestedAttributesInGeneral < ActiveRecord::TestCase end class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase - include AssertRaiseWithMessage - def setup @pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") @ship = @pirate.create_ship(:name => 'Nights Dirty Lightning') end def test_should_raise_argument_error_if_trying_to_build_polymorphic_belongs_to - assert_raise_with_message ArgumentError, "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?" do + exception = assert_raise ArgumentError do Treasure.new(:name => 'pearl', :looter_attributes => {:catchphrase => "Arrr"}) end + assert_equal "Cannot build association `looter'. Are you trying to build a polymorphic one-to-one association?", exception.message end def test_should_define_an_attribute_writer_method_for_the_association @@ -275,9 +259,10 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record - assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}" do + exception = assert_raise ActiveRecord::RecordNotFound do @pirate.ship_attributes = { :id => 1234567890 } end + assert_equal "Couldn't find Ship with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message end def test_should_take_a_hash_with_string_keys_and_update_the_associated_model @@ -403,8 +388,6 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase - include AssertRaiseWithMessage - def setup @ship = Ship.new(:name => 'Nights Dirty Lightning') @pirate = @ship.build_pirate(:catchphrase => 'Aye') @@ -460,9 +443,10 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record - assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}" do + exception = assert_raise ActiveRecord::RecordNotFound do @ship.pirate_attributes = { :id => 1234567890 } end + assert_equal "Couldn't find Pirate with ID=1234567890 for Ship with ID=#{@ship.id}", exception.message end def test_should_take_a_hash_with_string_keys_and_update_the_associated_model @@ -579,8 +563,6 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end module NestedAttributesOnACollectionAssociationTests - include AssertRaiseWithMessage - def test_should_define_an_attribute_writer_method_for_the_association assert_respond_to @pirate, association_setter end @@ -670,9 +652,10 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record - assert_raise_with_message ActiveRecord::RecordNotFound, "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}" do + exception = assert_raise ActiveRecord::RecordNotFound do @pirate.attributes = { association_getter => [{ :id => 1234567890 }] } end + assert_equal "Couldn't find #{@child_1.class.name} with ID=1234567890 for Pirate with ID=#{@pirate.id}", exception.message end def test_should_automatically_build_new_associated_models_for_each_entry_in_a_hash_where_the_id_is_missing @@ -727,9 +710,10 @@ module NestedAttributesOnACollectionAssociationTests assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, {}) } assert_nothing_raised(ArgumentError) { @pirate.send(association_setter, Hash.new) } - assert_raise_with_message ArgumentError, 'Hash or Array expected, got String ("foo")' do + exception = assert_raise ArgumentError do @pirate.send(association_setter, "foo") end + assert_equal 'Hash or Array expected, got String ("foo")', exception.message end def test_should_work_with_update_as_well diff --git a/activerecord/test/cases/pooled_connections_test.rb b/activerecord/test/cases/pooled_connections_test.rb index 626c6aeaf8..dd0e934ec2 100644 --- a/activerecord/test/cases/pooled_connections_test.rb +++ b/activerecord/test/cases/pooled_connections_test.rb @@ -10,7 +10,7 @@ class PooledConnectionsTest < ActiveRecord::TestCase @connection = ActiveRecord::Base.remove_connection end - def teardown + teardown do ActiveRecord::Base.clear_all_connections! ActiveRecord::Base.establish_connection(@connection) @per_test_teardown.each {|td| td.call } diff --git a/activerecord/test/cases/reaper_test.rb b/activerecord/test/cases/reaper_test.rb index b62a41c08e..f52fd22489 100644 --- a/activerecord/test/cases/reaper_test.rb +++ b/activerecord/test/cases/reaper_test.rb @@ -10,8 +10,7 @@ module ActiveRecord @pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec end - def teardown - super + teardown do @pool.connections.each(&:close) end @@ -64,17 +63,22 @@ module ActiveRecord spec.config[:reaping_frequency] = 0.0001 pool = ConnectionPool.new spec - pool.dead_connection_timeout = 0 - conn = pool.checkout - count = pool.connections.length + conn = nil + child = Thread.new do + conn = pool.checkout + Thread.stop + end + Thread.pass while conn.nil? + + assert conn.in_use? - conn.extend(Module.new { def active_threadsafe?; false; end; }) + child.terminate - while count == pool.connections.length + while conn.in_use? Thread.pass end - assert_equal(count - 1, pool.connections.length) + assert !conn.in_use? end end end diff --git a/activerecord/test/cases/relation/predicate_builder_test.rb b/activerecord/test/cases/relation/predicate_builder_test.rb index 14a8d97d36..4057835688 100644 --- a/activerecord/test/cases/relation/predicate_builder_test.rb +++ b/activerecord/test/cases/relation/predicate_builder_test.rb @@ -5,10 +5,10 @@ module ActiveRecord class PredicateBuilderTest < ActiveRecord::TestCase def test_registering_new_handlers PredicateBuilder.register_handler(Regexp, proc do |column, value| - Arel::Nodes::InfixOperation.new('~', column, value.source) + Arel::Nodes::InfixOperation.new('~', column, Arel.sql(value.source)) end) - assert_match %r{["`]topics["`].["`]title["`] ~ 'rails'}i, Topic.where(title: /rails/).to_sql + assert_match %r{["`]topics["`].["`]title["`] ~ rails}i, Topic.where(title: /rails/).to_sql end end end diff --git a/activerecord/test/cases/relation/where_chain_test.rb b/activerecord/test/cases/relation/where_chain_test.rb index fd2420cb88..c628ca44ff 100644 --- a/activerecord/test/cases/relation/where_chain_test.rb +++ b/activerecord/test/cases/relation/where_chain_test.rb @@ -12,13 +12,13 @@ module ActiveRecord end def test_not_eq - expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello') + expected = Post.arel_table[@name].not_eq('hello') relation = Post.where.not(title: 'hello') assert_equal([expected], relation.where_values) end def test_not_null - expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], nil) + expected = Post.arel_table[@name].not_eq(nil) relation = Post.where.not(title: nil) assert_equal([expected], relation.where_values) end @@ -30,13 +30,13 @@ module ActiveRecord end def test_not_in - expected = Arel::Nodes::NotIn.new(Post.arel_table[@name], %w[hello goodbye]) + expected = Post.arel_table[@name].not_in(%w[hello goodbye]) relation = Post.where.not(title: %w[hello goodbye]) assert_equal([expected], relation.where_values) end def test_association_not_eq - expected = Arel::Nodes::NotEqual.new(Comment.arel_table[@name], 'hello') + expected = Comment.arel_table[@name].not_eq('hello') relation = Post.joins(:comments).where.not(comments: {title: 'hello'}) assert_equal(expected.to_sql, relation.where_values.first.to_sql) end @@ -44,20 +44,20 @@ module ActiveRecord def test_not_eq_with_preceding_where relation = Post.where(title: 'hello').where.not(title: 'world') - expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'hello') + expected = Post.arel_table[@name].eq('hello') assert_equal(expected, relation.where_values.first) - expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'world') + expected = Post.arel_table[@name].not_eq('world') assert_equal(expected, relation.where_values.last) end def test_not_eq_with_succeeding_where relation = Post.where.not(title: 'hello').where(title: 'world') - expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'hello') + expected = Post.arel_table[@name].not_eq('hello') assert_equal(expected, relation.where_values.first) - expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'world') + expected = Post.arel_table[@name].eq('world') assert_equal(expected, relation.where_values.last) end @@ -76,17 +76,17 @@ module ActiveRecord def test_chaining_multiple relation = Post.where.not(author_id: [1, 2]).where.not(title: 'ruby on rails') - expected = Arel::Nodes::NotIn.new(Post.arel_table['author_id'], [1, 2]) + expected = Post.arel_table['author_id'].not_in([1, 2]) assert_equal(expected, relation.where_values[0]) - expected = Arel::Nodes::NotEqual.new(Post.arel_table[@name], 'ruby on rails') + expected = Post.arel_table[@name].not_eq('ruby on rails') assert_equal(expected, relation.where_values[1]) end def test_rewhere_with_one_condition relation = Post.where(title: 'hello').where(title: 'world').rewhere(title: 'alone') - expected = Arel::Nodes::Equality.new(Post.arel_table[@name], 'alone') + expected = Post.arel_table[@name].eq('alone') assert_equal 1, relation.where_values.size assert_equal expected, relation.where_values.first end @@ -94,8 +94,8 @@ module ActiveRecord def test_rewhere_with_multiple_overwriting_conditions relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone', body: 'again') - title_expected = Arel::Nodes::Equality.new(Post.arel_table['title'], 'alone') - body_expected = Arel::Nodes::Equality.new(Post.arel_table['body'], 'again') + title_expected = Post.arel_table['title'].eq('alone') + body_expected = Post.arel_table['body'].eq('again') assert_equal 2, relation.where_values.size assert_equal title_expected, relation.where_values.first @@ -105,8 +105,8 @@ module ActiveRecord def test_rewhere_with_one_overwriting_condition_and_one_unrelated relation = Post.where(title: 'hello').where(body: 'world').rewhere(title: 'alone') - title_expected = Arel::Nodes::Equality.new(Post.arel_table['title'], 'alone') - body_expected = Arel::Nodes::Equality.new(Post.arel_table['body'], 'world') + title_expected = Post.arel_table['title'].eq('alone') + body_expected = Post.arel_table['body'].eq('world') assert_equal 2, relation.where_values.size assert_equal body_expected, relation.where_values.first diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 3b4b4c92f0..fddb7c204a 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -1366,6 +1366,14 @@ class RelationTest < ActiveRecord::TestCase assert_equal ['comments'], scope.references_values end + def test_automatically_added_where_not_references + scope = Post.where.not(comments: { body: "Bla" }) + assert_equal ['comments'], scope.references_values + + scope = Post.where.not('comments.body' => 'Bla') + assert_equal ['comments'], scope.references_values + end + def test_automatically_added_having_references scope = Post.having(:comments => { :body => "Bla" }) assert_equal ['comments'], scope.references_values diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 0018fc06f2..d8a467ec4d 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -192,8 +192,9 @@ class NestedRelationScopingTest < ActiveRecord::TestCase Developer.where('salary = 80000').scoping do Developer.limit(10).scoping do devs = Developer.all - assert_match '(salary = 80000)', devs.to_sql - assert_equal 10, devs.taken + sql = devs.to_sql + assert_match '(salary = 80000)', sql + assert_match 'LIMIT 10', sql end end end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index bc67da8d27..5609cf310c 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -10,8 +10,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase MyObject = Struct.new :attribute1, :attribute2 - def teardown - super + teardown do Topic.serialize("content") end diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 717e0e1866..5308fa8808 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -89,6 +89,18 @@ class TimestampTest < ActiveRecord::TestCase assert_in_delta Time.now, task.ending, 1 end + def test_touching_many_attributes_updates_them + task = Task.first + previous_starting = task.starting + previous_ending = task.ending + task.touch(:starting, :ending) + + assert_not_equal previous_starting, task.starting + assert_not_equal previous_ending, task.ending + assert_in_delta Time.now, task.starting, 1 + assert_in_delta Time.now, task.ending, 1 + end + def test_touching_a_record_without_timestamps_is_unexceptional assert_nothing_raised { Car.first.touch } end diff --git a/activerecord/test/cases/unconnected_test.rb b/activerecord/test/cases/unconnected_test.rb index e82ca3f93d..afb893a52c 100644 --- a/activerecord/test/cases/unconnected_test.rb +++ b/activerecord/test/cases/unconnected_test.rb @@ -11,7 +11,7 @@ class TestUnconnectedAdapter < ActiveRecord::TestCase @specification = ActiveRecord::Base.remove_connection end - def teardown + teardown do @underlying = nil ActiveRecord::Base.establish_connection(@specification) load_schema if in_memory_db? diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index efa0c9b934..3db742c15b 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -14,7 +14,7 @@ class I18nValidationTest < ActiveRecord::TestCase I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) end - def teardown + teardown do I18n.load_path.replace @old_load_path I18n.backend = @old_backend end diff --git a/activerecord/test/fixtures/pirates.yml b/activerecord/test/fixtures/pirates.yml index 6004f390a4..1bb3bf0051 100644 --- a/activerecord/test/fixtures/pirates.yml +++ b/activerecord/test/fixtures/pirates.yml @@ -7,3 +7,6 @@ redbeard: parrot: louis created_on: "<%= 2.weeks.ago.to_s(:db) %>" updated_on: "<%= 2.weeks.ago.to_s(:db) %>" + +mark: + catchphrase: "X $LABELs the spot!" diff --git a/activerecord/test/models/person.rb b/activerecord/test/models/person.rb index 1a282dbce4..c7e54e7b63 100644 --- a/activerecord/test/models/person.rb +++ b/activerecord/test/models/person.rb @@ -89,6 +89,19 @@ class RichPerson < ActiveRecord::Base self.table_name = 'people' has_and_belongs_to_many :treasures, :join_table => 'peoples_treasures' + + before_validation :run_before_create, on: :create + before_validation :run_before_validation + + private + + def run_before_create + self.first_name = first_name.to_s + 'run_before_create' + end + + def run_before_validation + self.first_name = first_name.to_s + 'run_before_validation' + end end class NestedPerson < ActiveRecord::Base diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb index e864295acf..a69d3fd3df 100644 --- a/activerecord/test/models/treasure.rb +++ b/activerecord/test/models/treasure.rb @@ -3,6 +3,7 @@ class Treasure < ActiveRecord::Base belongs_to :looter, :polymorphic => true has_many :price_estimates, :as => :estimate_of + has_and_belongs_to_many :rich_people, join_table: 'peoples_treasures', validate: false accepts_nested_attributes_for :looter end diff --git a/activerecord/test/support/ddl_helper.rb b/activerecord/test/support/ddl_helper.rb new file mode 100644 index 0000000000..0107babaaf --- /dev/null +++ b/activerecord/test/support/ddl_helper.rb @@ -0,0 +1,8 @@ +module DdlHelper + def with_example_table(connection, table_name, definition = nil) + connection.exec_query("CREATE TABLE #{table_name}(#{definition})") + yield + ensure + connection.exec_query("DROP TABLE #{table_name}") + end +end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 38a761384e..7feb820d4f 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead. + + *Akshay Vishnoi* + +* Ensure classes which `include Enumerable` get `#to_json` in addition to + `#as_json`. + + *Sammy Larbi* + * Change the signature of `fetch_multi` to return a hash rather than an array. This makes it consistent with the output of `read_multi`. diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 6afb07bd72..e9ee98a128 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -85,13 +85,13 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: value = bypass_local_cache{super} - increment_or_decrement(value, name, amount, options) + set_cache_value(value, name, amount, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: value = bypass_local_cache{super} - increment_or_decrement(value, name, amount, options) + set_cache_value(value, name, amount, options) value end @@ -119,8 +119,7 @@ module ActiveSupport super end - private - def increment_or_decrement(value, name, amount, options) + def set_cache_value(value, name, amount, options) if local_cache local_cache.mute do if value @@ -132,6 +131,8 @@ module ActiveSupport end end + private + def local_cache_key @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, '_').to_sym end diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index 083b165dce..84d5e95e7a 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -1,6 +1,4 @@ -require 'active_support/deprecation' +# cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, +# but we keep this around for libraries that directly require it knowing they +# want cattr_*. No need to deprecate. require 'active_support/core_ext/module/attribute_accessors' - -ActiveSupport::Deprecation.warn( - "The cattr_* method definitions have been moved into active_support/core_ext/module/attribute_accessors. Please require that instead." -) diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb index c2219beb5a..1c305c5970 100644 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb @@ -1,5 +1,7 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/module/deprecation' + class Class def superclass_delegating_accessor(name, options = {}) @@ -21,6 +23,8 @@ class Class end end + deprecate superclass_delegating_accessor: :class_attribute + private # Take the object being set and store it in a method. This gives us automatic # inheritance behavior, without having to store the object in an instance diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 7bea461c77..6c3e48a3ca 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -105,7 +105,7 @@ class Hash # hash = Hash.from_xml(xml) # # => {"hash"=>{"foo"=>1, "bar"=>2}} # - # DisallowedType is raised if the XML contains attributes with <tt>type="yaml"</tt> or + # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML. def from_xml(xml, disallowed_types = nil) ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h @@ -241,4 +241,3 @@ module ActiveSupport end end - diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 8e08cfbf26..5496692373 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -26,7 +26,7 @@ require 'active_support/core_ext/module/aliasing' # bypassed completely. This means that as_json won't be invoked and the JSON gem will simply # ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} # should give exactly the same results with or without active support. -[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass| +[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass| klass.class_eval do def to_json_with_active_support_encoder(options = nil) if options.is_a?(::JSON::State) diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb index 3dcae6fc7f..f58364f9c6 100644 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ b/activesupport/lib/active_support/core_ext/object/to_json.rb @@ -2,4 +2,4 @@ ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/obje 'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \ 'instead.' -require 'active_support/core_ext/object/json' +require 'active_support/core_ext/object/json'
\ No newline at end of file diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index c42354fc83..c45f6cdcfa 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -32,8 +32,7 @@ module ActiveSupport end formatted_string = - case rounded_number - when BigDecimal + if BigDecimal === rounded_number && rounded_number.finite? s = rounded_number.to_s('F') + '0'*precision a, b = s.split('.', 2) a + '.' + b[0, precision] diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index 0e0742d147..86193c2b07 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -6,14 +6,18 @@ module DelegatingFixtures end class Child < Parent - superclass_delegating_accessor :some_attribute + ActiveSupport::Deprecation.silence do + superclass_delegating_accessor :some_attribute + end end class Mokopuna < Child end class PercysMom - superclass_delegating_accessor :superpower + ActiveSupport::Deprecation.silence do + superclass_delegating_accessor :superpower + end end class Percy < PercysMom @@ -29,7 +33,10 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_simple_accessor_declaration - single_class.superclass_delegating_accessor :both + ActiveSupport::Deprecation.silence do + single_class.superclass_delegating_accessor :both + end + # Class should have accessor and mutator # the instance should have an accessor only assert_respond_to single_class, :both @@ -40,7 +47,11 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_simple_accessor_declaration_with_instance_reader_false _instance_methods = single_class.public_instance_methods - single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false + + ActiveSupport::Deprecation.silence do + single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false + end + assert_respond_to single_class, :no_instance_reader assert_respond_to single_class, :no_instance_reader= assert !_instance_methods.include?(:no_instance_reader) @@ -49,7 +60,9 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_working_with_simple_attributes - single_class.superclass_delegating_accessor :both + ActiveSupport::Deprecation.silence do + single_class.superclass_delegating_accessor :both + end single_class.both = "HMMM" @@ -65,7 +78,11 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_child_class_delegates_to_parent_but_can_be_overridden parent = Class.new - parent.superclass_delegating_accessor :both + + ActiveSupport::Deprecation.silence do + parent.superclass_delegating_accessor :both + end + child = Class.new(parent) parent.both = "1" assert_equal "1", child.both @@ -97,4 +114,9 @@ class DelegatingAttributesTest < ActiveSupport::TestCase Child.some_attribute=nil end + def test_deprecation_warning + assert_deprecated(/superclass_delegating_accessor is deprecated/) do + single_class.superclass_delegating_accessor :test_attribute + end + end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index c4283ee79a..f22d7b8b02 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -327,12 +327,39 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end - def test_enumerable_should_pass_encoding_options_to_children_in_as_json - people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + People = Class.new(BasicObject) do + include Enumerable + def initialize() + @people = [ + { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, + { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} + ] + end + def each(*, &blk) + @people.each do |p| + yield p if blk + p + end.each + end + end + + def test_enumerable_should_generate_json_with_as_json + json = People.new.as_json :only => [:address, :city] + expected = [ + { 'address' => { 'city' => 'London' }}, + { 'address' => { 'city' => 'Paris' }} ] - json = people.each.as_json :only => [:address, :city] + + assert_equal(expected, json) + end + + def test_enumerable_should_generate_json_with_to_json + json = People.new.to_json :only => [:address, :city] + assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) + end + + def test_enumerable_should_pass_encoding_options_to_children_in_as_json + json = People.new.each.as_json :only => [:address, :city] expected = [ { 'address' => { 'city' => 'London' }}, { 'address' => { 'city' => 'Paris' }} @@ -342,11 +369,7 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_enumerable_should_pass_encoding_options_to_children_in_to_json - people = [ - { :name => 'John', :address => { :city => 'London', :country => 'UK' }}, - { :name => 'Jean', :address => { :city => 'Paris' , :country => 'France' }} - ] - json = people.each.to_json :only => [:address, :city] + json = People.new.each.to_json :only => [:address, :city] assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 6d8d835de7..9bdb92024e 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -79,6 +79,9 @@ module ActiveSupport assert_equal("123.4%", number_helper.number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true)) assert_equal("1.000,000%", number_helper.number_to_percentage(1000, :delimiter => '.', :separator => ',')) assert_equal("1000.000 %", number_helper.number_to_percentage(1000, :format => "%n %")) + assert_equal("98a%", number_helper.number_to_percentage("98a")) + assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN)) + assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY)) end end diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index f368268a37..f3b93991fc 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -291,6 +291,10 @@ for detailed changes. with `config.active_record.maintain_test_schema = false`. ([Pull Request](https://github.com/rails/rails/pull/13528)) +* Introduce `Rails.gem_version` as a convenience method to return + `Gem::Version.new(Rails.version)`, suggesting a more reliable way to perform + version comparison. ([Pull Request](https://github.com/rails/rails/pull/14103)) + Action Pack ----------- @@ -346,10 +350,14 @@ for detailed changes. params "deep munging" that was used to address security vulnerability CVE-2013-0155. ([Pull Request](https://github.com/rails/rails/pull/13188)) -* New config option `config.action_dispatch.cookies_serializer` for specifying - a serializer for the signed and encrypted cookie jars. (Pull Requests [1](https://github.com/rails/rails/pull/13692), [2](https://github.com/rails/rails/pull/13945) / [More Details](upgrading_ruby_on_rails.html#cookies-serializer)) +* New config option `config.action_dispatch.cookies_serializer` for specifying a + serializer for the signed and encrypted cookie jars. (Pull Requests + [1](https://github.com/rails/rails/pull/13692), + [2](https://github.com/rails/rails/pull/13945) / + [More Details](upgrading_ruby_on_rails.html#cookies-serializer)) -* Added `render :plain`, `render :html` and `render :body`. ([Pull Request](https://github.com/rails/rails/pull/14062) / +* Added `render :plain`, `render :html` and `render + :body`. ([Pull Request](https://github.com/rails/rails/pull/14062) / [More Details](upgrading_ruby_on_rails.html#rendering-content-from-string)) @@ -705,7 +713,7 @@ for detailed changes. `available_locales` list. ([Pull Request](https://github.com/rails/rails/commit/8e21ae37ad9fef6b7393a84f9b5f2e18a831e49a)) -* Introduce Module#concerning: a natural, low-ceremony way to separate +* Introduce `Module#concerning`: a natural, low-ceremony way to separate responsibilities within a class. ([Commit](https://github.com/rails/rails/commit/1eee0ca6de975b42524105a59e0521d18b38ab81)) diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 1f9342ca25..0f46ba8698 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -619,6 +619,30 @@ It is also possible to pass a custom serializer that responds to `load` and Rails.application.config.action_dispatch.cookies_serializer = MyCustomSerializer ``` +When using the `:json` or `:hybrid` serializer, you should beware that not all +Ruby objects can be serialized as JSON. For example, `Date` and `Time` objects +will be serialized as strings, and `Hash`es will have their keys stringified. + +```ruby +class CookiesController < ApplicationController + def set_cookie + cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014 + redirect_to action: 'read_cookie' + end + + def read_cookie + cookies.encrypted[:expiration_date] # => "2014-03-20" + end +end +``` + +It's advisable that you only store simple data (strings and numbers) in cookies. +If you have to store complex objects, you would need to handle the conversion +manually when reading the values on subsequent requests. + +If you use the cookie session store, this would apply to the `session` and +`flash` hash as well. + Rendering XML and JSON data --------------------------- diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 4900f176a6..0a332d7dd9 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -961,7 +961,7 @@ SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = WARNING: This method only works with `INNER JOIN`. -Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clause for those associations when using the `joins` method. +Active Record lets you use the names of the [associations](association_basics.html) defined on the model as a shortcut for specifying `JOIN` clauses for those associations when using the `joins` method. For example, consider the following `Category`, `Post`, `Comment`, `Guest` and `Tag` models: diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 8949ef4c78..57283f7c40 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -411,7 +411,7 @@ The `doc:` namespace has the tools to generate documentation for your app, API d ### `notes` -`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.erb`, `.haml`, `.slim`, `.css`, `.scss`, `.js`, `.coffee`, `.rake`, `.sass` and `.less` for both default and custom annotations. +`rake notes` will search through your code for comments beginning with FIXME, OPTIMIZE or TODO. The search is done in files with extension `.builder`, `.rb`, `.rake`, `.yml`, `.yaml`, `.ruby`, `.css`, `.js` and `.erb` for both default and custom annotations. ```bash $ rake notes @@ -425,6 +425,12 @@ app/models/school.rb: * [ 17] [FIXME] ``` +You can add support for new file extensions using `config.annotations.register_extensions` option, which receives a list of the extensions with its corresponding regex to match it up. + +```ruby +config.annotations.register_extensions("scss", "sass", "less") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } +``` + If you are looking for a specific annotation, say FIXME, you can use `rake notes:fixme`. Note that you have to lower case the annotation's name. ```bash diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 2cece90294..460fd3c301 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -110,7 +110,7 @@ numbers. New applications filter out passwords by adding the following `config.f * `config.log_level` defines the verbosity of the Rails logger. This option defaults to `:debug` for all modes except production, where it defaults to `:info`. -* `config.log_tags` accepts a list of methods that respond to `request` object. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. +* `config.log_tags` accepts a list of methods that the `request` object responds to. This makes it easy to tag log lines with debug information like subdomain and request id - both very helpful in debugging multi-user production applications. * `config.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to an instance of `ActiveSupport::Logger`, with auto flushing off in production mode. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index bb8753cb2e..c54c9efe94 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -749,10 +749,33 @@ article. Try it! You should get an error that looks like this: Rails has several security features that help you write secure applications, and you're running into one of them now. This one is called -`strong_parameters`, which requires us to tell Rails exactly which parameters -we want to accept in our controllers. In this case, we want to allow the -`title` and `text` parameters, so add the new `article_params` method, and -change your `create` controller action to use it, like this: +`[strong_parameters](http://guides.rubyonrails.org/action_controller_overview.html#strong-parameters)`, +which requires us to tell Rails exactly which parameters are allowed into +our controller actions. + +Why do you have to bother? The ability to grab and automatically assign +all controller parameters to your model in one shot makes the programmer's +job easier, but this convenience also allows malicious use. What if a +request to the server was crafted to look like a new article form submit +but also included extra fields with values that violated your applications +integrity? They would be 'mass assigned' into your model and then into the +database along with the good stuff - potentially breaking your application +or worse. + +We have to whitelist our controller parameters to prevent wrongful +mass assignment. In this case, we want to both allow and require the +`title` and `text` parameters for valid use of `create`. The syntax for +this introduces `require` and `permit`. The change will involve one line: + +```ruby + @article = Article.new(params.require(:article).permit(:title, :text)) +``` + +This is often factored out into its own method so it can be reused by +multiple actions in the same controller, for example `create` and `update`. +Above and beyond mass assignment issues, the method is often made +`private` to make sure it can't be called outside its intended context. +Here is the result: ```ruby def create @@ -768,13 +791,7 @@ private end ``` -See the `permit`? It allows us to accept both `title` and `text` in this -action. - -TIP: Note that `def article_params` is private. This new approach prevents an -attacker from setting the model's attributes by manipulating the hash passed to -the model. -For more information, refer to +TIP: For more information, refer to the reference above and [this blog article about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). ### Showing Articles diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 7467648d49..d58024df3d 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -111,6 +111,30 @@ in your application, you can add an initializer file with the following content: This would transparently migrate your existing `Marshal`-serialized cookies into the new `JSON`-based format. +When using the `:json` or `:hybrid` serializer, you should beware that not all +Ruby objects can be serialized as JSON. For example, `Date` and `Time` objects +will be serialized as strings, and `Hash`es will have their keys stringified. + +```ruby +class CookiesController < ApplicationController + def set_cookie + cookies.encrypted[:expiration_date] = Date.tomorrow # => Thu, 20 Mar 2014 + redirect_to action: 'read_cookie' + end + + def read_cookie + cookies.encrypted[:expiration_date] # => "2014-03-20" + end +end +``` + +It's advisable that you only store simple data (strings and numbers) in cookies. +If you have to store complex objects, you would need to handle the conversion +manually when reading the values on subsequent requests. + +If you use the cookie session store, this would apply to the `session` and +`flash` hash as well. + ### Flash structure changes Flash message keys are diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 35b3a379ae..6cf8714177 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,15 @@ +* Remove sqlite3 lines from `.gitignore` if the application is not using sqlite3. + + *Dmitrii Golub* + +* Add public API to register new extensions for `rake notes`. + + Example: + + config.annotations.register_extensions("scss", "sass") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ } + + *Roberto Miranda* + * Removed unnecessary `rails application` command. *Arun Agrawal* diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 20e3de32aa..9aec2f9734 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/file_update_checker' require 'rails/engine/configuration' +require 'rails/source_annotation_extractor' module Rails class Application @@ -149,6 +150,9 @@ module Rails end end + def annotations + SourceAnnotationExtractor::Annotation + end end end end diff --git a/railties/lib/rails/commands/commands_tasks.rb b/railties/lib/rails/commands/commands_tasks.rb index f061c2bf74..6cfbc70c51 100644 --- a/railties/lib/rails/commands/commands_tasks.rb +++ b/railties/lib/rails/commands/commands_tasks.rb @@ -27,7 +27,7 @@ In addition to those, there are: All commands can be run with -h (or --help) for more information. EOT - COMMAND_WHITELIST = %(plugin generate destroy console server dbconsole runner new version help) + COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole runner new version help) def initialize(argv) @argv = argv diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index fbdc47ea44..dd1438800c 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -172,6 +172,10 @@ module Rails options[value] ? '# ' : '' end + def sqlite3? + !options[:skip_active_record] && options[:database] == 'sqlite3' + end + class GemfileEntry < Struct.new(:name, :version, :comment, :options, :commented_out) def initialize(name, version, comment, options = {}, commented_out = false) super diff --git a/railties/lib/rails/generators/generated_attribute.rb b/railties/lib/rails/generators/generated_attribute.rb index 5e2784c4b0..c5326d70d1 100644 --- a/railties/lib/rails/generators/generated_attribute.rb +++ b/railties/lib/rails/generators/generated_attribute.rb @@ -94,6 +94,10 @@ module Rails name.sub(/_id$/, '').pluralize end + def singular_name + name.sub(/_id$/, '').singularize + end + def human_name name.humanize end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 83cb1dc0d5..abf6909a7f 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -50,7 +50,7 @@ module Rails end def gitignore - copy_file "gitignore", ".gitignore" + template "gitignore", ".gitignore" end def app diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index 6a502e997f..8775e5e235 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -7,10 +7,12 @@ # Ignore bundler config. /.bundle +<% if sqlite3? -%> # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal +<% end -%> # Ignore all logfiles and tempfiles. /log/*.log /tmp diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index edadeaca0e..9502876ebb 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -23,7 +23,7 @@ module Rails end def frameworks - %w( active_record action_pack action_view action_mailer active_support ) + %w( active_record action_pack action_view action_mailer active_support active_model ) end def framework_version(framework) diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 83e28090f8..201532d299 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -18,6 +18,20 @@ class SourceAnnotationExtractor @@directories ||= %w(app config db lib test) + (ENV['SOURCE_ANNOTATION_DIRECTORIES'] || '').split(',') end + def self.extensions + @@extensions ||= {} + end + + # Registers new Annotations File Extensions + # SourceAnnotationExtractor::Annotation.register_extensions("css", "scss", "sass", "less", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ } + def self.register_extensions(*exts, &block) + extensions[/\.(#{exts.join("|")})$/] = block + end + + register_extensions("builder", "rb", "rake", "yml", "yaml", "ruby") { |tag| /#\s*(#{tag}):?\s*(.*)$/ } + register_extensions("css", "js") { |tag| /\/\/\s*(#{tag}):?\s*(.*)$/ } + register_extensions("erb") { |tag| /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ } + # Returns a representation of the annotation that looks like this: # # [126] [TODO] This algorithm is simple and clearly correct, make it faster. @@ -78,21 +92,14 @@ class SourceAnnotationExtractor if File.directory?(item) results.update(find_in(item)) else - pattern = - case item - when /\.(builder|rb|coffee|rake)$/ - /#\s*(#{tag}):?\s*(.*)$/ - when /\.(css|scss|sass|less|js)$/ - /\/\/\s*(#{tag}):?\s*(.*)$/ - when /\.erb$/ - /<%\s*#\s*(#{tag}):?\s*(.*?)\s*%>/ - when /\.haml$/ - /-\s*#\s*(#{tag}):?\s*(.*)$/ - when /\.slim$/ - /\/\s*\s*(#{tag}):?\s*(.*)$/ - else nil - end - results.update(extract_annotations_from(item, pattern)) if pattern + extension = Annotation.extensions.detect do |regexp, _block| + regexp.match(item) + end + + if extension + pattern = extension.last.call(tag) + results.update(extract_annotations_from(item, pattern)) if pattern + end end end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index b39cd3747b..b11fd55170 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -793,5 +793,15 @@ module ApplicationTests assert ActiveRecord::Base.dump_schema_after_migration end + + test "config.annotations wrapping SourceAnnotationExtractor::Annotation class" do + make_basic_app do |app| + app.config.annotations.register_extensions("coffee") do |tag| + /#\s*(#{tag}):?\s*(.*)$/ + end + end + + assert_not_nil SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/] + end end end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 35d9c31c1e..b2c52a092f 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -26,57 +26,48 @@ module ApplicationTests FileUtils.rm_rf("#{app_path}/config/database.yml") end - def expected - @expected ||= {} - end - - def db_create_and_drop + def db_create_and_drop(expected_database) Dir.chdir(app_path) do output = `bundle exec rake db:create` - assert_equal output, "" - assert File.exist?(expected[:database]) - assert_equal expected[:database], - ActiveRecord::Base.connection_config[:database] + assert_empty output + assert File.exist?(expected_database) + assert_equal expected_database, ActiveRecord::Base.connection_config[:database] output = `bundle exec rake db:drop` - assert_equal output, "" - assert !File.exist?(expected[:database]) + assert_empty output + assert !File.exist?(expected_database) end end test 'db:create and db:drop without database url' do require "#{app_path}/config/environment" - expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database'] - db_create_and_drop - end + db_create_and_drop ActiveRecord::Base.configurations[Rails.env]['database'] + end test 'db:create and db:drop with database url' do require "#{app_path}/config/environment" set_database_url - expected[:database] = database_url_db_name - db_create_and_drop + db_create_and_drop database_url_db_name end - def db_migrate_and_status + def db_migrate_and_status(expected_database) Dir.chdir(app_path) do `rails generate model book title:string; bundle exec rake db:migrate` output = `bundle exec rake db:migrate:status` - assert_match(%r{database:\s+\S*#{Regexp.escape(expected[:database])}}, output) + assert_match(%r{database:\s+\S*#{Regexp.escape(expected_database)}}, output) assert_match(/up\s+\d{14}\s+Create books/, output) end end test 'db:migrate and db:migrate:status without database_url' do require "#{app_path}/config/environment" - expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database'] - db_migrate_and_status + db_migrate_and_status ActiveRecord::Base.configurations[Rails.env]['database'] end test 'db:migrate and db:migrate:status with database_url' do require "#{app_path}/config/environment" set_database_url - expected[:database] = database_url_db_name - db_migrate_and_status + db_migrate_and_status database_url_db_name end def db_schema_dump @@ -97,12 +88,11 @@ module ApplicationTests db_schema_dump end - def db_fixtures_load + def db_fixtures_load(expected_database) Dir.chdir(app_path) do `rails generate model book title:string; bundle exec rake db:migrate db:fixtures:load` - assert_match(/#{expected[:database]}/, - ActiveRecord::Base.connection_config[:database]) + assert_match expected_database, ActiveRecord::Base.connection_config[:database] require "#{app_path}/app/models/book" assert_equal 2, Book.count end @@ -110,43 +100,50 @@ module ApplicationTests test 'db:fixtures:load without database_url' do require "#{app_path}/config/environment" - expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database'] - db_fixtures_load + db_fixtures_load ActiveRecord::Base.configurations[Rails.env]['database'] end test 'db:fixtures:load with database_url' do require "#{app_path}/config/environment" set_database_url - expected[:database] = database_url_db_name - db_fixtures_load + db_fixtures_load database_url_db_name end - def db_structure_dump_and_load + def db_structure_dump_and_load(expected_database) Dir.chdir(app_path) do `rails generate model book title:string; bundle exec rake db:migrate db:structure:dump` structure_dump = File.read("db/structure.sql") assert_match(/CREATE TABLE \"books\"/, structure_dump) `bundle exec rake environment db:drop db:structure:load` - assert_match(/#{expected[:database]}/, - ActiveRecord::Base.connection_config[:database]) + assert_match expected_database, ActiveRecord::Base.connection_config[:database] require "#{app_path}/app/models/book" #if structure is not loaded correctly, exception would be raised - assert Book.count, 0 + assert_equal 0, Book.count end end test 'db:structure:dump and db:structure:load without database_url' do require "#{app_path}/config/environment" - expected[:database] = ActiveRecord::Base.configurations[Rails.env]['database'] - db_structure_dump_and_load + db_structure_dump_and_load ActiveRecord::Base.configurations[Rails.env]['database'] end test 'db:structure:dump and db:structure:load with database_url' do require "#{app_path}/config/environment" set_database_url - expected[:database] = database_url_db_name - db_structure_dump_and_load + db_structure_dump_and_load database_url_db_name + end + + test 'db:structure:dump does not dump schema information when no migrations are used' do + Dir.chdir(app_path) do + # create table without migrations + `bundle exec rails runner 'ActiveRecord::Base.connection.create_table(:posts) {|t| t.string :title }'` + + stderr_output = capture(:stderr) { `bundle exec rake db:structure:dump` } + assert_empty stderr_output + structure_dump = File.read("db/structure.sql") + assert_match(/CREATE TABLE \"posts\"/, structure_dump) + end end def db_test_load_structure @@ -157,9 +154,9 @@ module ApplicationTests ActiveRecord::Base.establish_connection :test require "#{app_path}/app/models/book" #if structure is not loaded correctly, exception would be raised - assert Book.count, 0 - assert_match(/#{ActiveRecord::Base.configurations['test']['database']}/, - ActiveRecord::Base.connection_config[:database]) + assert_equal 0, Book.count + assert_match ActiveRecord::Base.configurations['test']['database'], + ActiveRecord::Base.connection_config[:database] end end diff --git a/railties/test/application/rake/notes_test.rb b/railties/test/application/rake/notes_test.rb index 05f6338b68..95087bf29f 100644 --- a/railties/test/application/rake/notes_test.rb +++ b/railties/test/application/rake/notes_test.rb @@ -1,4 +1,5 @@ require "isolation/abstract_unit" +require 'rails/source_annotation_extractor' module ApplicationTests module RakeTests @@ -18,48 +19,27 @@ module ApplicationTests test 'notes finds notes for certain file_types' do app_file "app/views/home/index.html.erb", "<% # TODO: note in erb %>" - app_file "app/views/home/index.html.haml", "-# TODO: note in haml" - app_file "app/views/home/index.html.slim", "/ TODO: note in slim" - app_file "app/assets/javascripts/application.js.coffee", "# TODO: note in coffee" app_file "app/assets/javascripts/application.js", "// TODO: note in js" app_file "app/assets/stylesheets/application.css", "// TODO: note in css" - app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" - app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" - app_file "app/assets/stylesheets/application.css.less", "// TODO: note in less" app_file "app/controllers/application_controller.rb", 1000.times.map { "" }.join("\n") << "# TODO: note in ruby" app_file "lib/tasks/task.rake", "# TODO: note in rake" app_file 'app/views/home/index.html.builder', '# TODO: note in builder' + app_file 'config/locales/en.yml', '# TODO: note in yml' + app_file 'config/locales/en.yaml', '# TODO: note in yaml' + app_file "app/views/home/index.ruby", "# TODO: note in ruby" - boot_rails - require 'rake' - require 'rdoc/task' - require 'rake/testtask' - - Rails.application.load_tasks - - Dir.chdir(app_path) do - output = `bundle exec rake notes` - lines = output.scan(/\[([0-9\s]+)\](\s)/) - + run_rake_notes do |output, lines| assert_match(/note in erb/, output) - assert_match(/note in haml/, output) - assert_match(/note in slim/, output) - assert_match(/note in ruby/, output) - assert_match(/note in coffee/, output) assert_match(/note in js/, output) assert_match(/note in css/, output) - assert_match(/note in scss/, output) - assert_match(/note in sass/, output) - assert_match(/note in less/, output) assert_match(/note in rake/, output) assert_match(/note in builder/, output) + assert_match(/note in yml/, output) + assert_match(/note in yaml/, output) + assert_match(/note in ruby/, output) - assert_equal 12, lines.size - - lines.each do |line| - assert_equal 4, line[0].size - assert_equal ' ', line[1] - end + assert_equal 9, lines.size + assert_equal [4], lines.map(&:size).uniq end end @@ -72,18 +52,7 @@ module ApplicationTests app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" - boot_rails - - require 'rake' - require 'rdoc/task' - require 'rake/testtask' - - Rails.application.load_tasks - - Dir.chdir(app_path) do - output = `bundle exec rake notes` - lines = output.scan(/\[([0-9\s]+)\]/).flatten - + run_rake_notes do |output, lines| assert_match(/note in app directory/, output) assert_match(/note in config directory/, output) assert_match(/note in db directory/, output) @@ -92,10 +61,7 @@ module ApplicationTests assert_no_match(/note in some_other directory/, output) assert_equal 5, lines.size - - lines.each do |line_number| - assert_equal 4, line_number.size - end + assert_equal [4], lines.map(&:size).uniq end end @@ -108,18 +74,7 @@ module ApplicationTests app_file "some_other_dir/blah.rb", "# TODO: note in some_other directory" - boot_rails - - require 'rake' - require 'rdoc/task' - require 'rake/testtask' - - Rails.application.load_tasks - - Dir.chdir(app_path) do - output = `SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes` - lines = output.scan(/\[([0-9\s]+)\]/).flatten - + run_rake_notes "SOURCE_ANNOTATION_DIRECTORIES='some_other_dir' bundle exec rake notes" do |output, lines| assert_match(/note in app directory/, output) assert_match(/note in config directory/, output) assert_match(/note in db directory/, output) @@ -129,10 +84,7 @@ module ApplicationTests assert_match(/note in some_other directory/, output) assert_equal 6, lines.size - - lines.each do |line_number| - assert_equal 4, line_number.size - end + assert_equal [4], lines.map(&:size).uniq end end @@ -150,32 +102,51 @@ module ApplicationTests end EOS - boot_rails - - require 'rake' - require 'rdoc/task' - require 'rake/testtask' - - Rails.application.load_tasks - - Dir.chdir(app_path) do - output = `bundle exec rake notes_custom` - lines = output.scan(/\[([0-9\s]+)\]/).flatten - + run_rake_notes "bundle exec rake notes_custom" do |output, lines| assert_match(/\[FIXME\] note in lib directory/, output) assert_match(/\[TODO\] note in test directory/, output) assert_no_match(/OPTIMIZE/, output) assert_no_match(/note in app directory/, output) assert_equal 2, lines.size + assert_equal [4], lines.map(&:size).uniq + end + end - lines.each do |line_number| - assert_equal 4, line_number.size - end + test 'register a new extension' do + add_to_config %q{ config.annotations.register_extensions("scss", "sass") { |annotation| /\/\/\s*(#{annotation}):?\s*(.*)$/ } } + app_file "app/assets/stylesheets/application.css.scss", "// TODO: note in scss" + app_file "app/assets/stylesheets/application.css.sass", "// TODO: note in sass" + + run_rake_notes do |output, lines| + assert_match(/note in scss/, output) + assert_match(/note in sass/, output) + assert_equal 2, lines.size end end private + + def run_rake_notes(command = 'bundle exec rake notes') + boot_rails + load_tasks + + Dir.chdir(app_path) do + output = `#{command}` + lines = output.scan(/\[([0-9\s]+)\]\s/).flatten + + yield output, lines + end + end + + def load_tasks + require 'rake' + require 'rdoc/task' + require 'rake/testtask' + + Rails.application.load_tasks + end + def boot_rails super require "#{app_path}/config/environment" diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5ebdadacbf..8e1aeddb2b 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -408,7 +408,31 @@ class AppGeneratorTest < Rails::Generators::TestCase end end -protected + def test_gitignore_when_sqlite3 + run_generator + + assert_file '.gitignore' do |content| + assert_match(/sqlite3/, content) + end + end + + def test_gitignore_when_no_active_record + run_generator [destination_root, '--skip-active-record'] + + assert_file '.gitignore' do |content| + assert_no_match(/sqlite/i, content) + end + end + + def test_gitignore_when_non_sqlite3_db + run_generator([destination_root, "-d", "mysql"]) + + assert_file '.gitignore' do |content| + assert_no_match(/sqlite/i, content) + end + end + + protected def action(*args, &block) silence(:stdout) { generator.send(*args, &block) } diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index d876597944..6fac643ed0 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -197,4 +197,54 @@ class MigrationGeneratorTest < Rails::Generators::TestCase def test_properly_identifies_usage_file assert generator_class.send(:usage_path) end + + def test_migration_with_singular_table_name + with_singular_table_name do + migration = "add_title_body_to_post" + run_generator [migration, 'title:string'] + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/add_column :post, :title, :string/, change) + end + end + end + end + + def test_create_join_table_migration_with_singular_table_name + with_singular_table_name do + migration = "add_media_join_table" + run_generator [migration, "artist_id", "music:uniq"] + + assert_migration "db/migrate/#{migration}.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_join_table :artist, :music/, change) + assert_match(/# t.index \[:artist_id, :music_id\]/, change) + assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change) + end + end + end + end + + def test_create_table_migration_with_singular_table_name + with_singular_table_name do + run_generator ["create_book", "title:string", "content:text"] + assert_migration "db/migrate/create_book.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :book/, change) + assert_match(/ t\.string :title/, change) + assert_match(/ t\.text :content/, change) + end + end + end + end + + private + + def with_singular_table_name + old_state = ActiveRecord::Base.pluralize_table_names + ActiveRecord::Base.pluralize_table_names = false + yield + ensure + ActiveRecord::Base.pluralize_table_names = old_state + end end diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index b2fc3a2a4f..7ebc015fe2 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -185,22 +185,22 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator FileUtils.cd destination_root quietly { system 'bundle install' } - assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) + assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`) end def test_ensure_that_tests_works_in_full_mode run_generator [destination_root, "--full", "--skip_active_record"] FileUtils.cd destination_root quietly { system 'bundle install' } - assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test`) + assert_match(/1 runs, 1 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`) end def test_ensure_that_migration_tasks_work_with_mountable_option run_generator [destination_root, "--mountable"] FileUtils.cd destination_root quietly { system 'bundle install' } - `bundle exec rake db:migrate` - assert_equal 0, $?.exitstatus + output = `bundle exec rake db:migrate 2>&1` + assert $?.success?, "Command failed: #{output}" end def test_creating_engine_in_full_mode |