diff options
85 files changed, 948 insertions, 532 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 0f5a48e524..f8d6d5d24b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -137,7 +137,7 @@ PATH activesupport (= 5.0.0.alpha) arel (= 7.0.0.alpha) activesupport (5.0.0.alpha) - concurrent-ruby (~> 1.0.0.pre5, < 2.0.0) + concurrent-ruby (~> 1.0) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) method_source @@ -176,7 +176,7 @@ GEM builder (3.2.2) bunny (2.2.0) amq-protocol (>= 2.0.0) - byebug (6.0.2) + byebug (8.2.0) celluloid (0.17.2) celluloid-essentials celluloid-extras @@ -201,7 +201,7 @@ GEM coffee-script-source execjs coffee-script-source (1.9.1.1) - concurrent-ruby (1.0.0.pre5) + concurrent-ruby (1.0.0) connection_pool (2.2.0) dalli (2.7.4) dante (0.2.0) diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index cbd7cec70f..0b12860619 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -464,30 +464,26 @@ module ActionMailer # Either a class, string or symbol can be passed in as the Observer. # If a string or symbol is passed in it will be camelized and constantized. def register_observer(observer) - delivery_observer = case observer - when String, Symbol - observer.to_s.camelize.constantize - else - observer - end - - Mail.register_observer(delivery_observer) + Mail.register_observer(observer_class_for(observer)) end # Register an Interceptor which will be called before mail is sent. # Either a class, string or symbol can be passed in as the Interceptor. # If a string or symbol is passed in it will be camelized and constantized. def register_interceptor(interceptor) - delivery_interceptor = case interceptor - when String, Symbol - interceptor.to_s.camelize.constantize - else - interceptor - end - - Mail.register_interceptor(delivery_interceptor) + Mail.register_interceptor(observer_class_for(interceptor)) end + def observer_class_for(value) # :nodoc: + case value + when String, Symbol + value.to_s.camelize.constantize + else + value + end + end + private :observer_class_for + # Returns the name of current mailer. This method is also being used as a path for a view lookup. # If this is an anonymous mailer, this method will return +anonymous+ instead. def mailer_name @@ -796,52 +792,40 @@ module ActionMailer # end # def mail(headers = {}, &block) - return @_message if @_mail_was_called && headers.blank? && !block - - m = @_message + return message if @_mail_was_called && headers.blank? && !block # At the beginning, do not consider class default for content_type content_type = headers[:content_type] - # Call all the procs (if any) - default_values = {} - self.class.default.each do |k,v| - default_values[k] = v.is_a?(Proc) ? instance_eval(&v) : v - end - - # Handle defaults - headers = headers.reverse_merge(default_values) - headers[:subject] ||= default_i18n_subject + headers = apply_defaults(headers) # Apply charset at the beginning so all fields are properly quoted - m.charset = charset = headers[:charset] + message.charset = charset = headers[:charset] # Set configure delivery behavior - wrap_delivery_behavior!(headers.delete(:delivery_method), headers.delete(:delivery_method_options)) + wrap_delivery_behavior!(headers[:delivery_method], headers[:delivery_method_options]) - # Assign all headers except parts_order, content_type, body, template_name, and template_path - assignable = headers.except(:parts_order, :content_type, :body, :template_name, :template_path) - assignable.each { |k, v| m[k] = v } + assign_headers_to_message(message, headers) # Render the templates and blocks responses = collect_responses(headers, &block) @_mail_was_called = true - create_parts_from_responses(m, responses) + create_parts_from_responses(message, responses) # Setup content type, reapply charset and handle parts order - m.content_type = set_content_type(m, content_type, headers[:content_type]) - m.charset = charset + message.content_type = set_content_type(message, content_type, headers[:content_type]) + message.charset = charset - if m.multipart? - m.body.set_sort_order(headers[:parts_order]) - m.body.sort_parts! + if message.multipart? + message.body.set_sort_order(headers[:parts_order]) + message.body.sort_parts! end - m + message end - protected + protected # Used by #mail to set the content type of the message. # @@ -879,36 +863,61 @@ module ActionMailer I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) end - def collect_responses(headers) #:nodoc: - responses = [] + # Emails do not support relative path links. + def self.supports_path? + false + end + private + + def apply_defaults(headers) + default_values = self.class.default.map do |key, value| + [ + key, + value.is_a?(Proc) ? instance_eval(&value) : value + ] + end.to_h + + headers_with_defaults = headers.reverse_merge(default_values) + headers_with_defaults[:subject] ||= default_i18n_subject + headers_with_defaults + end + + def assign_headers_to_message(message, headers) + assignable = headers.except(:parts_order, :content_type, :body, :template_name, + :template_path, :delivery_method, :delivery_method_options) + assignable.each { |k, v| message[k] = v } + end + + def collect_responses(headers) if block_given? collector = ActionMailer::Collector.new(lookup_context) { render(action_name) } yield(collector) - responses = collector.responses + collector.responses elsif headers[:body] - responses << { + [{ body: headers.delete(:body), content_type: self.class.default[:content_type] || "text/plain" - } + }] else - templates_path = headers.delete(:template_path) || self.class.mailer_name - templates_name = headers.delete(:template_name) || action_name + collect_responses_from_templates(headers) + end + end - each_template(Array(templates_path), templates_name) do |template| - self.formats = template.formats + def collect_responses_from_templates(headers) + templates_path = headers[:template_path] || self.class.mailer_name + templates_name = headers[:template_name] || action_name - responses << { - body: render(template: template), - content_type: template.type.to_s - } - end + each_template(Array(templates_path), templates_name).map do |template| + self.formats = template.formats + { + body: render(template: template), + content_type: template.type.to_s + } end - - responses end - def each_template(paths, name, &block) #:nodoc: + def each_template(paths, name, &block) templates = lookup_context.find_all(name, paths) if templates.empty? raise ActionView::MissingTemplate.new(paths, name, paths, false, 'mailer') @@ -917,7 +926,7 @@ module ActionMailer end end - def create_parts_from_responses(m, responses) #:nodoc: + def create_parts_from_responses(m, responses) if responses.size == 1 && !m.has_attachments? responses[0].each { |k,v| m[k] = v } elsif responses.size > 1 && m.has_attachments? @@ -930,17 +939,12 @@ module ActionMailer end end - def insert_part(container, response, charset) #:nodoc: + def insert_part(container, response, charset) response[:charset] ||= charset part = Mail::Part.new(response) container.add_part(part) end - # Emails do not support relative path links. - def self.supports_path? - false - end - ActiveSupport.run_load_hooks(:action_mailer, self) end end diff --git a/actionmailer/lib/action_mailer/log_subscriber.rb b/actionmailer/lib/action_mailer/log_subscriber.rb index 7e9d916b66..2867bf90fb 100644 --- a/actionmailer/lib/action_mailer/log_subscriber.rb +++ b/actionmailer/lib/action_mailer/log_subscriber.rb @@ -8,7 +8,7 @@ module ActionMailer def deliver(event) info do recipients = Array(event.payload[:to]).join(', ') - "\nSent mail to #{recipients} (#{event.duration.round(1)}ms)" + "Sent mail to #{recipients} (#{event.duration.round(1)}ms)" end debug { event.payload[:mail] } @@ -16,7 +16,7 @@ module ActionMailer # An email was received. def receive(event) - info { "\nReceived mail (#{event.duration.round(1)}ms)" } + info { "Received mail (#{event.duration.round(1)}ms)" } debug { event.payload[:mail] } end @@ -25,7 +25,7 @@ module ActionMailer debug do mailer = event.payload[:mailer] action = event.payload[:action] - "\n#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms" + "#{mailer}##{action}: processed outbound mail in #{event.duration.round(1)}ms" end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index ea61ad0c02..bd0f38953a 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -306,10 +306,16 @@ module ActionDispatch end end - # Returns true if the request's content MIME type is - # +application/x-www-form-urlencoded+ or +multipart/form-data+. + # Determine whether the request body contains form-data by checking + # the request Content-Type for one of the media-types: + # "application/x-www-form-urlencoded" or "multipart/form-data". The + # list of form-data media types can be modified through the + # +FORM_DATA_MEDIA_TYPES+ array. + # + # A request body is not assumed to contain form-data when no + # Content-Type header is provided and the request_method is POST. def form_data? - FORM_DATA_MEDIA_TYPES.include?(content_mime_type.to_s) + FORM_DATA_MEDIA_TYPES.include?(media_type) end def body_stream #:nodoc: diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 2889acaeb8..65baf117ba 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -77,6 +77,12 @@ module ActionDispatch # # It can be read using the signed method `cookies.signed[:name]` # cookies.signed[:user_id] = current_user.id # + # # Sets an encrypted cookie value before sending it to the client which + # # prevent users from reading and tampering with its value. + # # The cookie is signed by your app's `secrets.secret_key_base` value. + # # It can be read using the encrypted method `cookies.encrypted[:name]` + # cookies.encrypted[:discount] = 45 + # # # Sets a "permanent" cookie (which expires in 20 years from now). # cookies.permanent[:login] = "XJ-122" # @@ -89,6 +95,7 @@ module ActionDispatch # cookies.size # => 2 # JSON.parse(cookies[:lat_lon]) # => [47.68, -122.37] # cookies.signed[:login] # => "XJ-122" + # cookies.encrypted[:discount] # => 45 # # Example for deleting: # diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index aee2334da9..31b75498b6 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -43,7 +43,7 @@ module ActionDispatch # Create a new +RemoteIp+ middleware instance. # - # The +check_ip_spoofing+ option is on by default. When on, an exception + # The +ip_spoofing_check+ option is on by default. When on, an exception # is raised if it looks like the client is trying to lie about its own IP # address. It makes sense to turn off this check on sites aimed at non-IP # clients (like WAP devices), or behind proxies that set headers in an @@ -57,9 +57,9 @@ module ActionDispatch # with your proxy servers after it. If your proxies aren't removed, pass # them in via the +custom_proxies+ parameter. That way, the middleware will # ignore those IP addresses, and return the one that you want. - def initialize(app, check_ip_spoofing = true, custom_proxies = nil) + def initialize(app, ip_spoofing_check = true, custom_proxies = nil) @app = app - @check_ip = check_ip_spoofing + @check_ip = ip_spoofing_check @proxies = if custom_proxies.blank? TRUSTED_PROXIES elsif custom_proxies.respond_to?(:any?) @@ -116,10 +116,18 @@ module ActionDispatch forwarded_ips = ips_from(@req.x_forwarded_for).reverse # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. - # If they are both set, it means that this request passed through two - # proxies with incompatible IP header conventions, and there is no way - # for us to determine which header is the right one after the fact. - # Since we have no idea, we give up and explode. + # If they are both set, it means that either: + # + # 1) This request passed through two proxies with incompatible IP header + # conventions. + # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+ + # (whichever the proxy servers weren't using) themselves. + # + # Either way, there is no way for us to determine which header is the + # right one after the fact. Since we have no idea, if we are concerned + # about IP spoofing we need to give up and explode. (If you're not + # concerned about IP spoofing you can turn the +ip_spoofing_check+ + # option off.) should_check_ip = @check_ip && client_ips.last && forwarded_ips.last if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb index e7b913bbe4..e7b913bbe4 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.html.erb diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.text.erb new file mode 100644 index 0000000000..23a9c7ba3f --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.text.erb @@ -0,0 +1,8 @@ +<% @source_extracts.first(3).each do |source_extract| %> +<% if source_extract[:code] %> +Extracted source (around line #<%= source_extract[:line_number] %>): + +<% source_extract[:code].each do |line, source| -%> +<%= line == source_extract[:line_number] ? "*#{line}" : "##{line}" -%> <%= source -%><% end -%> +<% end %> +<% end %> diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 5f54ea130b..c4228df925 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -371,10 +371,6 @@ module ActionDispatch end def eval_block(block) - if block.arity == 1 - raise "You are using the old router DSL which has been removed in Rails 3.1. " << - "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" - end mapper = Mapper.new(self) if default_scope mapper.with_default_scope(default_scope, &block) diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 4a2b02a003..a39fede5b9 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -289,12 +289,6 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal({:id=>"1", :filters=>"foo", :format=>"js"}, params) end - def test_draw_with_block_arity_one_raises - assert_raise(RuntimeError) do - rs.draw { |map| map.match '/:controller(/:action(/:id))' } - end - end - def test_specific_controller_action_failure rs.draw do mount lambda {} => "/foo" diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index 8960156d00..30772bd9ed 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -166,6 +166,14 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert_equal "text/plain", response.content_type assert_match(/RuntimeError\npuke/, body) + Rails.stub :root, Pathname.new('.') do + get "/", headers: xhr_request_env + + assert_response 500 + assert_match 'Extracted source (around line #', body + assert_select 'pre', { count: 0 }, body + end + get "/not_found", headers: xhr_request_env assert_response 404 assert_no_match(/<body>/, body) diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 22240699d9..08c4554721 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -1212,3 +1212,23 @@ class RequestVariant < BaseRequestTest end end end + +class RequestFormData < BaseRequestTest + test 'media_type is from the FORM_DATA_MEDIA_TYPES array' do + assert stub_request('CONTENT_TYPE' => 'application/x-www-form-urlencoded').form_data? + assert stub_request('CONTENT_TYPE' => 'multipart/form-data').form_data? + end + + test 'media_type is not from the FORM_DATA_MEDIA_TYPES array' do + assert !stub_request('CONTENT_TYPE' => 'application/xml').form_data? + assert !stub_request('CONTENT_TYPE' => 'multipart/related').form_data? + end + + test 'no Content-Type header is provided and the request_method is POST' do + request = stub_request('REQUEST_METHOD' => 'POST') + + assert_equal '', request.media_type + assert_equal 'POST', request.request_method + assert !request.form_data? + end +end diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 2ffed6b395..91e934cd64 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -205,6 +205,8 @@ module ActionView # # => <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> # image_tag("/icons/icon.gif", class: "menu_icon") # # => <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> + # image_tag("/icons/icon.gif", data: { title: 'Rails Application' }) + # # => <img data-title="Rails Application" src="/icons/icon.gif" /> def image_tag(source, options={}) options = options.symbolize_keys check_for_image_tag_errors(options) diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index dfb7d463b4..63a60542d4 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -46,9 +46,8 @@ module ActionView #:nodoc: class NullResolver < PathResolver def query(path, exts, formats) 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)] + [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, :virtual_path => path.virtual, :format => format, :variant => variant)] end end - end diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 2354e91822..79173f730f 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -280,7 +280,6 @@ def jruby_skip(message = '') skip message if defined?(JRUBY_VERSION) end -require 'mocha/setup' # FIXME: stop using mocha class ActiveSupport::TestCase include ActiveSupport::Testing::MethodCallAssertions end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index a63ac442cd..fe40010528 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -193,7 +193,8 @@ class AssetTagHelperTest < ActionView::TestCase %(image_tag("//www.rubyonrails.com/images/rails.png")) => %(<img alt="Rails" src="//www.rubyonrails.com/images/rails.png" />), %(image_tag("mouse.png", :alt => nil)) => %(<img src="/images/mouse.png" />), %(image_tag("data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", :alt => nil)) => %(<img src="data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" />), - %(image_tag("")) => %(<img src="" />) + %(image_tag("")) => %(<img src="" />), + %(image_tag("gold.png", data: { title: 'Rails Application' })) => %(<img data-title="Rails Application" src="/images/gold.png" alt="Gold" />) } FaviconLinkToTag = { diff --git a/actionview/test/template/date_helper_i18n_test.rb b/actionview/test/template/date_helper_i18n_test.rb index 21fca35185..52aef56a61 100644 --- a/actionview/test/template/date_helper_i18n_test.rb +++ b/actionview/test/template/date_helper_i18n_test.rb @@ -46,8 +46,9 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase end def test_time_ago_in_words_passes_locale - I18n.expects(:t).with(:less_than_x_minutes, :scope => :'datetime.distance_in_words', :count => 1, :locale => 'ru') - time_ago_in_words(15.seconds.ago, :locale => 'ru') + assert_called_with(I18n, :t, [:less_than_x_minutes, :scope => :'datetime.distance_in_words', :count => 1, :locale => 'ru']) do + time_ago_in_words(15.seconds.ago, :locale => 'ru') + end end def test_distance_of_time_pluralizations @@ -80,8 +81,9 @@ class DateHelperDistanceOfTimeInWordsI18nTests < ActiveSupport::TestCase options = { locale: 'en', scope: :'datetime.distance_in_words' }.merge!(expected_options) options[:count] = count if count - I18n.expects(:t).with(key, options) - distance_of_time_in_words(@from, to, passed_options.merge(locale: 'en')) + assert_called_with(I18n, :t, [key, options]) do + distance_of_time_in_words(@from, to, passed_options.merge(locale: 'en')) + end end end @@ -89,60 +91,74 @@ class DateHelperSelectTagsI18nTests < ActiveSupport::TestCase include ActionView::Helpers::DateHelper attr_reader :request - def setup - @prompt_defaults = {:year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds'} - - I18n.stubs(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES - end - # select_month def test_select_month_given_use_month_names_option_does_not_translate_monthnames - I18n.expects(:translate).never - select_month(8, :locale => 'en', :use_month_names => Date::MONTHNAMES) + assert_not_called(I18n, :translate) do + select_month(8, :locale => 'en', :use_month_names => Date::MONTHNAMES) + end end def test_select_month_translates_monthnames - I18n.expects(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES - select_month(8, :locale => 'en') + assert_called_with(I18n, :translate, [:'date.month_names', :locale => 'en'], returns: Date::MONTHNAMES) do + select_month(8, :locale => 'en') + end end def test_select_month_given_use_short_month_option_translates_abbr_monthnames - I18n.expects(:translate).with(:'date.abbr_month_names', :locale => 'en').returns Date::ABBR_MONTHNAMES - select_month(8, :locale => 'en', :use_short_month => true) + assert_called_with(I18n, :translate, [:'date.abbr_month_names', :locale => 'en'], returns: Date::ABBR_MONTHNAMES) do + select_month(8, :locale => 'en', :use_short_month => true) + end end def test_date_or_time_select_translates_prompts - @prompt_defaults.each do |key, prompt| - I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt + prompt_defaults = {:year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds'} + defaults = {[:'date.order', :locale => 'en', :default => []] => %w(year month day)} + + prompt_defaults.each do |key, prompt| + defaults[[('datetime.prompts.' + key.to_s).to_sym, :locale => 'en']] = prompt + end + + prompts_check = -> (prompt, x) do + @prompt_called ||= 0 + + return_value = defaults[[prompt, x]] + @prompt_called += 1 if return_value.present? + + return_value end - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(year month day) - datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true) + I18n.stub(:translate, prompts_check) do + datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true, :use_month_names => Date::MONTHNAMES) + end + assert_equal defaults.count, @prompt_called end # date_or_time_select def test_date_or_time_select_given_an_order_options_does_not_translate_order - I18n.expects(:translate).never - datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en') + assert_not_called(I18n, :translate) do + datetime_select('post', 'updated_at', :order => [:year, :month, :day], :locale => 'en', :use_month_names => Date::MONTHNAMES) + end end def test_date_or_time_select_given_no_order_options_translates_order - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(year month day) - datetime_select('post', 'updated_at', :locale => 'en') + assert_called_with(I18n, :translate, [ [:'date.order', :locale => 'en', :default => []], [:"date.month_names", {:locale=>"en"}] ], returns: %w(year month day)) do + datetime_select('post', 'updated_at', :locale => 'en') + end end def test_date_or_time_select_given_invalid_order - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns %w(invalid month day) - - assert_raise StandardError do - datetime_select('post', 'updated_at', :locale => 'en') + assert_called_with(I18n, :translate, [:'date.order', :locale => 'en', :default => []], returns: %w(invalid month day)) do + assert_raise StandardError do + datetime_select('post', 'updated_at', :locale => 'en') + end end end def test_date_or_time_select_given_symbol_keys - I18n.expects(:translate).with(:'date.order', :locale => 'en', :default => []).returns [:year, :month, :day] - datetime_select('post', 'updated_at', :locale => 'en') + assert_called_with(I18n, :translate, [ [:'date.order', :locale => 'en', :default => []], [:"date.month_names", {:locale=>"en"}] ], returns: [:year, :month, :day]) do + datetime_select('post', 'updated_at', :locale => 'en') + end end end diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index d7daba8bf3..6b97cec34c 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -17,13 +17,37 @@ class FormOptionsHelperTest < ActionView::TestCase Album = Struct.new('Album', :id, :title, :genre) end - def setup - @fake_timezones = %w(A B C D E).map do |id| - tz = stub(:name => id, :to_s => id) - ActiveSupport::TimeZone.stubs(:[]).with(id).returns(tz) - tz + module FakeZones + FakeZone = Struct.new(:name) do + def to_s; name; end end - ActiveSupport::TimeZone.stubs(:all).returns(@fake_timezones) + + module ClassMethods + def [](id); fake_zones ? fake_zones[id] : super; end + def all; fake_zones ? fake_zones.values : super; end + def dummy; :test; end + end + + def self.prepended(base) + class << base + mattr_accessor(:fake_zones) + prepend ClassMethods + end + end + end + + ActiveSupport::TimeZone.prepend FakeZones + + setup do + ActiveSupport::TimeZone.fake_zones = %w(A B C D E).map do |id| + [ id, FakeZones::FakeZone.new(id) ] + end.to_h + + @fake_timezones = ActiveSupport::TimeZone.all + end + + teardown do + ActiveSupport::TimeZone.fake_zones = nil end def test_collection_options @@ -1163,8 +1187,8 @@ class FormOptionsHelperTest < ActionView::TestCase def test_time_zone_select_with_priority_zones_as_regexp @firm = Firm.new("D") - @fake_timezones.each_with_index do |tz, i| - tz.stubs(:=~).returns(i.zero? || i == 3) + @fake_timezones.each do |tz| + def tz.=~(re); %(A D).include?(name) end end html = time_zone_select("firm", "time_zone", /A|D/) @@ -1179,15 +1203,16 @@ class FormOptionsHelperTest < ActionView::TestCase html end - def test_time_zone_select_with_priority_zones_as_regexp_using_grep_finds_no_zones + def test_time_zone_select_with_priority_zones_is_not_implemented_with_grep @firm = Firm.new("D") - priority_zones = /A|D/ + # `time_zone_select` can't be written with `grep` because Active Support + # time zones don't support implicit string coercion with `to_str`. @fake_timezones.each do |tz| - priority_zones.stubs(:===).with(tz).raises(Exception) + def tz.===(zone); raise Exception; end end - html = time_zone_select("firm", "time_zone", priority_zones) + html = time_zone_select("firm", "time_zone", /A|D/) assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + "<option value=\"A\">A</option>\n" + diff --git a/actionview/test/template/log_subscriber_test.rb b/actionview/test/template/log_subscriber_test.rb index 7f4c84929f..4776c18b0b 100644 --- a/actionview/test/template/log_subscriber_test.rb +++ b/actionview/test/template/log_subscriber_test.rb @@ -12,13 +12,18 @@ class AVLogSubscriberTest < ActiveSupport::TestCase lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"]) renderer = ActionView::Renderer.new(lookup_context) @view = ActionView::Base.new(renderer, {}) - Rails.stubs(:root).returns(File.expand_path(FIXTURE_LOAD_PATH)) ActionView::LogSubscriber.attach_to :action_view + unless Rails.respond_to?(:root) + @defined_root = true + def Rails.root; :defined_root; end # Minitest `stub` expects the method to be defined. + end end def teardown super ActiveSupport::LogSubscriber.log_subscribers.clear + # We need to undef `root`, RenderTestCases don't want this to be defined + Rails.instance_eval { undef :root } if @defined_root end def set_logger(logger) @@ -26,66 +31,82 @@ class AVLogSubscriberTest < ActiveSupport::TestCase end def test_render_file_template - @view.render(:file => "test/hello_world") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(:file => "test/hello_world") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered test\/hello_world\.erb/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered test\/hello_world\.erb/, @logger.logged(:info).last) + end end def test_render_text_template - @view.render(:text => "TEXT") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(:text => "TEXT") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered text template/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered text template/, @logger.logged(:info).last) + end end def test_render_inline_template - @view.render(:inline => "<%= 'TEXT' %>") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(:inline => "<%= 'TEXT' %>") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered inline template/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered inline template/, @logger.logged(:info).last) + end end def test_render_partial_template - @view.render(:partial => "test/customer") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(:partial => "test/customer") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last) + end end def test_render_partial_with_implicit_path - @view.render(Customer.new("david"), :greeting => "hi") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(Customer.new("david"), :greeting => "hi") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) + end end def test_render_collection_template - @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered test\/_customer.erb/, @logger.logged(:info).last) + end end def test_render_collection_with_implicit_path - @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render([ Customer.new("david"), Customer.new("mary") ], :greeting => "hi") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered customers\/_customer\.html\.erb/, @logger.logged(:info).last) + end end def test_render_collection_template_without_path - @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi") - wait + Rails.stub(:root, File.expand_path(FIXTURE_LOAD_PATH)) do + @view.render([ GoodCustomer.new("david"), Customer.new("mary") ], :greeting => "hi") + wait - assert_equal 1, @logger.logged(:info).size - assert_match(/Rendered collection/, @logger.logged(:info).last) + assert_equal 1, @logger.logged(:info).size + assert_match(/Rendered collection/, @logger.logged(:info).last) + end end end diff --git a/actionview/test/template/test_test.rb b/actionview/test/template/test_test.rb index 88bac85039..e1ff639979 100644 --- a/actionview/test/template/test_test.rb +++ b/actionview/test/template/test_test.rb @@ -41,12 +41,12 @@ class PeopleHelperTest < ActionView::TestCase extend ActiveModel::Naming def to_model; self; end def persisted?; true; end - def self.name; 'Mocha::Mock'; end + def self.name; 'Minitest::Mock'; end }.new "David" the_model = nil extend Module.new { - define_method(:mocha_mock_path) { |model, *args| + define_method(:minitest_mock_path) { |model, *args| the_model = model "/people/1" } diff --git a/activejob/test/integration/queuing_test.rb b/activejob/test/integration/queuing_test.rb index e435ed4aa6..d8425c9706 100644 --- a/activejob/test/integration/queuing_test.rb +++ b/activejob/test/integration/queuing_test.rb @@ -78,7 +78,7 @@ class QueuingTest < ActiveSupport::TestCase TestJob.perform_later @id wait_for_jobs_to_finish_for(5.seconds) assert job_executed - assert_equal 'de', job_output + assert_equal 'de', job_executed_in_locale ensure I18n.available_locales = [:en] I18n.locale = :en diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb index 0c062a025e..262ca72327 100644 --- a/activejob/test/support/integration/dummy_app_template.rb +++ b/activejob/test/support/integration/dummy_app_template.rb @@ -18,8 +18,11 @@ class TestJob < ActiveJob::Base queue_as :integration_tests def perform(x) - File.open(Rails.root.join("tmp/\#{x}"), "w+") do |f| - f.write I18n.locale + File.open(Rails.root.join("tmp/\#{x}"), "wb+") do |f| + f.write Marshal.dump({ + "locale" => I18n.locale.to_s || "en", + "executed_at" => Time.now.to_r + }) end end end diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb index 8319d09520..9897f76fd0 100644 --- a/activejob/test/support/integration/test_case_helpers.rb +++ b/activejob/test/support/integration/test_case_helpers.rb @@ -42,15 +42,23 @@ module TestCaseHelpers end end + def job_file(id) + Dummy::Application.root.join("tmp/#{id}") + end + def job_executed(id=@id) - Dummy::Application.root.join("tmp/#{id}").exist? + job_file(id).exist? + end + + def job_data(id) + Marshal.load(File.binread(job_file(id))) end def job_executed_at(id=@id) - File.new(Dummy::Application.root.join("tmp/#{id}")).ctime + job_data(id)["executed_at"] end - def job_output - File.read Dummy::Application.root.join("tmp/#{@id}") + def job_executed_in_locale(id=@id) + job_data(id)["locale"] end end diff --git a/activemodel/lib/active_model/attribute_assignment.rb b/activemodel/lib/active_model/attribute_assignment.rb index 087d11f708..62014cd1cd 100644 --- a/activemodel/lib/active_model/attribute_assignment.rb +++ b/activemodel/lib/active_model/attribute_assignment.rb @@ -27,7 +27,7 @@ module ActiveModel if !new_attributes.respond_to?(:stringify_keys) raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." end - return if new_attributes.blank? + return if new_attributes.nil? || new_attributes.empty? attributes = new_attributes.stringify_keys _assign_attributes(sanitize_for_mass_assignment(attributes)) diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 0ab8df42f5..6e2e5afd1b 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -26,6 +26,10 @@ module ActiveModel # # define_attribute_methods :name # + # def initialize(name) + # @name = name + # end + # # def name # @name # end @@ -54,7 +58,7 @@ module ActiveModel # # A newly instantiated +Person+ object is unchanged: # - # person = Person.new + # person = Person.new("Uncle Bob") # person.changed? # => false # # Change the name: diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb index 3336691841..287bea719c 100644 --- a/activemodel/test/cases/attribute_assignment_test.rb +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -1,4 +1,5 @@ require "cases/helper" +require "active_support/core_ext/hash/indifferent_access" require "active_support/hash_with_indifferent_access" class AttributeAssignmentTest < ActiveModel::TestCase @@ -23,13 +24,32 @@ class AttributeAssignmentTest < ActiveModel::TestCase class ErrorFromAttributeWriter < StandardError end - class ProtectedParams < ActiveSupport::HashWithIndifferentAccess + class ProtectedParams + attr_accessor :permitted + alias :permitted? :permitted + + delegate :keys, :key?, :has_key?, :empty?, to: :@parameters + + def initialize(attributes) + @parameters = attributes.with_indifferent_access + @permitted = false + end + def permit! @permitted = true + self + end + + def [](key) + @parameters[key] + end + + def to_h + @parameters end - def permitted? - @permitted ||= false + def stringify_keys + dup end def dup diff --git a/activemodel/test/cases/forbidden_attributes_protection_test.rb b/activemodel/test/cases/forbidden_attributes_protection_test.rb index 3cb204a2c5..d8d757f52a 100644 --- a/activemodel/test/cases/forbidden_attributes_protection_test.rb +++ b/activemodel/test/cases/forbidden_attributes_protection_test.rb @@ -2,12 +2,14 @@ require 'cases/helper' require 'active_support/core_ext/hash/indifferent_access' require 'models/account' -class ProtectedParams < ActiveSupport::HashWithIndifferentAccess +class ProtectedParams attr_accessor :permitted alias :permitted? :permitted + delegate :keys, :key?, :has_key?, :empty?, to: :@parameters + def initialize(attributes) - super(attributes) + @parameters = attributes @permitted = false end @@ -15,6 +17,10 @@ class ProtectedParams < ActiveSupport::HashWithIndifferentAccess @permitted = true self end + + def to_h + @parameters + end end class ActiveModelMassUpdateProtectionTest < ActiveSupport::TestCase diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 98c6384402..3724b1a387 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,13 @@ +* Add schema dumping support for PostgreSQL geometric data types. + + *Ryuta Kamizono* + +* Except keys of `build_record`'s argument from `create_scope` in `initialize_attributes`. + + Fixes #21893. + + *Yuichiro Kaneko* + * Deprecate `connection.tables` on the SQLite3 and MySQL adapters. Also deprecate passing arguments to `#tables`. And deprecate `table_exists?`. diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index c7b396f3d4..d64ab64c99 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -163,9 +163,12 @@ module ActiveRecord @reflection = @owner.class._reflect_on_association(reflection_name) end - def initialize_attributes(record) #:nodoc: + def initialize_attributes(record, except_from_scope_attributes = nil) #:nodoc: + except_from_scope_attributes ||= {} skip_assign = [reflection.foreign_key, reflection.type].compact - attributes = create_scope.except(*(record.changed - skip_assign)) + assigned_keys = record.changed + assigned_keys += except_from_scope_attributes.keys.map(&:to_s) + attributes = create_scope.except(*(assigned_keys - skip_assign)) record.assign_attributes(attributes) set_inverse_instance(record) end @@ -248,7 +251,7 @@ module ActiveRecord def build_record(attributes) reflection.build_association(attributes) do |record| - initialize_attributes(record) + initialize_attributes(record, attributes) end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index abf0124562..159cbcb85a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -206,14 +206,13 @@ module ActiveRecord include ColumnMethods attr_accessor :indexes - attr_reader :name, :temporary, :options, :as, :foreign_keys, :native + attr_reader :name, :temporary, :options, :as, :foreign_keys - def initialize(types, name, temporary, options, as = nil) + def initialize(name, temporary, options, as = nil) @columns_hash = {} @indexes = {} @foreign_keys = {} @primary_keys = nil - @native = types @temporary = temporary @options = options @as = as @@ -362,11 +361,8 @@ module ActiveRecord def new_column_definition(name, type, options) # :nodoc: type = aliased_types(type.to_s, type) column = create_column_definition name, type - limit = options.fetch(:limit) do - native[type][:limit] if native[type].is_a?(Hash) - end - column.limit = limit + column.limit = options[:limit] column.precision = options[:precision] column.scale = options[:scale] column.default = options[:default] @@ -627,11 +623,6 @@ module ActiveRecord def foreign_key_exists?(*args) # :nodoc: @base.foreign_key_exists?(name, *args) end - - private - def native - @base.native_database_types - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 5cacf6eddc..b50d28862c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1168,7 +1168,7 @@ module ActiveRecord private def create_table_definition(name, temporary = false, options = nil, as = nil) - TableDefinition.new native_database_types, name, temporary, options, as + TableDefinition.new(name, temporary, options, as) end def create_alter_table(name) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 55910865e5..4d4dc07b04 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -289,14 +289,14 @@ module ActiveRecord # locks # # Return true if we got the lock, otherwise false - def get_advisory_lock(key) # :nodoc: + def get_advisory_lock(lock_id) # :nodoc: end # This is meant to be implemented by the adapters that support advisory # locks. # # Return true if we released the lock, otherwise false - def release_advisory_lock(key) # :nodoc: + def release_advisory_lock(lock_id) # :nodoc: end # A list of extensions, to be filled in by adapters that support them. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index b6e17ad4ef..735bc0e67a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -134,9 +134,7 @@ module ActiveRecord time: { name: "time" }, date: { name: "date" }, binary: { name: "blob" }, - blob: { name: "blob" }, boolean: { name: "tinyint", limit: 1 }, - bigint: { name: "bigint" }, json: { name: "json" }, } @@ -226,12 +224,12 @@ module ActiveRecord version >= '5.0.0' end - def get_advisory_lock(key, timeout = 0) # :nodoc: - select_value("SELECT GET_LOCK('#{key}', #{timeout});").to_s == '1' + def get_advisory_lock(lock_name, timeout = 0) # :nodoc: + select_value("SELECT GET_LOCK('#{lock_name}', #{timeout});").to_s == '1' end - def release_advisory_lock(key) # :nodoc: - select_value("SELECT RELEASE_LOCK('#{key}')").to_s == '1' + def release_advisory_lock(lock_name) # :nodoc: + select_value("SELECT RELEASE_LOCK('#{lock_name}')").to_s == '1' end def native_database_types @@ -590,10 +588,8 @@ module ActiveRecord sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}" execute_and_free(sql, 'SCHEMA') do |result| each_hash(result).map do |field| - field_name = set_field_encoding(field[:Field]) - sql_type = field[:Type] - type_metadata = fetch_type_metadata(sql_type, field[:Extra]) - new_column(field_name, field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation]) + type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) + new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation]) end end end @@ -1043,7 +1039,7 @@ module ActiveRecord end def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: - MySQL::TableDefinition.new(native_database_types, name, temporary, options, as) + MySQL::TableDefinition.new(name, temporary, options, as) end def integer_to_sql(limit) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 773ecbe126..3944698910 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -185,10 +185,6 @@ module ActiveRecord def full_version @full_version ||= @connection.server_info[:version] end - - def set_field_encoding field_name - field_name - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 89d18ee14e..f2d7b54105 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -104,6 +104,11 @@ module ActiveRecord end end + def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc: + field = set_field_encoding(field) + super + end + def error_number(exception) # :nodoc: exception.errno if exception.respond_to?(:errno) end @@ -463,7 +468,7 @@ module ActiveRecord @full_version ||= @connection.server_info end - def set_field_encoding field_name + def set_field_encoding(field_name) field_name.force_encoding(client_encoding) if internal_enc = Encoding.default_internal field_name = field_name.encode!(internal_enc) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ed6ab8235f..f731da9e18 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -72,7 +72,6 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: "serial primary key", - bigserial: "bigserial", string: { name: "character varying" }, text: { name: "text" }, integer: { name: "integer" }, @@ -89,7 +88,6 @@ module ActiveRecord int8range: { name: "int8range" }, binary: { name: "bytea" }, boolean: { name: "boolean" }, - bigint: { name: "bigint" }, xml: { name: "xml" }, tsvector: { name: "tsvector" }, hstore: { name: "hstore" }, @@ -102,6 +100,12 @@ module ActiveRecord ltree: { name: "ltree" }, citext: { name: "citext" }, point: { name: "point" }, + line: { name: "line" }, + lseg: { name: "lseg" }, + box: { name: "box" }, + path: { name: "path" }, + polygon: { name: "polygon" }, + circle: { name: "circle" }, bit: { name: "bit" }, bit_varying: { name: "bit varying" }, money: { name: "money" }, @@ -306,18 +310,18 @@ module ActiveRecord postgresql_version >= 90300 end - def get_advisory_lock(key) # :nodoc: - unless key.is_a?(Integer) && key.bit_length <= 63 - raise(ArgumentError, "Postgres requires advisory lock keys to be a signed 64 bit integer") + def get_advisory_lock(lock_id) # :nodoc: + unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 + raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") end - select_value("SELECT pg_try_advisory_lock(#{key});") + select_value("SELECT pg_try_advisory_lock(#{lock_id});") end - def release_advisory_lock(key) # :nodoc: - unless key.is_a?(Integer) && key.bit_length <= 63 - raise(ArgumentError, "Postgres requires advisory lock keys to be a signed 64 bit integer") + def release_advisory_lock(lock_id) # :nodoc: + unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 + raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") end - select_value("SELECT pg_advisory_unlock(#{key})") + select_value("SELECT pg_advisory_unlock(#{lock_id})") end def enable_extension(name) @@ -457,15 +461,15 @@ module ActiveRecord m.register_type 'macaddr', OID::SpecializedString.new(:macaddr) m.register_type 'citext', OID::SpecializedString.new(:citext) m.register_type 'ltree', OID::SpecializedString.new(:ltree) + m.register_type 'line', OID::SpecializedString.new(:line) + m.register_type 'lseg', OID::SpecializedString.new(:lseg) + m.register_type 'box', OID::SpecializedString.new(:box) + m.register_type 'path', OID::SpecializedString.new(:path) + m.register_type 'polygon', OID::SpecializedString.new(:polygon) + m.register_type 'circle', OID::SpecializedString.new(:circle) # FIXME: why are we keeping these types as strings? m.alias_type 'interval', 'varchar' - m.alias_type 'path', 'varchar' - m.alias_type 'line', 'varchar' - m.alias_type 'polygon', 'varchar' - m.alias_type 'circle', 'varchar' - m.alias_type 'lseg', 'varchar' - m.alias_type 'box', 'varchar' register_class_with_precision m, 'time', Type::Time register_class_with_precision m, 'timestamp', OID::DateTime @@ -736,7 +740,7 @@ module ActiveRecord end def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: - PostgreSQL::TableDefinition.new native_database_types, name, temporary, options, as + PostgreSQL::TableDefinition.new(name, temporary, options, as) end def can_perform_case_insensitive_comparison_for?(column) diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 589c70db0d..8b719e0bcb 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -198,10 +198,11 @@ module ActiveRecord # If this is a StrongParameters hash, and access to inheritance_column is not permitted, # this will ignore the inheritance column and return nil def subclass_from_attributes?(attrs) - attribute_names.include?(inheritance_column) && attrs.is_a?(Hash) + attribute_names.include?(inheritance_column) && (attrs.is_a?(Hash) || attrs.respond_to?(:permitted?)) end def subclass_from_attributes(attrs) + attrs = attrs.to_h if attrs.respond_to?(:permitted?) subclass_name = attrs.with_indifferent_access[inheritance_column] if subclass_name.present? diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index ffd243a517..ca2537cdc3 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1202,17 +1202,17 @@ module ActiveRecord end def with_advisory_lock - key = generate_migrator_advisory_lock_key - got_lock = Base.connection.get_advisory_lock(key) + lock_id = generate_migrator_advisory_lock_id + got_lock = Base.connection.get_advisory_lock(lock_id) raise ConcurrentMigrationError unless got_lock load_migrated # reload schema_migrations to be sure it wasn't changed by another process before we got the lock yield ensure - Base.connection.release_advisory_lock(key) if got_lock + Base.connection.release_advisory_lock(lock_id) if got_lock end MIGRATOR_SALT = 2053462845 - def generate_migrator_advisory_lock_key + def generate_migrator_advisory_lock_id db_name_hash = Zlib.crc32(Base.connection.current_database) MIGRATOR_SALT * db_name_hash end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 94316d5249..46c6d8c293 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -358,6 +358,14 @@ module ActiveRecord # if the predicate returns +true+ the attribute will become +false+. This # method toggles directly the underlying value without calling any setter. # Returns +self+. + # + # Example: + # + # user = User.first + # user.banned? # => false + # user.toggle(:banned) + # user.banned? # => true + # def toggle(attribute) self[attribute] = !public_send("#{attribute}?") self diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 27de313d05..b1333f110c 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -36,13 +36,8 @@ module ActiveRecord # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. - BLACKLISTED_ARRAY_METHODS = [ - :compact!, :flatten!, :reject!, :reverse!, :rotate!, :map!, - :shuffle!, :slice!, :sort!, :sort_by!, :delete_if, - :keep_if, :pop, :shift, :delete_at, :select! - ].to_set # :nodoc: - - delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, to: :to_a + delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, + :[], :&, :|, :+, :-, :sample, :reverse, :compact, to: :to_a delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :to => :klass @@ -114,21 +109,14 @@ module ActiveRecord def respond_to?(method, include_private = false) super || @klass.respond_to?(method, include_private) || - array_delegable?(method) || arel.respond_to?(method, include_private) end protected - def array_delegable?(method) - Array.method_defined?(method) && BLACKLISTED_ARRAY_METHODS.exclude?(method) - end - def method_missing(method, *args, &block) if @klass.respond_to?(method) scoping { @klass.public_send(method, *args, &block) } - elsif array_delegable?(method) - to_a.public_send(method, *args, &block) elsif arel.respond_to?(method) arel.public_send(method, *args, &block) else diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 2dc52982c9..7a4bf5338d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -13,6 +13,8 @@ module ActiveRecord # WhereChain objects act as placeholder for queries in which #where does not have any parameter. # In this case, #where must be chained with #not to return a new relation. class WhereChain + include ActiveModel::ForbiddenAttributesProtection + def initialize(scope) @scope = scope end @@ -41,6 +43,8 @@ module ActiveRecord # User.where.not(name: "Jon", role: "admin") # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' def not(opts, *rest) + opts = sanitize_forbidden_attributes(opts) + where_clause = @scope.send(:where_clause_factory).build(opts, rest) @scope.references!(PredicateBuilder.references(opts)) if Hash === opts diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 5c3318651a..67d7f83cb4 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -12,6 +12,7 @@ module ActiveRecord # Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation. # Returns an array representing the intersection of the resulting records with <tt>other</tt>, if <tt>other</tt> is an array. + # # Post.where(published: true).joins(:comments).merge( Comment.where(spam: false) ) # # Performs a single join query with both where conditions. # @@ -37,11 +38,14 @@ module ActiveRecord end def merge!(other) # :nodoc: - if !other.is_a?(Relation) && other.respond_to?(:to_proc) + if other.is_a?(Hash) + Relation::HashMerger.new(self, other).merge + elsif other.is_a?(Relation) + Relation::Merger.new(self, other).merge + elsif other.respond_to?(:to_proc) instance_exec(&other) else - klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger - klass.new(self, other).merge + raise ArgumentError, "#{other.inspect} is not an ActiveRecord::Relation" end end diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 0b5c9e1798..8a7a0bb25d 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -16,8 +16,8 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase end def test_add_index - # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed - def (ActiveRecord::Base.connection).table_exists?(*); true; end + # add_index calls data_source_exists? and index_name_exists? which can't work since execute is stubbed + def (ActiveRecord::Base.connection).data_source_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " @@ -60,7 +60,7 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase end def test_index_in_create - def (ActiveRecord::Base.connection).table_exists?(*); false; end + def (ActiveRecord::Base.connection).data_source_exists?(*); false; end %w(SPATIAL FULLTEXT UNIQUE).each do |type| expected = "CREATE TABLE `people` (#{type} INDEX `index_people_on_last_name` (`last_name`) ) ENGINE=InnoDB" @@ -78,7 +78,7 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase end def test_index_in_bulk_change - def (ActiveRecord::Base.connection).table_exists?(*); true; end + def (ActiveRecord::Base.connection).data_source_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end %w(SPATIAL FULLTEXT UNIQUE).each do |type| @@ -152,7 +152,7 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase end def test_indexes_in_create - ActiveRecord::Base.connection.stubs(:table_exists?).with(:temp).returns(false) + ActiveRecord::Base.connection.stubs(:data_source_exists?).with(:temp).returns(false) ActiveRecord::Base.connection.stubs(:index_name_exists?).with(:index_temp_on_zip).returns(false) expected = "CREATE TEMPORARY TABLE `temp` ( INDEX `index_temp_on_zip` (`zip`) ) ENGINE=InnoDB AS SELECT id, name, zip FROM a_really_complicated_query" diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 75653ee9af..390dd15b92 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -171,31 +171,31 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase end def test_get_and_release_advisory_lock - key = "test_key" + lock_name = "test_lock_name" - got_lock = @connection.get_advisory_lock(key) + got_lock = @connection.get_advisory_lock(lock_name) assert got_lock, "get_advisory_lock should have returned true but it didn't" - assert_equal test_lock_free(key), false, + assert_equal test_lock_free(lock_name), false, "expected the test advisory lock to be held but it wasn't" - released_lock = @connection.release_advisory_lock(key) + released_lock = @connection.release_advisory_lock(lock_name) assert released_lock, "expected release_advisory_lock to return true but it didn't" - assert test_lock_free(key), 'expected the test key to be available after releasing' + assert test_lock_free(lock_name), 'expected the test lock to be available after releasing' end def test_release_non_existent_advisory_lock - fake_key = "fake_key" - released_non_existent_lock = @connection.release_advisory_lock(fake_key) + lock_name = "fake_lock_name" + released_non_existent_lock = @connection.release_advisory_lock(lock_name) assert_equal released_non_existent_lock, false, 'expected release_advisory_lock to return false when there was no lock to release' end protected - def test_lock_free(key) - @connection.select_value("SELECT IS_FREE_LOCK('#{key}');") == '1' + def test_lock_free(lock_name) + @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == '1' end private diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index b97eb3e228..99f97c7914 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -16,7 +16,7 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase end def test_add_index - # add_index calls table_exists? and index_name_exists? which can't work since execute is stubbed + # add_index calls data_source_exists? and index_name_exists? which can't work since execute is stubbed def (ActiveRecord::Base.connection).data_source_exists?(*); true; end def (ActiveRecord::Base.connection).index_name_exists?(*); false; end diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 71c4028675..507d024bb6 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -133,30 +133,30 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase end def test_get_and_release_advisory_lock - key = "test_key" + lock_name = "test_lock_name" - got_lock = @connection.get_advisory_lock(key) + got_lock = @connection.get_advisory_lock(lock_name) assert got_lock, "get_advisory_lock should have returned true but it didn't" - assert_equal test_lock_free(key), false, + assert_equal test_lock_free(lock_name), false, "expected the test advisory lock to be held but it wasn't" - released_lock = @connection.release_advisory_lock(key) + released_lock = @connection.release_advisory_lock(lock_name) assert released_lock, "expected release_advisory_lock to return true but it didn't" - assert test_lock_free(key), 'expected the test key to be available after releasing' + assert test_lock_free(lock_name), 'expected the test lock to be available after releasing' end def test_release_non_existent_advisory_lock - fake_key = "fake_key" - released_non_existent_lock = @connection.release_advisory_lock(fake_key) + lock_name = "fake_lock_name" + released_non_existent_lock = @connection.release_advisory_lock(lock_name) assert_equal released_non_existent_lock, false, 'expected release_advisory_lock to return false when there was no lock to release' end protected - def test_lock_free(key) - @connection.select_value("SELECT IS_FREE_LOCK('#{key}');") == 1 + def test_lock_free(lock_name) + @connection.select_value("SELECT IS_FREE_LOCK('#{lock_name}');") == 1 end end diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 0ecac2cfa3..d559de3e28 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -211,33 +211,33 @@ module ActiveRecord end def test_get_and_release_advisory_lock - key = 5295901941911233559 + lock_id = 5295901941911233559 list_advisory_locks = <<-SQL SELECT locktype, - (classid::bigint << 32) | objid::bigint AS lock_key + (classid::bigint << 32) | objid::bigint AS lock_id FROM pg_locks WHERE locktype = 'advisory' SQL - got_lock = @connection.get_advisory_lock(key) + got_lock = @connection.get_advisory_lock(lock_id) assert got_lock, "get_advisory_lock should have returned true but it didn't" - advisory_lock = @connection.query(list_advisory_locks).find {|l| l[1] == key} + advisory_lock = @connection.query(list_advisory_locks).find {|l| l[1] == lock_id} assert advisory_lock, - "expected to find an advisory lock with key #{key} but there wasn't one" + "expected to find an advisory lock with lock_id #{lock_id} but there wasn't one" - released_lock = @connection.release_advisory_lock(key) + released_lock = @connection.release_advisory_lock(lock_id) assert released_lock, "expected release_advisory_lock to return true but it didn't" - advisory_locks = @connection.query(list_advisory_locks).select {|l| l[1] == key} + advisory_locks = @connection.query(list_advisory_locks).select {|l| l[1] == lock_id} assert_empty advisory_locks, - "expected to have released advisory lock with key #{key} but it was still held" + "expected to have released advisory lock with lock_id #{lock_id} but it was still held" end def test_release_non_existent_advisory_lock - fake_key = 2940075057017742022 + fake_lock_id = 2940075057017742022 with_warning_suppression do - released_non_existent_lock = @connection.release_advisory_lock(fake_key) + released_non_existent_lock = @connection.release_advisory_lock(fake_lock_id) assert_equal released_non_existent_lock, false, 'expected release_advisory_lock to return false when there was no lock to release' end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 0baf985654..8d0c5bf23f 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -167,16 +167,18 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase end class PostgresqlGeometricTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + class PostgresqlGeometric < ActiveRecord::Base; end setup do @connection = ActiveRecord::Base.connection @connection.create_table("postgresql_geometrics") do |t| - t.column :a_line_segment, :lseg - t.column :a_box, :box - t.column :a_path, :path - t.column :a_polygon, :polygon - t.column :a_circle, :circle + t.lseg :a_line_segment + t.box :a_box + t.path :a_path + t.polygon :a_polygon + t.circle :a_circle end end @@ -233,4 +235,142 @@ class PostgresqlGeometricTest < ActiveRecord::PostgreSQLTestCase objs = PostgresqlGeometric.find_by_sql "SELECT isclosed(a_path) FROM postgresql_geometrics ORDER BY id ASC" assert_equal [false, true], objs.map(&:isclosed) end + + def test_schema_dumping + output = dump_table_schema("postgresql_geometrics") + assert_match %r{t\.lseg\s+"a_line_segment"$}, output + assert_match %r{t\.box\s+"a_box"$}, output + assert_match %r{t\.path\s+"a_path"$}, output + assert_match %r{t\.polygon\s+"a_polygon"$}, output + assert_match %r{t\.circle\s+"a_circle"$}, output + end +end + +class PostgreSQLGeometricLineTest < ActiveRecord::PostgreSQLTestCase + include SchemaDumpingHelper + + class PostgresqlLine < ActiveRecord::Base; end + + setup do + unless ActiveRecord::Base.connection.send(:postgresql_version) >= 90400 + skip("line type is not fully implemented") + end + @connection = ActiveRecord::Base.connection + @connection.create_table("postgresql_lines") do |t| + t.line :a_line + end + end + + teardown do + @connection.drop_table 'postgresql_lines', if_exists: true + end + + def test_geometric_line_type + g = PostgresqlLine.new( + a_line: '{2.0, 3, 5.5}' + ) + g.save! + + h = PostgresqlLine.find(g.id) + assert_equal '{2,3,5.5}', h.a_line + end + + def test_alternative_format_line_type + g = PostgresqlLine.new( + a_line: '(2.0, 3), (4.0, 6.0)' + ) + g.save! + + h = PostgresqlLine.find(g.id) + assert_equal '{1.5,-1,0}', h.a_line + end + + def test_schema_dumping_for_line_type + output = dump_table_schema("postgresql_lines") + assert_match %r{t\.line\s+"a_line"$}, output + end +end + +class PostgreSQLGeometricTypesTest < ActiveRecord::PostgreSQLTestCase + attr_reader :connection, :table_name + + def setup + super + @connection = ActiveRecord::Base.connection + @table_name = :testings + end + + def test_creating_column_with_point_type + connection.create_table(table_name) do |t| + t.point :foo_point + end + + assert_column_exists(:foo_point) + assert_type_correct(:foo_point, :point) + end + + def test_creating_column_with_line_type + connection.create_table(table_name) do |t| + t.line :foo_line + end + + assert_column_exists(:foo_line) + assert_type_correct(:foo_line, :line) + end + + def test_creating_column_with_lseg_type + connection.create_table(table_name) do |t| + t.lseg :foo_lseg + end + + assert_column_exists(:foo_lseg) + assert_type_correct(:foo_lseg, :lseg) + end + + def test_creating_column_with_box_type + connection.create_table(table_name) do |t| + t.box :foo_box + end + + assert_column_exists(:foo_box) + assert_type_correct(:foo_box, :box) + end + + def test_creating_column_with_path_type + connection.create_table(table_name) do |t| + t.path :foo_path + end + + assert_column_exists(:foo_path) + assert_type_correct(:foo_path, :path) + end + + def test_creating_column_with_polygon_type + connection.create_table(table_name) do |t| + t.polygon :foo_polygon + end + + assert_column_exists(:foo_polygon) + assert_type_correct(:foo_polygon, :polygon) + end + + def test_creating_column_with_circle_type + connection.create_table(table_name) do |t| + t.circle :foo_circle + end + + assert_column_exists(:foo_circle) + assert_type_correct(:foo_circle, :circle) + end + + private + + def assert_column_exists(column_name) + assert connection.column_exists?(table_name, column_name) + end + + def assert_type_correct(column_name, type) + column = connection.columns(table_name).find { |c| c.name == column_name.to_s } + assert_equal type, column.type + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index eb94870a35..50ca6537cc 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -203,9 +203,22 @@ class HasManyAssociationsTest < ActiveRecord::TestCase bulb = car.bulbs.create assert_equal 'defaulty', bulb.name + end + + def test_build_and_create_from_association_should_respect_passed_attributes_over_default_scope + car = Car.create(name: 'honda') + + bulb = car.bulbs.build(name: 'exotic') + assert_equal 'exotic', bulb.name - bulb = car.bulbs.create(:name => 'exotic') + bulb = car.bulbs.create(name: 'exotic') assert_equal 'exotic', bulb.name + + bulb = car.awesome_bulbs.build(frickinawesome: false) + assert_equal false, bulb.frickinawesome + + bulb = car.awesome_bulbs.create(frickinawesome: false) + assert_equal false, bulb.frickinawesome end def test_build_from_association_should_respect_scope diff --git a/activerecord/test/cases/forbidden_attributes_protection_test.rb b/activerecord/test/cases/forbidden_attributes_protection_test.rb index f4e7646f03..91921469b8 100644 --- a/activerecord/test/cases/forbidden_attributes_protection_test.rb +++ b/activerecord/test/cases/forbidden_attributes_protection_test.rb @@ -1,14 +1,20 @@ require 'cases/helper' require 'active_support/core_ext/hash/indifferent_access' -require 'models/person' + require 'models/company' +require 'models/person' +require 'models/ship' +require 'models/ship_part' +require 'models/treasure' -class ProtectedParams < ActiveSupport::HashWithIndifferentAccess +class ProtectedParams attr_accessor :permitted alias :permitted? :permitted + delegate :keys, :key?, :has_key?, :empty?, to: :@parameters + def initialize(attributes) - super(attributes) + @parameters = attributes.with_indifferent_access @permitted = false end @@ -17,6 +23,18 @@ class ProtectedParams < ActiveSupport::HashWithIndifferentAccess self end + def [](key) + @parameters[key] + end + + def to_h + @parameters + end + + def stringify_keys + dup + end + def dup super.tap do |duplicate| duplicate.instance_variable_set :@permitted, @permitted @@ -75,6 +93,13 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase end end + def test_create_with_works_with_permitted_params + params = ProtectedParams.new(first_name: 'Guille').permit! + + person = Person.create_with(params).create! + assert_equal 'Guille', person.first_name + end + def test_create_with_works_with_params_values params = ProtectedParams.new(first_name: 'Guille') @@ -90,10 +115,51 @@ class ForbiddenAttributesProtectionTest < ActiveRecord::TestCase end end + def test_where_works_with_permitted_params + params = ProtectedParams.new(first_name: 'Guille').permit! + + person = Person.where(params).create! + assert_equal 'Guille', person.first_name + end + def test_where_works_with_params_values params = ProtectedParams.new(first_name: 'Guille') person = Person.where(first_name: params[:first_name]).create! assert_equal 'Guille', person.first_name end + + def test_where_not_checks_permitted + params = ProtectedParams.new(first_name: 'Guille', gender: 'm') + + assert_raises(ActiveModel::ForbiddenAttributesError) do + Person.where().not(params) + end + end + + def test_where_not_works_with_permitted_params + params = ProtectedParams.new(first_name: 'Guille').permit! + Person.create!(params) + assert_empty Person.where.not(params).select {|p| p.first_name == 'Guille' } + end + + def test_strong_params_style_objects_work_with_singular_associations + params = ProtectedParams.new( name: "Stern", ship_attributes: ProtectedParams.new(name: "The Black Rock").permit!).permit! + part = ShipPart.new(params) + + assert_equal "Stern", part.name + assert_equal "The Black Rock", part.ship.name + end + + def test_strong_params_style_objects_work_with_collection_associations + params = ProtectedParams.new( + trinkets_attributes: ProtectedParams.new( + "0" => ProtectedParams.new(name: "Necklace").permit!, + "1" => ProtectedParams.new(name: "Spoon").permit! ) ).permit! + part = ShipPart.new(params) + + assert_equal "Necklace", part.trinkets[0].name + assert_equal "Spoon", part.trinkets[1].name + end + end diff --git a/activerecord/test/cases/migration/postgresql_geometric_types_test.rb b/activerecord/test/cases/migration/postgresql_geometric_types_test.rb deleted file mode 100644 index e4772905bb..0000000000 --- a/activerecord/test/cases/migration/postgresql_geometric_types_test.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'cases/helper' - -module ActiveRecord - class Migration - class PostgreSQLGeometricTypesTest < ActiveRecord::TestCase - attr_reader :connection, :table_name - - def setup - super - @connection = ActiveRecord::Base.connection - @table_name = :testings - end - - if current_adapter?(:PostgreSQLAdapter) - def test_creating_column_with_point_type - connection.create_table(table_name) do |t| - t.point :foo_point - end - - assert_column_exists(:foo_point) - assert_type_correct(:foo_point, :point) - end - - def test_creating_column_with_line_type - connection.create_table(table_name) do |t| - t.line :foo_line - end - - assert_column_exists(:foo_line) - assert_type_correct(:foo_line, :line) - end - - def test_creating_column_with_lseg_type - connection.create_table(table_name) do |t| - t.lseg :foo_lseg - end - - assert_column_exists(:foo_lseg) - assert_type_correct(:foo_lseg, :lseg) - end - - def test_creating_column_with_box_type - connection.create_table(table_name) do |t| - t.box :foo_box - end - - assert_column_exists(:foo_box) - assert_type_correct(:foo_box, :box) - end - - def test_creating_column_with_path_type - connection.create_table(table_name) do |t| - t.path :foo_path - end - - assert_column_exists(:foo_path) - assert_type_correct(:foo_path, :path) - end - - def test_creating_column_with_polygon_type - connection.create_table(table_name) do |t| - t.polygon :foo_polygon - end - - assert_column_exists(:foo_polygon) - assert_type_correct(:foo_polygon, :polygon) - end - - def test_creating_column_with_circle_type - connection.create_table(table_name) do |t| - t.circle :foo_circle - end - - assert_column_exists(:foo_circle) - assert_type_correct(:foo_circle, :circle) - end - end - - private - def assert_column_exists(column_name) - columns = connection.columns(table_name) - assert columns.map(&:name).include?(column_name.to_s) - end - - def assert_type_correct(column_name, type) - columns = connection.columns(table_name) - column = columns.select{ |c| c.name == column_name.to_s }.first - assert_equal type.to_s, column.sql_type - end - - end - end -end
\ No newline at end of file diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 15a0e0516d..c3c204cf9f 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -524,33 +524,33 @@ class MigrationTest < ActiveRecord::TestCase end if ActiveRecord::Base.connection.supports_advisory_locks? - def test_migrator_generates_valid_lock_key + def test_migrator_generates_valid_lock_id migration = Class.new(ActiveRecord::Migration).new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - lock_key = migrator.send(:generate_migrator_advisory_lock_key) + lock_id = migrator.send(:generate_migrator_advisory_lock_id) - assert ActiveRecord::Base.connection.get_advisory_lock(lock_key), - "the Migrator should have generated a valid lock key, but it didn't" - assert ActiveRecord::Base.connection.release_advisory_lock(lock_key), - "the Migrator should have generated a valid lock key, but it didn't" + assert ActiveRecord::Base.connection.get_advisory_lock(lock_id), + "the Migrator should have generated a valid lock id, but it didn't" + assert ActiveRecord::Base.connection.release_advisory_lock(lock_id), + "the Migrator should have generated a valid lock id, but it didn't" end - def test_generate_migrator_advisory_lock_key + def test_generate_migrator_advisory_lock_id # It is important we are consistent with how we generate this so that # exclusive locking works across migrator versions migration = Class.new(ActiveRecord::Migration).new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - lock_key = migrator.send(:generate_migrator_advisory_lock_key) + lock_id = migrator.send(:generate_migrator_advisory_lock_id) current_database = ActiveRecord::Base.connection.current_database salt = ActiveRecord::Migrator::MIGRATOR_SALT - expected_key = Zlib.crc32(current_database) * salt + expected_id = Zlib.crc32(current_database) * salt - assert lock_key == expected_key, "expected lock key generated by the migrator to be #{expected_key}, but it was #{lock_key} instead" - assert lock_key.is_a?(Fixnum), "expected lock key to be a Fixnum, but it wasn't" - assert lock_key.bit_length <= 63, "lock key must be a signed integer of max 63 bits magnitude" + assert lock_id == expected_id, "expected lock id generated by the migrator to be #{expected_id}, but it was #{lock_id} instead" + assert lock_id.is_a?(Fixnum), "expected lock id to be a Fixnum, but it wasn't" + assert lock_id.bit_length <= 63, "lock id must be a signed integer of max 63 bits magnitude" end def test_migrator_one_up_with_unavailable_lock @@ -564,9 +564,9 @@ class MigrationTest < ActiveRecord::TestCase }.new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - lock_key = migrator.send(:generate_migrator_advisory_lock_key) + lock_id = migrator.send(:generate_migrator_advisory_lock_id) - with_another_process_holding_lock(lock_key) do + with_another_process_holding_lock(lock_id) do assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.migrate } end @@ -585,9 +585,9 @@ class MigrationTest < ActiveRecord::TestCase }.new migrator = ActiveRecord::Migrator.new(:up, [migration], 100) - lock_key = migrator.send(:generate_migrator_advisory_lock_key) + lock_id = migrator.send(:generate_migrator_advisory_lock_id) - with_another_process_holding_lock(lock_key) do + with_another_process_holding_lock(lock_id) do assert_raise(ActiveRecord::ConcurrentMigrationError) { migrator.run } end @@ -606,18 +606,18 @@ class MigrationTest < ActiveRecord::TestCase } end - def with_another_process_holding_lock(lock_key) + def with_another_process_holding_lock(lock_id) thread_lock = Concurrent::CountDownLatch.new test_terminated = Concurrent::CountDownLatch.new other_process = Thread.new do begin conn = ActiveRecord::Base.connection_pool.checkout - conn.get_advisory_lock(lock_key) + conn.get_advisory_lock(lock_id) thread_lock.count_down test_terminated.wait # hold the lock open until we tested everything ensure - conn.release_advisory_lock(lock_key) + conn.release_advisory_lock(lock_id) ActiveRecord::Base.connection_pool.checkin(conn) end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index 93cb631a04..0b700afcb4 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -1068,39 +1068,4 @@ class TestHasManyAutosaveAssociationWhichItselfHasAutosaveAssociations < ActiveR assert_not part.valid? assert_equal ["Ship name can't be blank"], part.errors.full_messages end - - class ProtectedParameters - def initialize(hash) - @hash = hash - end - - def permitted? - true - end - - def [](key) - @hash[key] - end - - def to_h - @hash - end - end - - test "strong params style objects can be assigned for singular associations" do - params = { name: "Stern", ship_attributes: - ProtectedParameters.new(name: "The Black Rock") } - part = ShipPart.new(params) - - assert_equal "Stern", part.name - assert_equal "The Black Rock", part.ship.name - end - - test "strong params style objects can be assigned for collection associations" do - params = { trinkets_attributes: ProtectedParameters.new("0" => ProtectedParameters.new(name: "Necklace"), "1" => ProtectedParameters.new(name: "Spoon")) } - part = ShipPart.new(params) - - assert_equal "Necklace", part.trinkets[0].name - assert_equal "Spoon", part.trinkets[1].name - end end diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index 989f4e1e5d..b4269bd56d 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -40,12 +40,6 @@ module ActiveRecord assert_respond_to target, method end end - - ActiveRecord::Delegation::BLACKLISTED_ARRAY_METHODS.each do |method| - define_method "test_#{method}_is_not_delegated_to_Array" do - assert_raises(NoMethodError) { call_method(target, method) } - end - end end class DelegationAssociationTest < DelegationTest diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index 8794bc8043..f46d414b95 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -235,6 +235,13 @@ module ActiveRecord assert_equal 3, relation.where(id: post.id).pluck(:id).size end + def test_merge_raises_with_invalid_argument + assert_raises ArgumentError do + relation = Relation.new(FakeKlass, :b, nil) + relation.merge(true) + end + end + def test_respond_to_for_non_selected_element post = Post.select(:title).first assert_equal false, post.respond_to?(:body), "post should not respond_to?(:body) since invoking it raises exception" diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 2a2c2bc8d0..43f133b12d 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -353,6 +353,38 @@ class SchemaDumperTest < ActiveRecord::TestCase ActiveRecord::Base.table_name_suffix = ActiveRecord::Base.table_name_prefix = '' $stdout = original end + + def test_schema_dump_with_table_name_prefix_and_ignoring_tables + original, $stdout = $stdout, StringIO.new + + create_cat_migration = Class.new(ActiveRecord::Migration) do + def change + create_table("cats") do |t| + end + create_table("omg_cats") do |t| + end + end + end + + original_table_name_prefix = ActiveRecord::Base.table_name_prefix + original_schema_dumper_ignore_tables = ActiveRecord::SchemaDumper.ignore_tables + ActiveRecord::Base.table_name_prefix = 'omg_' + ActiveRecord::SchemaDumper.ignore_tables = ["cats"] + migration = create_cat_migration.new + migration.migrate(:up) + + stream = StringIO.new + output = ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream).string + + assert_match %r{create_table "omg_cats"}, output + refute_match %r{create_table "cats"}, output + ensure + migration.migrate(:down) + ActiveRecord::Base.table_name_prefix = original_table_name_prefix + ActiveRecord::SchemaDumper.ignore_tables = original_schema_dumper_ignore_tables + + $stdout = original + end end class SchemaDumperDefaultsTest < ActiveRecord::TestCase diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index a6e83fe353..c1e491e5c5 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,6 +1,7 @@ class Bulb < ActiveRecord::Base default_scope { where(:name => 'defaulty') } belongs_to :car, :touch => true + scope :awesome, -> { where(frickinawesome: true) } attr_reader :scope_after_initialize, :attributes_after_initialize diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index 81263b79d1..778c22b1f6 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -4,6 +4,7 @@ class Car < ActiveRecord::Base has_many :funky_bulbs, class_name: 'FunkyBulb', dependent: :destroy has_many :failed_bulbs, class_name: 'FailedBulb', dependent: :destroy has_many :foo_bulbs, -> { where(:name => 'foo') }, :class_name => "Bulb" + has_many :awesome_bulbs, -> { awesome }, class_name: "Bulb" has_one :bulb diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 66a1f5aa8a..99098017d7 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -114,7 +114,7 @@ ActiveRecord::Schema.define do create_table :bulbs, force: true do |t| t.integer :car_id t.string :name - t.boolean :frickinawesome + t.boolean :frickinawesome, default: false t.string :color end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 9a00825247..d946fc1bbd 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* `ActiveSupport::Cache::Store#namespaced_key`, + `ActiveSupport::Cache::MemCachedStore#escape_key`, and + `ActiveSupport::Cache::FileStore#key_file_path` + are deprecated and replaced with `normalize_key` that now calls `super`. + + `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`. + + *Michael Grosser* + * Implements an evented file system monitor to asynchronously detect changes in the application source code, routes, locales, etc. @@ -9,7 +18,7 @@ *Puneet Agarwal* and *Xavier Noria* -* Added `Time#days_in_year` to return the number of days in the given year, or the +* Added `Time.days_in_year` to return the number of days in the given year, or the current year if no argument is provided. *Jon Pascoe* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 404c16b29f..32e28c0212 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -24,6 +24,6 @@ Gem::Specification.new do |s| s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' - s.add_dependency 'concurrent-ruby', '~> 1.0.0.pre5', '< 2.0.0' + s.add_dependency 'concurrent-ruby', '~> 1.0' s.add_dependency 'method_source' end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index d0e53eaf05..5011014e96 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/string/inflections' +require 'active_support/core_ext/string/strip' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -275,7 +276,7 @@ module ActiveSupport def fetch(name, options = nil) if block_given? options = merged_options(options) - key = namespaced_key(name, options) + key = normalize_key(name, options) instrument(:read, name, options) do |payload| cached_entry = read_entry(key, options) unless options[:force] @@ -302,7 +303,7 @@ module ActiveSupport # Options are passed to the underlying cache implementation. def read(name, options = nil) options = merged_options(options) - key = namespaced_key(name, options) + key = normalize_key(name, options) instrument(:read, name, options) do |payload| entry = read_entry(key, options) if entry @@ -334,7 +335,7 @@ module ActiveSupport instrument_multi(:read, names, options) do |payload| results = {} names.each do |name| - key = namespaced_key(name, options) + key = normalize_key(name, options) entry = read_entry(key, options) if entry if entry.expired? @@ -386,7 +387,7 @@ module ActiveSupport instrument(:write, name, options) do entry = Entry.new(value, options) - write_entry(namespaced_key(name, options), entry, options) + write_entry(normalize_key(name, options), entry, options) end end @@ -397,7 +398,7 @@ module ActiveSupport options = merged_options(options) instrument(:delete, name) do - delete_entry(namespaced_key(name, options), options) + delete_entry(normalize_key(name, options), options) end end @@ -408,7 +409,7 @@ module ActiveSupport options = merged_options(options) instrument(:exist?, name) do - entry = read_entry(namespaced_key(name, options), options) + entry = read_entry(normalize_key(name, options), options) (entry && !entry.expired?) || false end end @@ -529,7 +530,7 @@ module ActiveSupport # Prefix a key with the namespace. Namespace and key will be delimited # with a colon. - def namespaced_key(key, options) + def normalize_key(key, options) key = expanded_key(key) namespace = options[:namespace] if options prefix = namespace.is_a?(Proc) ? namespace.call : namespace @@ -537,8 +538,16 @@ module ActiveSupport key end + def namespaced_key(*args) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `namespaced_key` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key. + MESSAGE + normalize_key(*args) + end + def instrument(operation, key, options = nil) - log { "Cache #{operation}: #{namespaced_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } + log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } payload = { :key => key } payload.merge!(options) if options.is_a?(Hash) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 9a88fc286a..dff2443bc8 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -60,7 +60,7 @@ module ActiveSupport matcher = key_matcher(matcher, options) search_dir(cache_path) do |path| key = file_path_key(path) - delete_entry(key, options) if key.match(matcher) + delete_entry(path, options) if key.match(matcher) end end end @@ -68,9 +68,8 @@ module ActiveSupport protected def read_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) - File.open(file_name) { |f| Marshal.load(f) } + if File.exist?(key) + File.open(key) { |f| Marshal.load(f) } end rescue => e logger.error("FileStoreError (#{e}): #{e.message}") if logger @@ -78,23 +77,21 @@ module ActiveSupport end def write_entry(key, entry, options) - file_name = key_file_path(key) - return false if options[:unless_exist] && File.exist?(file_name) - ensure_cache_path(File.dirname(file_name)) - File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)} + return false if options[:unless_exist] && File.exist?(key) + ensure_cache_path(File.dirname(key)) + File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)} true end def delete_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) + if File.exist?(key) begin - File.delete(file_name) - delete_empty_directories(File.dirname(file_name)) + File.delete(key) + delete_empty_directories(File.dirname(key)) true rescue => e # Just in case the error was caused by another process deleting the file first. - raise e if File.exist?(file_name) + raise e if File.exist?(key) false end end @@ -118,7 +115,8 @@ module ActiveSupport end # Translate a key into a file path. - def key_file_path(key) + def normalize_key(key, options) + key = super fname = URI.encode_www_form_component(key) if fname.size > FILEPATH_MAX_SIZE @@ -139,6 +137,14 @@ module ActiveSupport File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) end + def key_file_path(key) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `key_file_path` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key or nothing. + MESSAGE + key + end + # Translate a file path into a key. def file_path_key(path) fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last @@ -175,7 +181,7 @@ module ActiveSupport # Modifies the amount of an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def modify_value(name, amount, options) - file_name = key_file_path(namespaced_key(name, options)) + file_name = normalize_key(name, options) lock_file(file_name) do options = merged_options(options) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index e2f536ef1e..4b0ad37586 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -97,7 +97,7 @@ module ActiveSupport options = merged_options(options) instrument_multi(:read, names, options) do - keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] + keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}] raw_values = @data.get_multi(keys_to_names.keys, :raw => true) values = {} raw_values.each do |key, value| @@ -115,7 +115,7 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:increment, name, :amount => amount) do - @data.incr(escape_key(namespaced_key(name, options)), amount) + @data.incr(normalize_key(name, options), amount) end rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger @@ -129,7 +129,7 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:decrement, name, :amount => amount) do - @data.decr(escape_key(namespaced_key(name, options)), amount) + @data.decr(normalize_key(name, options), amount) end rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger @@ -153,7 +153,7 @@ module ActiveSupport protected # Read an entry from the cache. def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(escape_key(key), options)) + deserialize_entry(@data.get(key, options)) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger nil @@ -168,7 +168,7 @@ module ActiveSupport # Set the memcache expire a few minutes in the future to support race condition ttls on read expires_in += 5.minutes end - @data.send(method, escape_key(key), value, expires_in, options) + @data.send(method, key, value, expires_in, options) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger false @@ -176,7 +176,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) # :nodoc: - @data.delete(escape_key(key)) + @data.delete(key) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger false @@ -187,14 +187,22 @@ module ActiveSupport # Memcache keys are binaries. So we need to force their encoding to binary # before applying the regular expression to ensure we are escaping all # characters properly. - def escape_key(key) - key = key.to_s.dup + def normalize_key(key, options) + key = super.dup key = key.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" } key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 key end + def escape_key(key) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `escape_key` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key or nothing. + MESSAGE + key + end + def deserialize_entry(raw_value) if raw_value entry = Marshal.load(raw_value) rescue raw_value diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index fa007aad56..df38dbcf11 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -93,14 +93,14 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: return super unless local_cache value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) + write_cache_value(name, value, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: return super unless local_cache value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) + write_cache_value(name, value, options) value end @@ -124,6 +124,15 @@ module ActiveSupport end def set_cache_value(value, name, amount, options) # :nodoc: + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `set_cache_value` is deprecated and will be removed from Rails 5.1. + Please use `write_cache_value` + MESSAGE + write_cache_value name, value, options + end + + def write_cache_value(name, value, options) # :nodoc: + name = normalize_key(name, options) cache = local_cache cache.mute do if value diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 3c4bfc5f1e..b0f9a8be34 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -3,7 +3,8 @@ require 'active_support/core_ext/module/reachable' class Class begin - ObjectSpace.each_object(Class.new) {} + # Test if this Ruby supports each_object against singleton_class + ObjectSpace.each_object(Numeric.singleton_class) {} def descendants # :nodoc: descendants = [] @@ -12,7 +13,7 @@ class Class end descendants end - rescue StandardError # JRuby + rescue StandardError # JRuby 9.0.4.0 and earlier def descendants # :nodoc: descendants = [] ObjectSpace.each_object(Class) do |k| diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index fc7531d088..8a74ad4d66 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -21,7 +21,7 @@ module Enumerable if block_given? map(&block).sum(identity) else - inject { |sum, element| sum + element } || identity + inject(:+) || identity end end diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb index 638458c6ce..bb0f26f874 100644 --- a/activesupport/lib/active_support/file_evented_update_checker.rb +++ b/activesupport/lib/active_support/file_evented_update_checker.rb @@ -1,6 +1,7 @@ require 'listen' require 'set' require 'pathname' +require 'concurrent/atomic/atomic_boolean' module ActiveSupport class FileEventedUpdateChecker #:nodoc: all @@ -14,7 +15,7 @@ module ActiveSupport end @block = block - @updated = false + @updated = Concurrent::AtomicBoolean.new(false) @lcsp = @ph.longest_common_subpath(@dirs.keys) if (dtw = directories_to_watch).any? @@ -23,13 +24,12 @@ module ActiveSupport end def updated? - @updated + @updated.true? end def execute + @updated.make_false @block.call - ensure - @updated = false end def execute_if_updated @@ -43,7 +43,7 @@ module ActiveSupport def changed(modified, added, removed) unless updated? - @updated = (modified + added + removed).any? { |f| watching?(f) } + @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } end end diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index 506dd950cb..a909a65bb6 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/delegation' + module ActiveSupport # This module is used to encapsulate access to thread local variables. # diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 3629f5e64b..7bef73136c 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -416,7 +416,7 @@ module CacheStoreBehavior def test_race_condition_protection_skipped_if_not_defined @cache.write('foo', 'bar') - time = @cache.send(:read_entry, 'foo', {}).expires_at + time = @cache.send(:read_entry, @cache.send(:normalize_key, 'foo', {}), {}).expires_at Time.stub(:now, Time.at(time)) do result = @cache.fetch('foo') do @@ -518,6 +518,12 @@ module CacheStoreBehavior ensure ActiveSupport::Notifications.unsubscribe "cache_read.active_support" end + + def test_can_call_deprecated_namesaced_key + assert_deprecated "`namespaced_key` is deprecated" do + @cache.send(:namespaced_key, 111, {}) + end + end end # https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters @@ -693,6 +699,15 @@ module LocalCacheBehavior app = @cache.middleware.new(app) app.call({}) end + + def test_can_call_deprecated_set_cache_value + @cache.with_local_cache do + assert_deprecated "`set_cache_value` is deprecated" do + @cache.send(:set_cache_value, 1, 'foo', :ignored, {}) + end + assert_equal 1, @cache.read('foo') + end + end end module AutoloadingCacheBehavior @@ -783,13 +798,13 @@ class FileStoreTest < ActiveSupport::TestCase end def test_key_transformation - key = @cache.send(:key_file_path, "views/index?id=1") + key = @cache.send(:normalize_key, "views/index?id=1", {}) assert_equal "views/index?id=1", @cache.send(:file_path_key, key) end def test_key_transformation_with_pathname FileUtils.touch(File.join(cache_dir, "foo")) - key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") + key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {}) assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end @@ -797,7 +812,7 @@ class FileStoreTest < ActiveSupport::TestCase # remain valid def test_filename_max_size key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}" - path = @cache.send(:key_file_path, key) + path = @cache.send(:normalize_key, key, {}) Dir::Tmpname.create(path) do |tmpname, n, opts| assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}" end @@ -807,7 +822,7 @@ class FileStoreTest < ActiveSupport::TestCase # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B def test_key_transformation_max_filename_size key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B" - path = @cache.send(:key_file_path, key) + path = @cache.send(:normalize_key, key, {}) assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE} assert_equal 'B', File.basename(path) end @@ -858,6 +873,12 @@ class FileStoreTest < ActiveSupport::TestCase @cache.write(1, nil) assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) end + + def test_can_call_deprecated_key_file_path + assert_deprecated "`key_file_path` is deprecated" do + assert_equal 111, @cache.send(:key_file_path, 111) + end + end end class MemoryStoreTest < ActiveSupport::TestCase @@ -1032,6 +1053,12 @@ class MemCacheStoreTest < ActiveSupport::TestCase value << 'bingo' assert_not_equal value, @cache.read('foo') end + + def test_can_call_deprecated_escape_key + assert_deprecated "`escape_key` is deprecated" do + assert_equal 111, @cache.send(:escape_key, 111) + end + end end class NullStoreTest < ActiveSupport::TestCase diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index b7a94f144c..7618fce2c8 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -162,7 +162,7 @@ module RailsGuides def select_only(guides) prefixes = ENV['ONLY'].split(",").map(&:strip) guides.select do |guide| - prefixes.any? { |p| guide.start_with?(p) || guide.start_with?("kindle") } + guide.start_with?('kindle'.freeze, *prefixes) end end diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 32926622e3..081afcb09f 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -27,7 +27,7 @@ module Kindle generate_document_metadata(mobi_outfile) - puts "Creating MOBI document with kindlegen. This make take a while." + puts "Creating MOBI document with kindlegen. This may take a while." cmd = "kindlerb . > #{File.absolute_path logfile} 2>&1" puts cmd system(cmd) diff --git a/guides/source/api_app.md b/guides/source/api_app.md index feaaff166a..fb3127555e 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -221,7 +221,7 @@ For instance, using the `stale?` method: ```ruby def show - @post = Post.find(params[:id]) + @post = Post.find(params[:id]) if stale?(last_modified: @post.updated_at) render json: @post @@ -240,7 +240,7 @@ cross-client caching in the call to `stale?`: ```ruby def show - @post = Post.find(params[:id]) + @post = Post.find(params[:id]) if stale?(last_modified: @post.updated_at, public: true) render json: @post diff --git a/guides/source/configuring.md b/guides/source/configuring.md index ee3f742367..dbb432db20 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -35,7 +35,7 @@ In general, the work of configuring Rails means configuring the components of Ra For example, the `config/application.rb` file includes this setting: ```ruby -config.autoload_paths += %W(#{config.root}/extras) +config.time_zone = 'Central Time (US & Canada)' ``` This is a setting for Rails itself. If you want to pass settings to individual Rails components, you can do so via the same `config` object in `config/application.rb`: @@ -199,7 +199,7 @@ The full set of methods that can be used in this block are as follows: Every Rails application comes with a standard set of middleware which it uses in this order in the development environment: * `ActionDispatch::SSL` forces every request to be under HTTPS protocol. Will be available if `config.force_ssl` is set to `true`. Options passed to this can be configured by using `config.ssl_options`. -* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.static_index` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.static_index` to `"main"`. +* `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`. * `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 53c3cbf80b..5885eb6e1c 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -148,6 +148,42 @@ NOTE: To help our CI servers you should add [ci skip] to your documentation comm WARNING: Docrails has a very strict policy: no code can be touched whatsoever, no matter how trivial or small the change. Only RDoc and guides can be edited via docrails. Also, CHANGELOGs should never be edited in docrails. +Translating Rails Guides +------------------------ + +We are happy to have people volunteer to translate the Rails guides into their own language. +If you want to translate the Rails guides in your own language, follows these steps: + +* Fork the project (rails/rails). +* Add a source folder for your own language, for example: *guides/source/it-IT* for Italian. +* Copy the contents of *guides/source* into your own language directory and translate them. +* Do NOT translate the HTML files, as they are automatically generated. + +To generate the guides in HTML format cd into the *guides* direcotry then run (eg. for it-IT): + +```bash +$ bundle install +$ bundle exec rake guides:generate:html GUIDES_LANGUAGE=it-IT +``` + +This will generate the guides in an *output* directory. + +NOTE: The instructions are for Rails > 4. The Redcarpet Gem doesn't work with JRuby. + +Translation efforts we know about (various versions): + +* **Italian**: [https://github.com/rixlabs/docrails](https://github.com/rixlabs/docrails) +* **Spanish**: [http://wiki.github.com/gramos/docrails](http://wiki.github.com/gramos/docrails) +* **Polish**: [http://github.com/apohllo/docrails/tree/master](http://github.com/apohllo/docrails/tree/master) +* **French** : [http://github.com/railsfrance/docrails](http://github.com/railsfrance/docrails) +* **Czech** : [https://github.com/rubyonrails-cz/docrails/tree/czech](https://github.com/rubyonrails-cz/docrails/tree/czech) +* **Turkish** : [https://github.com/ujk/docrails/tree/master](https://github.com/ujk/docrails/tree/master) +* **Korean** : [https://github.com/rorlakr/rails-guides](https://github.com/rorlakr/rails-guides) +* **Simplified Chinese** : [https://github.com/ruby-china/guides](https://github.com/ruby-china/guides) +* **Traditional Chinese** : [https://github.com/docrails-tw/guides](https://github.com/docrails-tw/guides) +* **Russian** : [https://github.com/morsbox/rusrails](https://github.com/morsbox/rusrails) +* **Japanese** : [https://github.com/yasslab/railsguides.jp](https://github.com/yasslab/railsguides.jp) + Contributing to the Rails Code ------------------------------ diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index a05abb61d6..5424313b33 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -862,8 +862,8 @@ such as Valgrind. ### Valgrind -[Valgrind](http://valgrind.org/) is a Linux-only application for detecting -C-based memory leaks and race conditions. +[Valgrind](http://valgrind.org/) is an application for detecting C-based memory +leaks and race conditions. There are Valgrind tools that can automatically detect many memory management and threading bugs, and profile your programs in detail. For example, if a C diff --git a/guides/source/plugins.md b/guides/source/plugins.md index b94c26a1ae..922bbb4f73 100644 --- a/guides/source/plugins.md +++ b/guides/source/plugins.md @@ -294,7 +294,7 @@ Getting closer... Now we will implement the code of the `acts_as_yaffle` method module Yaffle module ActsAsYaffle - extend ActiveSupport::Concern + extend ActiveSupport::Concern included do end diff --git a/guides/source/testing.md b/guides/source/testing.md index f63ea49955..3bfbf4f7ff 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -54,10 +54,12 @@ NOTE: Your tests are run under `RAILS_ENV=test`. ### Rails meets Minitest -If you remember when you used the `rails generate scaffold` command from the [Getting Started with Rails](getting_started.html) guide. We created our first resource among other things it created test stubs in the `test` directory: +If you remember when you used the `rails generate model` command from the +[Getting Started with Rails](getting_started.html) guide. We created our first +model among other things it created test stubs in the `test` directory: ```bash -$ bin/rails generate scaffold article title:string body:text +$ bin/rails generate model article title:string body:text ... create app/models/article.rb create test/models/article_test.rb @@ -649,14 +651,31 @@ You should test for things such as: * was the correct object stored in the response template? * was the appropriate message displayed to the user in the view? -Now that we have used Rails scaffold generator for our `Article` resource, it has already created the controller code and tests. You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory. +The easiest way to see functional tests in action is to generate a controller +scaffold: -The following command will generate a controller test case with a filled up -test for each of the seven default actions. +```bash +$ bin/rails generate scaffold_controller article title:string body:test +... +create app/controllers/articles_controller.rb +... +invoke test_unit +create test/controllers/articles_controller_test.rb +... +``` + +This will generate the controller code and tests for an `Article` resource. +You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory. + +If you already have a controller and just want to generate the test scaffold code for +each of the seven default actions, you can use the following command: ```bash $ bin/rails generate test_unit:scaffold article +... +invoke test_unit create test/controllers/articles_controller_test.rb +... ``` Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`. @@ -1253,4 +1272,3 @@ assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visib Please see [`ActiveSupport::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html) for in-depth information about the available time helpers. - diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 2fedeac292..aa12708669 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Make `static_index` part of the `config.public_file_server` config and + call it `public_file_server.index_name`. + + *Yuki Nishijima* + * Generated `Gemfile`s for new applications include a new dependency on [listen](https://github.com/guard/listen) commented out. diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 6d89caf514..80733c2d90 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -14,7 +14,7 @@ module Rails :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, :railties_order, :relative_url_root, :secret_key_base, :secret_token, - :ssl_options, :static_index, :public_file_server, + :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x @@ -29,9 +29,9 @@ module Rails @filter_parameters = [] @filter_redirect = [] @helpers_paths = [] - @static_index = "index" @public_file_server = ActiveSupport::OrderedOptions.new @public_file_server.enabled = true + @public_file_server.index_name = "index" @force_ssl = false @ssl_options = {} @session_store = :cookie_store diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index b0d481c21a..5cb5bfb8b7 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -21,7 +21,7 @@ module Rails headers = config.public_file_server.headers || {} headers['Cache-Control'.freeze] = config.static_cache_control if config.static_cache_control - middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.static_index, headers: headers + middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers end if rack_cache = load_rack_cache diff --git a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec index ff242adbab..3f5682b0c1 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin/templates/%name%.gemspec @@ -15,9 +15,6 @@ Gem::Specification.new do |s| s.license = "MIT" s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] -<% unless options.skip_test? -%> - s.test_files = Dir["test/**/*"] -<% end -%> <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" <% unless options[:skip_active_record] -%> diff --git a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb index 673de44108..694510edc0 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb +++ b/railties/lib/rails/generators/rails/plugin/templates/rails/routes.rb @@ -1,4 +1,3 @@ Rails.application.routes.draw do - mount <%= camelized_modules %>::Engine => "/<%= name %>" end diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake index e949172d3f..4593100465 100644 --- a/railties/lib/rails/tasks/dev.rake +++ b/railties/lib/rails/tasks/dev.rake @@ -1,7 +1,6 @@ namespace :dev do + desc 'Toggle development mode caching on/off' task :cache do - desc 'Toggle development mode caching on/off' - if File.exist? 'tmp/caching-dev.txt' File.delete 'tmp/caching-dev.txt' puts 'Development mode is no longer being cached.' diff --git a/railties/test/application/middleware/static_test.rb b/railties/test/application/middleware/static_test.rb index 5366537dc2..1246e20d94 100644 --- a/railties/test/application/middleware/static_test.rb +++ b/railties/test/application/middleware/static_test.rb @@ -44,7 +44,7 @@ module ApplicationTests assert_equal 'public, max-age=60', last_response.headers["Cache-Control"] end - test "static_index defaults to 'index'" do + test "public_file_server.index_name defaults to 'index'" do app_file "public/index.html", "/index.html" require "#{app_path}/config/environment" @@ -54,10 +54,10 @@ module ApplicationTests assert_equal "/index.html\n", last_response.body end - test "static_index configurable" do + test "public_file_server.index_name configurable" do app_file "public/other-index.html", "/other-index.html" - add_to_config "config.static_index = 'other-index'" - + add_to_config "config.public_file_server.index_name = 'other-index'" + require "#{app_path}/config/environment" get '/' |