diff options
96 files changed, 1519 insertions, 638 deletions
diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 3907e93097..8f74ac0928 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,6 @@ +## Rails 4.0.0 (unreleased) ## + + ## Rails 4.0.0.beta1 (February 25, 2013) ## * Allow passing interpolations to `#default_i18n_subject`, e.g.: diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 5197d944b1..3fc3e06160 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,44 @@ +## Rails 4.0.0 (unreleased) ## + +* Fixed `ActionView::Helpers::CaptureHelper#content_for` regression when trying to use it in + a boolean statement. + Fixes #9360. + + *Nikolay Shebanov* + +* `format: true` does not override existing format constraints. + Fixes #9466. + + Example: + + # This will force the .json extension. + get '/json_only', to: ok, format: true, constraints: { format: /json/ } + + *Yves Senn* + +* Skip valid encoding checks for non-String parameters that come + from the matched route's defaults. + Fixes #9435. + + Example: + + root to: 'main#posts', page: 1 + + *Yves Senn* + +* Don't verify Regexp requirements for non-Regexp `:constraints`. + Fixes #9432. + + Example: + + get '/photos.:format' => 'feeds#photos', constraints: {format: 'xml'} + + *Yves Senn* + +* Make `ActionDispatch::Journey::Path::Pattern#new` raise more meaningful exception message. + + *Thierry Zires* + ## Rails 4.0.0.beta1 (February 25, 2013) ## * Fix `respond_to` not using formats that have no block if all is present. *Michael Grosser* diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 4b984d0558..f8e4cb4384 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -97,15 +97,23 @@ module AbstractController self.response_body = render_to_body(options) end - # Raw rendering of a template to a string. Just convert the results of - # render_response into a String. + # Raw rendering of a template to a string. + # + # It is similar to render, except that it does not + # set the response_body and it should be guaranteed + # to always return a string. + # + # If a component extends the semantics of response_body + # (as Action Controller extends it to be anything that + # responds to the method each), this method needs to + # overriden in order to still return a string. # :api: plugin def render_to_string(*args, &block) options = _normalize_render(*args, &block) render_to_body(options) end - # Raw rendering of a template to a Rack-compatible body. + # Raw rendering of a template. # :api: plugin def render_to_body(options = {}) _process_options(options) diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index db48022b9f..02028d8e05 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -11,7 +11,7 @@ module AbstractController def translate(*args) key = args.first if key.is_a?(String) && (key[0] == '.') - key = "#{ controller_path.gsub('/', '.') }.#{ action_name }#{ key }" + key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }" args[0] = key end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 89a7b12818..40bb060d52 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -121,7 +121,7 @@ module ActionDispatch BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ def valid_accept_header - (xhr? && (accept || content_mime_type)) || + (xhr? && (accept.present? || content_mime_type)) || (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) end diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index 4a571ec546..d37aa1fbe5 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -20,7 +20,7 @@ module ActionDispatch @separators = strexp.separators.join @anchored = strexp.anchor else - raise "wtf bro: #{strexp}" + raise ArgumentError, "Bad expression: #{strexp}" end @names = nil diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 063302e0f2..6fda085681 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -71,6 +71,10 @@ module ActionDispatch Visitors::Formatter.new(path_options).accept(path.spec) end + def optimized_path + Visitors::OptimizedPath.new.accept(path.spec) + end + def optional_parts path.optional_names.map { |n| n.to_sym } end diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 46bd58c178..2964d80d9f 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -74,6 +74,14 @@ module ActionDispatch end end + class OptimizedPath < String # :nodoc: + private + + def visit_GROUP(node) + "" + end + end + # Used for formatting urls (url_for) class Formatter < Visitor # :nodoc: attr_reader :options, :consumed diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 550f4dbd0d..6b1f233930 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -10,7 +10,7 @@ clean_params.delete("action") clean_params.delete("controller") - request_dump = clean_params.empty? ? 'None' : clean_params.inspect.gsub(',', ",\n") + request_dump = clean_params.empty? ? 'None' : clean_params.inspect.tr(',', ",\n") def debug_hash(object) object.to_hash.sort_by { |k, v| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") @@ -31,4 +31,4 @@ </div> <h2 style="margin-top: 30px">Response</h2> -<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre> +<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.tr(',', ",\n") : 'None' %></pre> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index 61690d3e50..cd3daff065 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -8,7 +8,7 @@ <h2>Failure reasons:</h2> <ol> <% @exception.failures.each do |route, reason| %> - <li><code><%= route.inspect.gsub('\\', '') %></code> failed because <%= reason.downcase %></li> + <li><code><%= route.inspect.delete('\\') %></code> failed because <%= reason.downcase %></li> <% end %> </ol> </p> diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 817480c7ae..c5f2b33602 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -113,31 +113,15 @@ module ActionDispatch @options.merge!(default_controller_and_action) end - def normalize_format! - if options[:format] == true - options[:format] = /.+/ - elsif options[:format] == false - options.delete(:format) - end - end - def normalize_requirements! constraints.each do |key, requirement| next unless segment_keys.include?(key) || key == :controller - - if requirement.source =~ ANCHOR_CHARACTERS_REGEX - raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" - end - - if requirement.multiline? - raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" - end - + verify_regexp_requirement(requirement) if requirement.is_a?(Regexp) @requirements[key] = requirement end if options[:format] == true - @requirements[:format] = /.+/ + @requirements[:format] ||= /.+/ elsif Regexp === options[:format] @requirements[:format] = options[:format] elsif String === options[:format] @@ -145,6 +129,16 @@ module ActionDispatch end end + def verify_regexp_requirement(requirement) + if requirement.source =~ ANCHOR_CHARACTERS_REGEX + raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}" + end + + if requirement.multiline? + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" + end + end + def normalize_defaults! @defaults.merge!(scope[:defaults]) if scope[:defaults] @defaults.merge!(options[:defaults]) if options[:defaults] @@ -187,7 +181,8 @@ module ActionDispatch if !via_all && options[:via].blank? msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \ - "If you want to expose your action to GET, use `get` in the router:\n\n" \ + "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \ + "If you want to expose your action to GET, use `get` in the router:\n" \ " Instead of: match \"controller#action\"\n" \ " Do: get \"controller#action\"" raise msg @@ -425,11 +420,15 @@ module ActionDispatch # end # # [:constraints] - # Constrains parameters with a hash of regular expressions or an - # object that responds to <tt>matches?</tt> + # Constrains parameters with a hash of regular expressions + # or an object that responds to <tt>matches?</tt>. In addition, constraints + # other than path can also be specified with any object + # that responds to <tt>===</tt> (eg. String, Array, Range, etc.). # # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ } # + # match 'json_only', constraints: { format: 'json' } + # # class Blacklist # def matches?(request) request.remote_ip == '1.2.3.4' end # end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ff86f87d49..619dd22ec1 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -31,6 +31,8 @@ module ActionDispatch # If any of the path parameters has a invalid encoding then # raise since it's likely to trigger errors further on. params.each do |key, value| + next unless value.respond_to?(:valid_encoding?) + unless value.valid_encoding? raise ActionController::BadRequest, "Invalid parameter: #{key} => #{value}" end @@ -163,7 +165,7 @@ module ActionDispatch super @path_parts = @route.required_parts @arg_size = @path_parts.size - @string_route = string_route(route) + @string_route = @route.optimized_path end def call(t, args) @@ -178,14 +180,6 @@ module ActionDispatch private - def string_route(route) - string_route = route.ast.to_s.dup - while string_route.gsub!(/\([^\)]*\)/, "") - true - end - string_route - end - def optimized_helper(args) path = @string_route.dup klass = Journey::Router::Utils diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionpack/lib/action_view/dependency_tracker.rb index 3de5cd150b..a2a555dfcb 100644 --- a/actionpack/lib/action_view/dependency_tracker.rb +++ b/actionpack/lib/action_view/dependency_tracker.rb @@ -54,8 +54,10 @@ module ActionView render_dependencies + explicit_dependencies end + attr_reader :name, :template + private :name, :template + private - attr_reader :name, :template def source template.source diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 4ec860d69a..1bad82159a 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -156,7 +156,7 @@ module ActionView end nil else - @view_flow.get(name) + @view_flow.get(name).presence end end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index c29c1b1eea..db7f9f3494 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -28,7 +28,7 @@ module ActionView # </pre> def debug(object) Marshal::dump(object) - object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe + object = ERB::Util.html_escape(object.to_yaml).tr(" ", " ").html_safe content_tag(:pre, object, :class => "debug_dump") rescue Exception # errors from Marshal or YAML # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 1adc8225f1..8abd5d6e9c 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -778,7 +778,7 @@ module ActionView # see http://www.w3.org/TR/html4/types.html#type-name def sanitize_to_id(name) - name.to_s.gsub(']','').gsub(/[^-a-zA-Z0-9:.]/, "_") + name.to_s.delete(']').gsub(/[^-a-zA-Z0-9:.]/, "_") end end end diff --git a/actionpack/test/activerecord/controller_runtime_test.rb b/actionpack/test/activerecord/controller_runtime_test.rb index 1df826fe2b..368bec1c70 100644 --- a/actionpack/test/activerecord/controller_runtime_test.rb +++ b/actionpack/test/activerecord/controller_runtime_test.rb @@ -8,6 +8,8 @@ ActionController::Base.send :include, ActiveRecord::Railties::ControllerRuntime class ControllerRuntimeLogSubscriberTest < ActionController::TestCase class LogSubscriberController < ActionController::Base + respond_to :html + def show render :inline => "<%= Project.all %>" end @@ -16,6 +18,12 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase render :inline => "Zero DB runtime" end + def create + ActiveRecord::LogSubscriber.runtime += 100 + project = Project.last + respond_with(project, location: url_for(action: :show)) + end + def redirect Project.all redirect_to :action => 'show' @@ -64,6 +72,12 @@ class ControllerRuntimeLogSubscriberTest < ActionController::TestCase assert_match(/\(Views: [\d.]+ms \| ActiveRecord: 0.0ms\)/, @logger.logged(:info)[1]) end + def test_log_with_active_record_when_post + post :create + wait + assert_match(/ActiveRecord: ([1-9][\d.]+)ms\)/, @logger.logged(:info)[2]) + end + def test_log_with_active_record_when_redirecting get :redirect wait diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 5e821046db..93e94f0f48 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -456,6 +456,22 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_with_hash + rs.draw do + root "hello#index", as: :index + end + + routes = setup_for_named_route + assert_equal("http://test.host/", routes.send(:index_url)) + assert_equal("/", routes.send(:index_path)) + end + + def test_root_without_path_raises_argument_error + assert_raises ArgumentError do + rs.draw { root nil } + end + end + def test_named_route_root_with_trailing_slash rs.draw do root "hello#index" diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 91810864d5..f6de9748ca 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -594,6 +594,11 @@ class RequestTest < ActiveSupport::TestCase request.expects(:parameters).at_least_once.returns({}) assert_equal [Mime::HTML], request.formats + request = stub_request 'HTTP_ACCEPT' => '', + 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" + request.expects(:parameters).at_least_once.returns({}) + assert_equal [Mime::JS], request.formats + request = stub_request 'CONTENT_TYPE' => 'application/xml; charset=UTF-8', 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 37ad9ddb6b..2bf7056ff7 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1346,7 +1346,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'en', @request.params[:locale] end - def test_default_params + def test_default_string_params draw do get 'inline_pages/(:id)', :to => 'pages#show', :id => 'home' get 'default_pages/(:id)', :to => 'pages#show', :defaults => { :id => 'home' } @@ -1366,6 +1366,26 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal 'home', @request.params[:id] end + def test_default_integer_params + draw do + get 'inline_pages/(:page)', to: 'pages#show', page: 1 + get 'default_pages/(:page)', to: 'pages#show', defaults: { page: 1 } + + defaults page: 1 do + get 'scoped_pages/(:page)', to: 'pages#show' + end + end + + get '/inline_pages' + assert_equal 1, @request.params[:page] + + get '/default_pages' + assert_equal 1, @request.params[:page] + + get '/scoped_pages' + assert_equal 1, @request.params[:page] + end + def test_resource_constraints draw do resources :products, :constraints => { :id => /\d{4}/ } do @@ -3178,6 +3198,7 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest app.draw do ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } get '/foo' => ok, as: :foo + get '/post(/:action(/:id))' => ok, as: :posts end end @@ -3195,6 +3216,11 @@ class TestOptimizedNamedRoutes < ActionDispatch::IntegrationTest test 'named route called on included module' do assert_equal '/foo', foo_path end + + test 'nested optional segments are removed' do + assert_equal '/post', Routes.url_helpers.posts_path + assert_equal '/post', posts_path + end end class TestNamedRouteUrlHelpers < ActionDispatch::IntegrationTest @@ -3380,6 +3406,66 @@ class TestPortConstraints < ActionDispatch::IntegrationTest end end +class TestFormatConstraints < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + + get '/string', to: ok, constraints: { format: 'json' } + get '/regexp', to: ok, constraints: { format: /json/ } + get '/json_only', to: ok, format: true, constraints: { format: /json/ } + get '/xml_only', to: ok, format: 'xml' + end + end + + include Routes.url_helpers + def app; Routes end + + def test_string_format_constraints + get 'http://www.example.com/string' + assert_response :success + + get 'http://www.example.com/string.json' + assert_response :success + + get 'http://www.example.com/string.html' + assert_response :not_found + end + + def test_regexp_format_constraints + get 'http://www.example.com/regexp' + assert_response :success + + get 'http://www.example.com/regexp.json' + assert_response :success + + get 'http://www.example.com/regexp.html' + assert_response :not_found + end + + def test_enforce_with_format_true_with_constraint + get 'http://www.example.com/json_only.json' + assert_response :success + + get 'http://www.example.com/json_only.html' + assert_response :not_found + + get 'http://www.example.com/json_only' + assert_response :not_found + end + + def test_enforce_with_string + get 'http://www.example.com/xml_only.xml' + assert_response :success + + get 'http://www.example.com/xml_only' + assert_response :success + + get 'http://www.example.com/xml_only.json' + assert_response :not_found + end +end + class TestRouteDefaults < ActionDispatch::IntegrationTest stub_controllers do |routes| Routes = routes diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index 2b7227cd0d..ce02104181 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -75,6 +75,10 @@ module ActionDispatch end end + def test_to_raise_exception_with_bad_expression + assert_raise(ArgumentError, "Bad expression: []") { Pattern.new [] } + end + def test_to_regexp_with_extended_group strexp = Router::Strexp.new( '/page/:name', diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 234ac3252d..938f1c3e54 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -137,6 +137,10 @@ class CaptureHelperTest < ActionView::TestCase assert_equal 'bar', content_for(:title) end + def test_content_for_returns_nil_when_content_missing + assert_equal nil, content_for(:some_missing_key) + end + def test_content_for_question_mark assert ! content_for?(:title) content_for :title, 'title' diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index d8fffe75ed..6e640889d2 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -1,323 +1,75 @@ -require 'abstract_unit' +require "abstract_unit" class NumberHelperTest < ActionView::TestCase tests ActionView::Helpers::NumberHelper - def kilobytes(number) - number * 1024 - end - - def megabytes(number) - kilobytes(number) * 1024 - end - - def gigabytes(number) - megabytes(number) * 1024 - end - - def terabytes(number) - gigabytes(number) * 1024 - end - def test_number_to_phone - assert_equal("555-1234", number_to_phone(5551234)) - assert_equal("800-555-1212", number_to_phone(8005551212)) - assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) - assert_equal("", number_to_phone("", {:area_code => true})) - assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) - assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) - assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) - assert_equal("555.1212", number_to_phone(5551212, :delimiter => '.')) - assert_equal("800-555-1212", number_to_phone("8005551212")) - assert_equal("+1-800-555-1212", number_to_phone(8005551212, :country_code => 1)) - assert_equal("+18005551212", number_to_phone(8005551212, :country_code => 1, :delimiter => '')) - assert_equal("22-555-1212", number_to_phone(225551212)) - assert_equal("+45-22-555-1212", number_to_phone(225551212, :country_code => 45)) - assert_equal '111<script></script>111<script></script>1111', number_to_phone(1111111111, :delimiter => "<script></script>") + assert_equal nil, number_to_phone(nil) + assert_equal "555-1234", number_to_phone(5551234) + assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123) + assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "") end def test_number_to_currency - assert_equal("$1,234,567,890.50", number_to_currency(1234567890.50)) - assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506)) - assert_equal("-$1,234,567,890.50", number_to_currency(-1234567890.50)) - assert_equal("-$ 1,234,567,890.50", number_to_currency(-1234567890.50, {:format => "%u %n"})) - assert_equal("($1,234,567,890.50)", number_to_currency(-1234567890.50, {:negative_format => "(%u%n)"})) - assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0})) - assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1})) - assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""})) - assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50")) - assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"})) - assert_equal("1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", {:unit => "Kč", :format => "%n %u", :negative_format => "%n - %u"})) - assert_equal '$1<script></script>01', number_to_currency(1.01, :separator => "<script></script>") - assert_equal '$1<script></script>000.00', number_to_currency(1000, :delimiter => "<script></script>") + assert_equal nil, number_to_currency(nil) + assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50) + assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0) + assert_equal "1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", unit: "Kč", format: "%n %u", negative_format: "%n - %u") end def test_number_to_percentage - assert_equal("100.000%", number_to_percentage(100)) - assert_equal("100%", number_to_percentage(100, {:precision => 0})) - assert_equal("302.06%", number_to_percentage(302.0574, {:precision => 2})) - assert_equal("100.000%", number_to_percentage("100")) - assert_equal("1000.000%", number_to_percentage("1000")) - assert_equal("123.4%", number_to_percentage(123.400, :precision => 3, :strip_insignificant_zeros => true)) - assert_equal("1.000,000%", number_to_percentage(1000, :delimiter => '.', :separator => ',')) - assert_equal("1000.000 %", number_to_percentage(1000, :format => "%n %")) - assert_equal '1<script></script>010%', number_to_percentage(1.01, :separator => "<script></script>") - assert_equal '1<script></script>000.000%', number_to_percentage(1000, :delimiter => "<script></script>") + assert_equal nil, number_to_percentage(nil) + assert_equal "100.000%", number_to_percentage(100) + assert_equal "100%", number_to_percentage(100, precision: 0) + assert_equal "123.4%", number_to_percentage(123.400, precision: 3, strip_insignificant_zeros: true) + assert_equal "1.000,000%", number_to_percentage(1000, delimiter: ".", separator: ",") end def test_number_with_delimiter - assert_equal("12,345,678", number_with_delimiter(12345678)) - assert_equal("0", number_with_delimiter(0)) - assert_equal("123", number_with_delimiter(123)) - assert_equal("123,456", number_with_delimiter(123456)) - assert_equal("123,456.78", number_with_delimiter(123456.78)) - assert_equal("123,456.789", number_with_delimiter(123456.789)) - assert_equal("123,456.78901", number_with_delimiter(123456.78901)) - assert_equal("123,456,789.78901", number_with_delimiter(123456789.78901)) - assert_equal("0.78901", number_with_delimiter(0.78901)) - assert_equal("123,456.78", number_with_delimiter("123456.78")) - end - - def test_number_with_delimiter_with_options_hash - assert_equal '12 345 678', number_with_delimiter(12345678, :delimiter => ' ') - assert_equal '12,345,678-05', number_with_delimiter(12345678.05, :separator => '-') - assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :separator => ',', :delimiter => '.') - assert_equal '12.345.678,05', number_with_delimiter(12345678.05, :delimiter => '.', :separator => ',') - assert_equal '1<script></script>01', number_with_delimiter(1.01, :separator => "<script></script>") - assert_equal '1<script></script>000', number_with_delimiter(1000, :delimiter => "<script></script>") + assert_equal nil, number_with_delimiter(nil) + assert_equal "12,345,678", number_with_delimiter(12345678) + assert_equal "0", number_with_delimiter(0) end def test_number_with_precision - assert_equal("-111.235", number_with_precision(-111.2346)) - assert_equal("111.235", number_with_precision(111.2346)) - assert_equal("31.83", number_with_precision(31.825, :precision => 2)) - assert_equal("111.23", number_with_precision(111.2346, :precision => 2)) - assert_equal("111.00", number_with_precision(111, :precision => 2)) - assert_equal("111.235", number_with_precision("111.2346")) - assert_equal("31.83", number_with_precision("31.825", :precision => 2)) - assert_equal("3268", number_with_precision((32.6751 * 100.00), :precision => 0)) - assert_equal("112", number_with_precision(111.50, :precision => 0)) - assert_equal("1234567892", number_with_precision(1234567891.50, :precision => 0)) - assert_equal("0", number_with_precision(0, :precision => 0)) - assert_equal("0.00100", number_with_precision(0.001, :precision => 5)) - assert_equal("0.001", number_with_precision(0.00111, :precision => 3)) - assert_equal("10.00", number_with_precision(9.995, :precision => 2)) - assert_equal("11.00", number_with_precision(10.995, :precision => 2)) - assert_equal("0.00", number_with_precision(-0.001, :precision => 2)) - end - - def test_number_with_precision_with_custom_delimiter_and_separator - assert_equal '31,83', number_with_precision(31.825, :precision => 2, :separator => ',') - assert_equal '1.231,83', number_with_precision(1231.825, :precision => 2, :separator => ',', :delimiter => '.') - assert_equal '1<script></script>010', number_with_precision(1.01, :separator => "<script></script>") - assert_equal '1<script></script>000.000', number_with_precision(1000, :delimiter => "<script></script>") - end - - def test_number_with_precision_with_significant_digits - assert_equal "124000", number_with_precision(123987, :precision => 3, :significant => true) - assert_equal "120000000", number_with_precision(123987876, :precision => 2, :significant => true ) - assert_equal "40000", number_with_precision("43523", :precision => 1, :significant => true ) - assert_equal "9775", number_with_precision(9775, :precision => 4, :significant => true ) - assert_equal "5.4", number_with_precision(5.3923, :precision => 2, :significant => true ) - assert_equal "5", number_with_precision(5.3923, :precision => 1, :significant => true ) - assert_equal "1", number_with_precision(1.232, :precision => 1, :significant => true ) - assert_equal "7", number_with_precision(7, :precision => 1, :significant => true ) - assert_equal "1", number_with_precision(1, :precision => 1, :significant => true ) - assert_equal "53", number_with_precision(52.7923, :precision => 2, :significant => true ) - assert_equal "9775.00", number_with_precision(9775, :precision => 6, :significant => true ) - assert_equal "5.392900", number_with_precision(5.3929, :precision => 7, :significant => true ) - assert_equal "0.0", number_with_precision(0, :precision => 2, :significant => true ) - assert_equal "0", number_with_precision(0, :precision => 1, :significant => true ) - assert_equal "0.0001", number_with_precision(0.0001, :precision => 1, :significant => true ) - assert_equal "0.000100", number_with_precision(0.0001, :precision => 3, :significant => true ) - assert_equal "0.0001", number_with_precision(0.0001111, :precision => 1, :significant => true ) - assert_equal "10.0", number_with_precision(9.995, :precision => 3, :significant => true) - assert_equal "9.99", number_with_precision(9.994, :precision => 3, :significant => true) - assert_equal "11.0", number_with_precision(10.995, :precision => 3, :significant => true) - end - - def test_number_with_precision_with_strip_insignificant_zeros - assert_equal "9775.43", number_with_precision(9775.43, :precision => 4, :strip_insignificant_zeros => true ) - assert_equal "9775.2", number_with_precision(9775.2, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - assert_equal "0", number_with_precision(0, :precision => 6, :significant => true, :strip_insignificant_zeros => true ) - end - - def test_number_with_precision_with_significant_true_and_zero_precision - # Zero precision with significant is a mistake (would always return zero), - # so we treat it as if significant was false (increases backwards compatibility for number_to_human_size) - assert_equal "124", number_with_precision(123.987, :precision => 0, :significant => true) - assert_equal "12", number_with_precision(12, :precision => 0, :significant => true ) - assert_equal "12", number_with_precision("12.3", :precision => 0, :significant => true ) + assert_equal nil, number_with_precision(nil) + assert_equal "-111.235", number_with_precision(-111.2346) + assert_equal "111.00", number_with_precision(111, precision: 2) + assert_equal "0.00100", number_with_precision(0.001, precision: 5) end def test_number_to_human_size - assert_equal '0 Bytes', number_to_human_size(0) - assert_equal '1 Byte', number_to_human_size(1) - assert_equal '3 Bytes', number_to_human_size(3.14159265) - assert_equal '123 Bytes', number_to_human_size(123.0) - assert_equal '123 Bytes', number_to_human_size(123) - assert_equal '1.21 KB', number_to_human_size(1234) - assert_equal '12.1 KB', number_to_human_size(12345) - assert_equal '1.18 MB', number_to_human_size(1234567) - assert_equal '1.15 GB', number_to_human_size(1234567890) - assert_equal '1.12 TB', number_to_human_size(1234567890123) - assert_equal '1030 TB', number_to_human_size(terabytes(1026)) - assert_equal '444 KB', number_to_human_size(kilobytes(444)) - assert_equal '1020 MB', number_to_human_size(megabytes(1023)) - assert_equal '3 TB', number_to_human_size(terabytes(3)) - assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) - assert_equal '123 Bytes', number_to_human_size('123') - assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 Byte', number_to_human_size(1.1) - assert_equal '10 Bytes', number_to_human_size(10) - end - - def test_number_to_human_size_with_si_prefix - assert_equal '3 Bytes', number_to_human_size(3.14159265, :prefix => :si) - assert_equal '123 Bytes', number_to_human_size(123.0, :prefix => :si) - assert_equal '123 Bytes', number_to_human_size(123, :prefix => :si) - assert_equal '1.23 KB', number_to_human_size(1234, :prefix => :si) - assert_equal '12.3 KB', number_to_human_size(12345, :prefix => :si) - assert_equal '1.23 MB', number_to_human_size(1234567, :prefix => :si) - assert_equal '1.23 GB', number_to_human_size(1234567890, :prefix => :si) - assert_equal '1.23 TB', number_to_human_size(1234567890123, :prefix => :si) - end - - def test_number_to_human_size_with_options_hash - assert_equal '1.2 MB', number_to_human_size(1234567, :precision => 2) - assert_equal '3 Bytes', number_to_human_size(3.14159265, :precision => 4) - assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 2) - assert_equal '1.01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4) - assert_equal '10 KB', number_to_human_size(kilobytes(10.000), :precision => 4) - assert_equal '1 TB', number_to_human_size(1234567890123, :precision => 1) - assert_equal '500 MB', number_to_human_size(524288000, :precision=>3) - assert_equal '10 MB', number_to_human_size(9961472, :precision=>0) - assert_equal '40 KB', number_to_human_size(41010, :precision => 1) - assert_equal '40 KB', number_to_human_size(41100, :precision => 2) - assert_equal '1.0 KB', number_to_human_size(kilobytes(1.0123), :precision => 2, :strip_insignificant_zeros => false) - assert_equal '1.012 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :significant => false) - assert_equal '1 KB', number_to_human_size(kilobytes(1.0123), :precision => 0, :significant => true) #ignores significant it precision is 0 - assert_equal '9<script></script>86 KB', number_to_human_size(10100, :separator => "<script></script>") - end - - def test_number_to_human_size_with_custom_delimiter_and_separator - assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0123), :precision => 3, :separator => ',') - assert_equal '1,01 KB', number_to_human_size(kilobytes(1.0100), :precision => 4, :separator => ',') - assert_equal '1.000,1 TB', number_to_human_size(terabytes(1000.1), :precision => 5, :delimiter => '.', :separator => ',') + assert_equal nil, number_to_human_size(nil) + assert_equal "3 Bytes", number_to_human_size(3.14159265) + assert_equal "1.2 MB", number_to_human_size(1234567, precision: 2) end def test_number_to_human - assert_equal '-123', number_to_human(-123) - assert_equal '-0.5', number_to_human(-0.5) - assert_equal '0', number_to_human(0) - assert_equal '0.5', number_to_human(0.5) - assert_equal '123', number_to_human(123) - assert_equal '1.23 Thousand', number_to_human(1234) - assert_equal '12.3 Thousand', number_to_human(12345) - assert_equal '1.23 Million', number_to_human(1234567) - assert_equal '1.23 Billion', number_to_human(1234567890) - assert_equal '1.23 Trillion', number_to_human(1234567890123) - assert_equal '1.23 Quadrillion', number_to_human(1234567890123456) - assert_equal '1230 Quadrillion', number_to_human(1234567890123456789) - assert_equal '490 Thousand', number_to_human(489939, :precision => 2) - assert_equal '489.9 Thousand', number_to_human(489939, :precision => 4) - assert_equal '489 Thousand', number_to_human(489000, :precision => 4) - assert_equal '489.0 Thousand', number_to_human(489000, :precision => 4, :strip_insignificant_zeros => false) - assert_equal '1.2346 Million', number_to_human(1234567, :precision => 4, :significant => false) - assert_equal '1,2 Million', number_to_human(1234567, :precision => 1, :significant => false, :separator => ',') - assert_equal '1 Million', number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false - end - - def test_number_to_human_with_custom_units - #Only integers - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123 lt', number_to_human(123456, :units => volume) - assert_equal '12 ml', number_to_human(12, :units => volume) - assert_equal '1.23 m3', number_to_human(1234567, :units => volume) - - #Including fractionals - distance = {:mili => "mm", :centi => "cm", :deci => "dm", :unit => "m", :ten => "dam", :hundred => "hm", :thousand => "km"} - assert_equal '1.23 mm', number_to_human(0.00123, :units => distance) - assert_equal '1.23 cm', number_to_human(0.0123, :units => distance) - assert_equal '1.23 dm', number_to_human(0.123, :units => distance) - assert_equal '1.23 m', number_to_human(1.23, :units => distance) - assert_equal '1.23 dam', number_to_human(12.3, :units => distance) - assert_equal '1.23 hm', number_to_human(123, :units => distance) - assert_equal '1.23 km', number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_to_human(1230, :units => distance) - assert_equal '1.23 km', number_to_human(1230, :units => distance) - assert_equal '12.3 km', number_to_human(12300, :units => distance) - - #The quantifiers don't need to be a continuous sequence - gangster = {:hundred => "hundred bucks", :million => "thousand quids"} - assert_equal '1 hundred bucks', number_to_human(100, :units => gangster) - assert_equal '25 hundred bucks', number_to_human(2500, :units => gangster) - assert_equal '25 thousand quids', number_to_human(25000000, :units => gangster) - assert_equal '12300 thousand quids', number_to_human(12345000000, :units => gangster) - - #Spaces are stripped from the resulting string - assert_equal '4', number_to_human(4, :units => {:unit => "", :ten => 'tens '}) - assert_equal '4.5 tens', number_to_human(45, :units => {:unit => "", :ten => ' tens '}) - - assert_equal '1<script></script>01', number_to_human(1.01, :separator => "<script></script>") - assert_equal '100<script></script>000 Quadrillion', number_to_human(10**20, :delimiter => "<script></script>") + assert_equal nil, number_to_human(nil) + assert_equal "0", number_to_human(0) + assert_equal "1.23 Thousand", number_to_human(1234) + assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false) end - def test_number_to_human_with_custom_format - assert_equal '123 times Thousand', number_to_human(123456, :format => "%n times %u") - volume = {:unit => "ml", :thousand => "lt", :million => "m3"} - assert_equal '123.lt', number_to_human(123456, :units => volume, :format => "%n.%u") - end - - def test_number_helpers_should_return_nil_when_given_nil - assert_nil number_to_phone(nil) - assert_nil number_to_currency(nil) - assert_nil number_to_percentage(nil) - assert_nil number_with_delimiter(nil) - assert_nil number_with_precision(nil) - assert_nil number_to_human_size(nil) - assert_nil number_to_human(nil) - end + def test_number_helpers_escape_delimiter_and_separator + assert_equal "111<script></script>111<script></script>1111", number_to_phone(1111111111, delimiter: "<script></script>") - def test_number_helpers_do_not_mutate_options_hash - options = { 'raise' => true } + assert_equal "$1<script></script>01", number_to_currency(1.01, separator: "<script></script>") + assert_equal "$1<script></script>000.00", number_to_currency(1000, delimiter: "<script></script>") - number_to_phone(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal "1<script></script>010%", number_to_percentage(1.01, separator: "<script></script>") + assert_equal "1<script></script>000.000%", number_to_percentage(1000, delimiter: "<script></script>") - number_to_currency(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal "1<script></script>01", number_with_delimiter(1.01, separator: "<script></script>") + assert_equal "1<script></script>000", number_with_delimiter(1000, delimiter: "<script></script>") - number_to_percentage(1, options) - assert_equal({ 'raise' => true }, options) + assert_equal "1<script></script>010", number_with_precision(1.01, separator: "<script></script>") + assert_equal "1<script></script>000.000", number_with_precision(1000, delimiter: "<script></script>") - number_with_delimiter(1, options) - assert_equal({ 'raise' => true }, options) - - number_with_precision(1, options) - assert_equal({ 'raise' => true }, options) - - number_to_human_size(1, options) - assert_equal({ 'raise' => true }, options) - - number_to_human(1, options) - assert_equal({ 'raise' => true }, options) - end + assert_equal "9<script></script>86 KB", number_to_human_size(10100, separator: "<script></script>") - def test_number_helpers_should_return_non_numeric_param_unchanged - assert_equal("+1-x x 123", number_to_phone("x", :country_code => 1, :extension => 123)) - assert_equal("x", number_to_phone("x")) - assert_equal("$x.", number_to_currency("x.")) - assert_equal("$x", number_to_currency("x")) - assert_equal("x%", number_to_percentage("x")) - assert_equal("x", number_with_delimiter("x")) - assert_equal("x.", number_with_precision("x.")) - assert_equal("x", number_with_precision("x")) - assert_equal "x", number_to_human_size('x') - assert_equal "x", number_to_human('x') + assert_equal "1<script></script>01", number_to_human(1.01, separator: "<script></script>") + assert_equal "100<script></script>000 Quadrillion", number_to_human(10**20, delimiter: "<script></script>") end def test_number_helpers_outputs_are_html_safe @@ -332,8 +84,8 @@ class NumberHelperTest < ActionView::TestCase assert number_to_human_size("asdf".html_safe).html_safe? assert number_to_human_size("1".html_safe).html_safe? - assert number_with_precision(1, :strip_insignificant_zeros => false).html_safe? - assert number_with_precision(1, :strip_insignificant_zeros => true).html_safe? + assert number_with_precision(1, strip_insignificant_zeros: false).html_safe? + assert number_with_precision(1, strip_insignificant_zeros: true).html_safe? assert !number_with_precision("<script></script>").html_safe? assert number_with_precision("asdf".html_safe).html_safe? assert number_with_precision("1".html_safe).html_safe? @@ -362,37 +114,37 @@ class NumberHelperTest < ActionView::TestCase def test_number_helpers_should_raise_error_if_invalid_when_specified exception = assert_raise InvalidNumberError do - number_to_human("x", :raise => true) + number_to_human("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_human_size("x", :raise => true) + number_to_human_size("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_with_precision("x", :raise => true) + number_with_precision("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_currency("x", :raise => true) + number_to_currency("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_percentage("x", :raise => true) + number_to_percentage("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_with_delimiter("x", :raise => true) + number_with_delimiter("x", raise: true) end assert_equal "x", exception.number exception = assert_raise InvalidNumberError do - number_to_phone("x", :raise => true) + number_to_phone("x", raise: true) end assert_equal "x", exception.number end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 227374b331..1fe6dbd4d9 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,6 @@ +## Rails 4.0.0 (unreleased) ## + + ## Rails 4.0.0.beta1 (February 25, 2013) ## * Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 3fbf043c7c..97616ffc58 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,8 +1,88 @@ -## Rails 4.0.0.beta1 (February 25, 2013) ## +## Rails 4.0.0 (unreleased) ## -* Fix overriding of attributes by default_scope on `ActiveRecord::Base#dup`. +* Assigning "0.0" to a nullable numeric column does not make it dirty. + Fix #9034. - *Hiroshige UMINO* + Example: + + product = Product.create price: 0.0 + product.price = '0.0' + product.changed? # => false (this used to return true) + product.changes # => {} (this used to return { price: [0.0, 0.0] }) + + *Yves Senn* + +* Added functionality to unscope relations in a relations chain. For + instance, if you are passed in a chain of relations as follows: + + User.where(name: "John").order('id DESC') + + but you want to get rid of order, then this feature allows you to do: + + User.where(name: "John").order('id DESC').unscope(:order) + == User.where(name: "John") + + The .unscope() function is more general than the .except() method because + .except() only works on the relation it is acting on. However, .unscope() + works for any relation in the entire relation chain. + + *John Wang* + +* Postgresql timestamp with time zone (timestamptz) datatype now returns a + ActiveSupport::TimeWithZone instance instead of a string + + *Troy Kruthoff* + +* The `#append` method for collection associations behaves like`<<`. + `#prepend` is not defined and `<<` or `#append` should be used. + Fixes #7364. + + *Yves Senn* + +* Added support for creating a table via Rails migration generator. + For example, + + rails g migration create_books title:string content:text + + will generate a migration that creates a table called books with + the listed attributes, without creating a model. + + *Sammy Larbi* + +* Fix bug that raises the wrong exception when the exception handled by PostgreSQL adapter + doesn't respond to `#result`. + Fixes #8617. + + *kennyj* + +* Support PostgreSQL specific column types when using `change_table`. + Fixes #9480. + + Example: + + change_table :authors do |t| + t.hstore :books + t.json :metadata + end + + *Yves Senn* + +* Revert 408227d9c5ed7d, 'quote numeric'. This introduced some regressions. + + *Steve Klabnik* + +* Fix calculation of `db_runtime` property in + `ActiveRecord::Railties::ControllerRuntime#cleanup_view_runtime`. + Previously, after raising `ActionView::MissingTemplate`, `db_runtime` was + not populated. + Fixes #9215. + + *Igor Fedoronchuk* + +* Do not try to touch invalid (and thus not persisted) parent record + for a `belongs_to :parent, touch: true` association + + *Olek Janiszewski* * Fix when performing an ordered join query. The bug only affected queries where the order was given with a symbol. @@ -13,6 +93,17 @@ # This will expand the order :name to "authors".name. Author.joins(:books).where('books.published = 1').order(:name) + +## Rails 4.0.0.beta1 (February 25, 2013) ## + +* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`. + + *Hiroshige UMINO* + +* Update queries now use prepared statements. + + *Olli Rissanen* + * Fixing issue #8345. Now throwing an error when one attempts to touch a new object that has not yet been persisted. For instance: @@ -1507,4 +1598,5 @@ *Aaron Patterson* + Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 2f2600b7fb..97b1ff18e2 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -48,7 +48,7 @@ module ActiveRecord::Associations::Builder def belongs_to_touch_after_save_or_destroy_for_#{name} record = #{name} - unless record.nil? + unless record.nil? || record.new_record? record.touch #{options[:touch].inspect if options[:touch] != true} end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index e93e700c93..543204abac 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -83,9 +83,9 @@ module ActiveRecord # # #<Pet id: 3, name: "Choo-Choo"> # # ] # - # Be careful because this also means you’re initializing a model - # object with only the fields that you’ve selected. If you attempt - # to access a field that is not in the initialized record you’ll + # Be careful because this also means you're initializing a model + # object with only the fields that you've selected. If you attempt + # to access a field that is not in the initialized record you'll # receive: # # person.pets.select(:name).first.person_id @@ -924,7 +924,7 @@ module ActiveRecord alias_method :to_a, :to_ary # Adds one or more +records+ to the collection by setting their foreign keys - # to the association‘s primary key. Returns +self+, so several appends may be + # to the association's primary key. Returns +self+, so several appends may be # chained together. # # class Person < ActiveRecord::Base @@ -947,6 +947,11 @@ module ActiveRecord proxy_association.concat(records) && self end alias_method :push, :<< + alias_method :append, :<< + + def prepend(*args) + raise NoMethodError, "prepend on association is not defined. Please use << or append" + end # Equivalent to +delete_all+. The difference is that returns +self+, instead # of an array with the deleted objects, so methods can be chained. See diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 616ae1631f..6315dd9549 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -107,7 +107,11 @@ module ActiveRecord def changes_from_zero_to_string?(old, value) # For columns with old 0 and value non-empty string - old == 0 && value.is_a?(String) && value.present? && value != '0' + old == 0 && value.is_a?(String) && value.present? && non_zero?(value) + end + + def non_zero?(value) + value !~ /\A0+(\.0+)?\z/ end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index aec4654eee..d18b9c991f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -25,19 +25,13 @@ module ActiveRecord when true, false if column && column.type == :integer value ? '1' : '0' - elsif column && [:text, :string, :binary].include?(column.type) - value ? "'1'" : "'0'" else value ? quoted_true : quoted_false end # BigDecimals need to be put in a non-normalized form and quoted. when nil then "NULL" - when Numeric, ActiveSupport::Duration - value = BigDecimal === value ? value.to_s('F') : value.to_s - if column && ![:integer, :float, :decimal].include?(column.type) - value = "'#{value}'" - end - value + when BigDecimal then value.to_s('F') + when Numeric, ActiveSupport::Duration then value.to_s when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value.to_s}'" 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 f758e19a4f..42206de8fc 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -44,8 +44,8 @@ module ActiveRecord # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # - # Inside migration files, the +t+ object in +create_table+ and - # +change_table+ is actually of this type: + # Inside migration files, the +t+ object in +create_table+ + # is actually of this type: # # class SomeMigration < ActiveRecord::Migration # def up @@ -489,7 +489,7 @@ module ActiveRecord args.each do |name| @base.add_column(@table_name, name, column_type, options) end - end + end end private diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index cc289d4a14..f587bf8140 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -1,3 +1,5 @@ +require 'ipaddr' + module ActiveRecord module ConnectionAdapters # :nodoc: # The goal of this module is to move Adapter specific column @@ -50,6 +52,15 @@ module ActiveRecord when Range # infinity dumps as Infinity, which causes uninitialized constant error value.inspect.gsub('Infinity', '::Float::INFINITY') + when IPAddr + subnet_mask = value.instance_variable_get(:@mask_addr) + + # If the subnet mask is equal to /32, don't output it + if subnet_mask == (2**32 - 1) + "\"#{value.to_s}\"" + else + "\"#{value.to_s}/#{subnet_mask.to_s(2).count('1')}\"" + end else value.inspect 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 9bae880024..0cce8c7596 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -156,7 +156,7 @@ module ActiveRecord # # See also TableDefinition#column for details on how to create columns. def create_table(table_name, options = {}) - td = table_definition + td = create_table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false yield td if block_given? @@ -298,10 +298,10 @@ module ActiveRecord def change_table(table_name, options = {}) if supports_bulk_alter? && options[:bulk] recorder = ActiveRecord::Migration::CommandRecorder.new(self) - yield Table.new(table_name, recorder) + yield update_table_definition(table_name, recorder) bulk_change_table(table_name, recorder.commands) else - yield Table.new(table_name, self) + yield update_table_definition(table_name, self) end end @@ -727,9 +727,13 @@ module ActiveRecord end private - def table_definition + def create_table_definition TableDefinition.new(self) end + + def update_table_definition(table_name, base) + Table.new(table_name, base) + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 5480204511..9826b18053 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -212,6 +212,8 @@ module ActiveRecord if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) s = column.class.string_to_binary(value).unpack("H*")[0] "x'#{s}'" + elsif value.kind_of?(BigDecimal) + value.to_s("F") else super end diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 2c683fc3ac..8bad7d0cf5 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -38,7 +38,7 @@ module ActiveRecord private def resolve_string_connection(spec) # :nodoc: hash = configurations.fetch(spec) do |k| - self.class.connection_url_to_hash(k) + connection_url_to_hash(k) end raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash @@ -65,7 +65,7 @@ module ActiveRecord ConnectionSpecification.new(spec, adapter_method) end - def self.connection_url_to_hash(url) # :nodoc: + def connection_url_to_hash(url) # :nodoc: config = URI.parse url adapter = config.scheme adapter = "postgresql" if adapter == "postgres" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index e09319890a..68f2f2ca7b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -317,10 +317,6 @@ module ActiveRecord alias_type 'macaddr', 'text' alias_type 'uuid', 'text' - # FIXME: I don't think this is correct. We should probably be returning a parsed date, - # but the tests pass with a string returned. - register_type 'timestamptz', OID::Identity.new - register_type 'money', OID::Money.new register_type 'bytea', OID::Bytea.new register_type 'bool', OID::Boolean.new @@ -329,6 +325,7 @@ module ActiveRecord alias_type 'float8', 'float4' register_type 'timestamp', OID::Timestamp.new + register_type 'timestamptz', OID::Timestamp.new register_type 'date', OID::Date.new register_type 'time', OID::Time.new diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2bb2557efd..c91e1b3fb9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -263,7 +263,7 @@ module ActiveRecord attr_accessor :array end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + module ColumnMethods def xml(*args) options = args.extract_options! column(args[0], 'xml', options) @@ -325,6 +325,10 @@ module ActiveRecord def json(name, options = {}) column(name, 'json', options) end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods def column(name, type = nil, options = {}) super @@ -344,6 +348,10 @@ module ActiveRecord end end + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + ADAPTER_NAME = 'PostgreSQL' NATIVE_DATABASE_TYPES = { @@ -667,6 +675,8 @@ module ActiveRecord UNIQUE_VIOLATION = "23505" def translate_exception(exception, message) + return exception unless exception.respond_to?(:result) + case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE) when UNIQUE_VIOLATION RecordNotUnique.new(message, exception) @@ -884,9 +894,13 @@ module ActiveRecord $1.strip if $1 end - def table_definition + def create_table_definition TableDefinition.new(self) end + + def update_table_definition(table_name, base) + Table.new(table_name, base) + end end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 64eac3aca7..13f3bf7085 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -152,7 +152,7 @@ module ActiveRecord # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do - self.configurations = app.config.database_configuration + self.configurations = app.config.database_configuration || {} establish_connection end end diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb index 7695eacbff..af4840476c 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -21,9 +21,10 @@ module ActiveRecord def cleanup_view_runtime if ActiveRecord::Base.connected? db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime = (db_runtime || 0) + db_rt_before_render runtime = super db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime - self.db_runtime = db_rt_before_render + db_rt_after_render + self.db_runtime += db_rt_after_render runtime - db_rt_after_render else super diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f36af7182f..d92e268109 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -2,7 +2,7 @@ require 'active_record' db_namespace = namespace :db do task :load_config do - ActiveRecord::Base.configurations = Rails.application.config.database_configuration + ActiveRecord::Base.configurations = Rails.application.config.database_configuration || {} ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index bc50802c4a..efbae108b9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -236,8 +236,9 @@ module ActiveRecord # Scope all queries to the current scope. # # Comment.where(post_id: 1).scoping do - # Comment.first # SELECT * FROM comments WHERE post_id = 1 + # Comment.first # end + # # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1 # # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 5cd015eba7..bd783a94cf 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -98,11 +98,6 @@ module ActiveRecord when Class # FIXME: I think we need to deprecate this behavior attribute.eq(value.name) - when Integer, ActiveSupport::Duration - # Arel treats integers as literals, but they should be quoted when compared with strings - table = attribute.relation - column = table.engine.connection.schema_cache.columns_hash(table.name)[attribute.name.to_s] - attribute.eq(Arel::Nodes::SqlLiteral.new(table.engine.connection.quote(value, column))) else attribute.eq(value) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5076ae8a76..b7960936cf 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -317,6 +317,67 @@ module ActiveRecord self end + VALID_UNSCOPING_VALUES = Set.new([:where, :select, :group, :order, :lock, + :limit, :offset, :joins, :includes, :from, + :readonly, :having]) + + # Removes an unwanted relation that is already defined on a chain of relations. + # This is useful when passing around chains of relations and would like to + # modify the relations without reconstructing the entire chain. + # + # User.order('email DESC').unscope(:order) == User.all + # + # The method arguments are symbols which correspond to the names of the methods + # which should be unscoped. The valid arguments are given in VALID_UNSCOPING_VALUES. + # The method can also be called with multiple arguments. For example: + # + # User.order('email DESC').select('id').where(name: "John") + # .unscope(:order, :select, :where) == User.all + # + # One can additionally pass a hash as an argument to unscope specific :where values. + # This is done by passing a hash with a single key-value pair. The key should be + # :where and the value should be the where value to unscope. For example: + # + # User.where(name: "John", active: true).unscope(where: :name) + # == User.where(active: true) + # + # Note that this method is more generalized than ActiveRecord::SpawnMethods#except + # because #except will only affect a particular relation's values. It won't wipe + # the order, grouping, etc. when that relation is merged. For example: + # + # Post.comments.except(:order) + # + # will still have an order if it comes from the default_scope on Comment. + def unscope(*args) + check_if_method_has_arguments!("unscope", args) + spawn.unscope!(*args) + end + + def unscope!(*args) + args.flatten! + + args.each do |scope| + case scope + when Symbol + symbol_unscoping(scope) + when Hash + scope.each do |key, target_value| + if key != :where + raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key." + end + + Array(target_value).each do |val| + where_unscoping(val) + end + end + else + raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example." + end + end + + self + end + # Performs a joins on +args+: # # User.joins(:posts) @@ -762,6 +823,39 @@ module ActiveRecord private + def symbol_unscoping(scope) + if !VALID_UNSCOPING_VALUES.include?(scope) + raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." + end + + single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope) + unscope_code = :"#{scope}_value#{'s' unless single_val_method}=" + + case scope + when :order + self.send(:reverse_order_value=, false) + result = [] + else + result = [] unless single_val_method + end + + self.send(unscope_code, result) + end + + def where_unscoping(target_value) + target_value_sym = target_value.to_sym + + where_values.reject! do |rel| + case rel + when Arel::Nodes::In, Arel::Nodes::Equality + subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) + subrelation.name.to_sym == target_value_sym + else + raise "unscope(where: #{target_value.inspect}) failed: unscoping #{rel.class} is unimplemented." + end + end + end + def custom_join_ast(table, joins) joins = joins.reject { |join| join.blank? } diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 6b55af4205..bd9079b596 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -5,7 +5,7 @@ module ActiveRecord #:nodoc: include ActiveModel::Serializers::JSON included do - self.include_root_in_json = true + self.include_root_in_json = false end def serializable_hash(options = nil) diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 5f1dbe36d6..b967bb6e0f 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -8,13 +8,14 @@ module ActiveRecord def create_migration_file set_local_assigns! validate_file_name! - migration_template "migration.rb", "db/migrate/#{file_name}.rb" + migration_template @migration_template, "db/migrate/#{file_name}.rb" end protected attr_reader :migration_action, :join_tables def set_local_assigns! + @migration_template = "migration.rb" case file_name when /^(add|remove)_.*_(?:to|from)_(.*)/ @migration_action = $1 @@ -26,6 +27,9 @@ module ActiveRecord set_index_names end + when /^create_(.+)/ + @table_name = $1.pluralize + @migration_template = "create_table_migration.rb" end end @@ -44,7 +48,10 @@ module ActiveRecord end private - + def attributes_with_index + attributes.select { |a| !a.reference? && a.has_index? } + end + def validate_file_name! unless file_name =~ /^[_a-z0-9]+$/ raise IllegalMigrationNameError.new(file_name) diff --git a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index 3a3cf86d73..3a3cf86d73 100644 --- a/activerecord/lib/rails/generators/active_record/model/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index 5f36181694..40e134e626 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -15,7 +15,7 @@ module ActiveRecord def create_migration_file return unless options[:migration] && options[:parent].nil? attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false - migration_template "migration.rb", "db/migrate/create_#{table_name}.rb" + migration_template "../../migration/templates/create_table_migration.rb", "db/migrate/create_#{table_name}.rb" end def create_model_file diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 33c796191e..1e6ae85a25 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -573,6 +573,7 @@ _SQL @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time + assert_instance_of Time, @first_timestamp_with_zone.time ensure ActiveRecord::Base.default_timezone = old_default_tz ActiveRecord::Base.time_zone_aware_attributes = old_tz @@ -590,6 +591,7 @@ _SQL @first_timestamp_with_zone = PostgresqlTimestampWithZone.find(1) assert_equal Time.utc(2010,1,1, 11,0,0), @first_timestamp_with_zone.time + assert_instance_of Time, @first_timestamp_with_zone.time ensure ActiveRecord::Base.default_timezone = old_default_tz ActiveRecord::Base.time_zone_aware_attributes = old_tz diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 6640f9b497..ad98d7c8ce 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -65,6 +65,21 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase assert_equal :hstore, @column.type end + def test_change_table_supports_hstore + @connection.transaction do + @connection.change_table('hstores') do |t| + t.hstore 'users', default: '' + end + Hstore.reset_column_information + column = Hstore.columns.find { |c| c.name == 'users' } + assert_equal :hstore, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + Hstore.reset_column_information + end + def test_type_cast_hstore assert @column diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index d64037eec0..6fc08ae4f0 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -31,6 +31,21 @@ class PostgresqlJSONTest < ActiveRecord::TestCase assert_equal :json, @column.type end + def test_change_table_supports_json + @connection.transaction do + @connection.change_table('json_data_type') do |t| + t.json 'users', default: '{}' + end + JsonDataType.reset_column_information + column = JsonDataType.columns.find { |c| c.name == 'users' } + assert_equal :json, column.type + + raise ActiveRecord::Rollback # reset the schema change + end + ensure + JsonDataType.reset_column_information + end + def test_type_cast_json assert @column diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 872204c644..05e0f0e192 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -250,6 +250,12 @@ module ActiveRecord assert_equal "DISTINCT posts.title, posts.updater_id AS alias_0", @connection.distinct("posts.title", ["posts.updater_id desc nulls last"]) end + def test_raise_error_when_cannot_translate_exception + assert_raise TypeError do + @connection.send(:log, nil) { @connection.execute(nil) } + end + end + private def insert(ctx, data) binds = data.map { |name, value| diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index d7f25f760e..201fa5d5a9 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -172,6 +172,18 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal 1, josh.posts.size end + def test_append_behaves_like_push + josh = Author.new(:name => "Josh") + josh.posts.append Post.new(:title => "New on Edge", :body => "More cool stuff!") + assert josh.posts.loaded? + assert_equal 1, josh.posts.size + end + + def test_prepend_is_not_defined + josh = Author.new(:name => "Josh") + assert_raises(NoMethodError) { josh.posts.prepend Post.new } + end + def test_save_on_parent_does_not_load_target david = developers(:david) @@ -291,7 +303,7 @@ class OverridingAssociationsTest < ActiveRecord::TestCase end def test_requires_symbol_argument - assert_raises ArgumentError do + assert_raises ArgumentError do Class.new(Post) do belongs_to "author" end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index c7d2ba6073..7b2034dadf 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -243,6 +243,21 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.changed? end + def test_float_zero_to_string_zero_not_marked_as_changed + data = NumericData.new :temperature => 0.0 + data.save! + + assert_not data.changed? + + data.temperature = '0' + assert_empty data.changes + + data.temperature = '0.0' + assert_empty data.changes + + data.temperature = '0.00' + assert_empty data.changes + end def test_zero_to_blank_marked_as_changed pirate = Pirate.new diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index a86b165c78..a222675918 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -6,7 +6,21 @@ require 'models/tagging' require 'models/tag' require 'models/comment' +module JsonSerializationHelpers + private + + def set_include_root_in_json(value) + original_root_in_json = ActiveRecord::Base.include_root_in_json + ActiveRecord::Base.include_root_in_json = value + yield + ensure + ActiveRecord::Base.include_root_in_json = original_root_in_json + end +end + class JsonSerializationTest < ActiveRecord::TestCase + include JsonSerializationHelpers + class NamespacedContact < Contact column :name, :string end @@ -23,20 +37,24 @@ class JsonSerializationTest < ActiveRecord::TestCase end def test_should_demodulize_root_in_json - @contact = NamespacedContact.new :name => 'whatever' - json = @contact.to_json - assert_match %r{^\{"namespaced_contact":\{}, json + set_include_root_in_json(true) do + @contact = NamespacedContact.new name: 'whatever' + json = @contact.to_json + assert_match %r{^\{"namespaced_contact":\{}, json + end end def test_should_include_root_in_json - json = @contact.to_json - - assert_match %r{^\{"contact":\{}, json - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json + set_include_root_in_json(true) do + json = @contact.to_json + + assert_match %r{^\{"contact":\{}, json + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + end end def test_should_encode_all_encodable_attributes @@ -141,6 +159,8 @@ end class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase fixtures :authors, :posts, :comments, :tags, :taggings + include JsonSerializationHelpers + def setup @david = authors(:david) @mary = authors(:mary) @@ -227,23 +247,21 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_should_allow_only_option_for_list_of_authors - ActiveRecord::Base.include_root_in_json = false - authors = [@david, @mary] - assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, :only => :name) - ensure - ActiveRecord::Base.include_root_in_json = true + set_include_root_in_json(false) do + authors = [@david, @mary] + assert_equal %([{"name":"David"},{"name":"Mary"}]), ActiveSupport::JSON.encode(authors, only: :name) + end end def test_should_allow_except_option_for_list_of_authors - ActiveRecord::Base.include_root_in_json = false - authors = [@david, @mary] - encoded = ActiveSupport::JSON.encode(authors, :except => [ - :name, :author_address_id, :author_address_extra_id, - :organization_id, :owned_essay_id - ]) - assert_equal %([{"id":1},{"id":2}]), encoded - ensure - ActiveRecord::Base.include_root_in_json = true + set_include_root_in_json(false) do + authors = [@david, @mary] + encoded = ActiveSupport::JSON.encode(authors, except: [ + :name, :author_address_id, :author_address_extra_id, + :organization_id, :owned_essay_id + ]) + assert_equal %([{"id":1},{"id":2}]), encoded + end end def test_should_allow_includes_for_list_of_authors @@ -262,17 +280,21 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase end def test_should_allow_options_for_hash_of_authors - authors_hash = { - 1 => @david, - 2 => @mary - } - assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, :only => [1, :name]) + set_include_root_in_json(true) do + authors_hash = { + 1 => @david, + 2 => @mary + } + assert_equal %({"1":{"author":{"name":"David"}}}), ActiveSupport::JSON.encode(authors_hash, only: [1, :name]) + end end def test_should_be_able_to_encode_relation - authors_relation = Author.where(:id => [@david.id, @mary.id]) + set_include_root_in_json(true) do + authors_relation = Author.where(id: [@david.id, @mary.id]) - json = ActiveSupport::JSON.encode authors_relation, :only => :name - assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json + json = ActiveSupport::JSON.encode authors_relation, only: :name + assert_equal '[{"author":{"name":"David"}},{"author":{"name":"Mary"}}]', json + end end end diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 0ad05223d4..3dd11ae89d 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -122,35 +122,35 @@ module ActiveRecord def test_quote_float float = 1.2 assert_equal float.to_s, @quoter.quote(float, nil) - assert_equal float.to_s, @quoter.quote(float, FakeColumn.new(:float)) + assert_equal float.to_s, @quoter.quote(float, Object.new) end def test_quote_fixnum fixnum = 1 assert_equal fixnum.to_s, @quoter.quote(fixnum, nil) - assert_equal fixnum.to_s, @quoter.quote(fixnum, FakeColumn.new(:integer)) + assert_equal fixnum.to_s, @quoter.quote(fixnum, Object.new) end def test_quote_bignum bignum = 1 << 100 assert_equal bignum.to_s, @quoter.quote(bignum, nil) - assert_equal bignum.to_s, @quoter.quote(bignum, FakeColumn.new(:integer)) + assert_equal bignum.to_s, @quoter.quote(bignum, Object.new) end def test_quote_bigdecimal bigdec = BigDecimal.new((1 << 100).to_s) assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, nil) - assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, FakeColumn.new(:decimal)) + assert_equal bigdec.to_s('F'), @quoter.quote(bigdec, Object.new) end def test_dates_and_times @quoter.extend(Module.new { def quoted_date(value) 'lol' end }) assert_equal "'lol'", @quoter.quote(Date.today, nil) - assert_equal "'lol'", @quoter.quote(Date.today, FakeColumn.new(:date)) + assert_equal "'lol'", @quoter.quote(Date.today, Object.new) assert_equal "'lol'", @quoter.quote(Time.now, nil) - assert_equal "'lol'", @quoter.quote(Time.now, FakeColumn.new(:time)) + assert_equal "'lol'", @quoter.quote(Time.now, Object.new) assert_equal "'lol'", @quoter.quote(DateTime.now, nil) - assert_equal "'lol'", @quoter.quote(DateTime.now, FakeColumn.new(:datetime)) + assert_equal "'lol'", @quoter.quote(DateTime.now, Object.new) end def test_crazy_object diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index 53cdf89b1f..c43c7601a2 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -108,30 +108,5 @@ module ActiveRecord assert_equal 4, Edge.where(blank).order("sink_id").to_a.size end end - - def test_where_with_integer_for_string_column - count = Post.where(:title => 0).count - assert_equal 0, count - end - - def test_where_with_float_for_string_column - count = Post.where(:title => 0.0).count - assert_equal 0, count - end - - def test_where_with_boolean_for_string_column - count = Post.where(:title => false).count - assert_equal 0, count - end - - def test_where_with_decimal_for_string_column - count = Post.where(:title => BigDecimal.new(0)).count - assert_equal 0, count - end - - def test_where_with_duration_for_string_column - count = Post.where(:title => 0.seconds).count - assert_equal 0, count - end end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 8e6c38706f..2b4aadc7ed 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -391,19 +391,19 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_with_inheritance wheres = InheritedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_default_scope_with_module_includes wheres = ModuleIncludedPoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_default_scope_with_multiple_calls wheres = MultiplePoorDeveloperCalledJamis.all.where_values_hash assert_equal "Jamis", wheres[:name] - assert_equal Arel.sql("50000"), wheres[:salary] + assert_equal 50000, wheres[:salary] end def test_scope_overwrites_default @@ -424,6 +424,150 @@ class DefaultScopingTest < ActiveRecord::TestCase assert_equal expected, received end + def test_unscope_overrides_default_scope + expected = Developer.all.collect { |dev| [dev.name, dev.id] } + received = Developer.order('name ASC, id DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } + assert_equal expected, received + end + + def test_unscope_after_reordering_and_combining + expected = Developer.order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] } + received = DeveloperOrderedBySalary.reorder('name DESC').unscope(:order).order('id DESC, name DESC').collect { |dev| [dev.name, dev.id] } + assert_equal expected, received + + expected_2 = Developer.all.collect { |dev| [dev.name, dev.id] } + received_2 = Developer.order('id DESC, name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } + assert_equal expected_2, received_2 + + expected_3 = Developer.all.collect { |dev| [dev.name, dev.id] } + received_3 = Developer.reorder('name DESC').unscope(:order).collect { |dev| [dev.name, dev.id] } + assert_equal expected_3, received_3 + end + + def test_unscope_with_where_attributes + expected = Developer.order('salary DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.where(name: 'David').unscope(where: :name).collect { |dev| dev.name } + assert_equal expected, received + + expected_2 = Developer.order('salary DESC').collect { |dev| dev.name } + received_2 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope({:where => :name}, :select).collect { |dev| dev.name } + assert_equal expected_2, received_2 + + expected_3 = Developer.order('salary DESC').collect { |dev| dev.name } + received_3 = DeveloperOrderedBySalary.select("id").where("name" => "Jamis").unscope(:select, :where).collect { |dev| dev.name } + assert_equal expected_3, received_3 + end + + def test_unscope_multiple_where_clauses + expected = Developer.order('salary DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.where(name: 'Jamis').where(id: 1).unscope(where: [:name, :id]).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_unscope_with_grouping_attributes + expected = Developer.order('salary DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.group(:name).unscope(:group).collect { |dev| dev.name } + assert_equal expected, received + + expected_2 = Developer.order('salary DESC').collect { |dev| dev.name } + received_2 = DeveloperOrderedBySalary.group("name").unscope(:group).collect { |dev| dev.name } + assert_equal expected_2, received_2 + end + + def test_unscope_with_limit_in_query + expected = Developer.order('salary DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.limit(1).unscope(:limit).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_order_to_unscope_reordering + expected = DeveloperOrderedBySalary.all.collect { |dev| [dev.name, dev.id] } + received = DeveloperOrderedBySalary.order('salary DESC, name ASC').reverse_order.unscope(:order).collect { |dev| [dev.name, dev.id] } + assert_equal expected, received + end + + def test_unscope_reverse_order + expected = Developer.all.collect { |dev| dev.name } + received = Developer.order('salary DESC').reverse_order.unscope(:order).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_unscope_select + expected = Developer.order('salary ASC').collect { |dev| dev.name } + received = Developer.order('salary DESC').reverse_order.select(:name => "Jamis").unscope(:select).collect { |dev| dev.name } + assert_equal expected, received + + expected_2 = Developer.all.collect { |dev| dev.id } + received_2 = Developer.select(:name).unscope(:select).collect { |dev| dev.id } + assert_equal expected_2, received_2 + end + + def test_unscope_offset + expected = Developer.all.collect { |dev| dev.name } + received = Developer.offset(5).unscope(:offset).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_unscope_joins_and_select_on_developers_projects + expected = Developer.all.collect { |dev| dev.name } + received = Developer.joins('JOIN developers_projects ON id = developer_id').select(:id).unscope(:joins, :select).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_unscope_includes + expected = Developer.all.collect { |dev| dev.name } + received = Developer.includes(:projects).select(:id).unscope(:includes, :select).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_unscope_having + expected = DeveloperOrderedBySalary.all.collect { |dev| dev.name } + received = DeveloperOrderedBySalary.having("name IN ('Jamis', 'David')").unscope(:having).collect { |dev| dev.name } + assert_equal expected, received + end + + def test_unscope_errors_with_invalid_value + assert_raises(ArgumentError) do + Developer.includes(:projects).where(name: "Jamis").unscope(:stupidly_incorrect_value) + end + + assert_raises(ArgumentError) do + Developer.all.unscope(:includes, :select, :some_broken_value) + end + + assert_raises(ArgumentError) do + Developer.order('name DESC').reverse_order.unscope(:reverse_order) + end + + assert_raises(ArgumentError) do + Developer.order('name DESC').where(name: "Jamis").unscope() + end + end + + def test_unscope_errors_with_non_where_hash_keys + assert_raises(ArgumentError) do + Developer.where(name: "Jamis").limit(4).unscope(limit: 4) + end + + assert_raises(ArgumentError) do + Developer.where(name: "Jamis").unscope("where" => :name) + end + end + + def test_unscope_errors_with_non_symbol_or_hash_arguments + assert_raises(ArgumentError) do + Developer.where(name: "Jamis").limit(3).unscope("limit") + end + + assert_raises(ArgumentError) do + Developer.select("id").unscope("select") + end + + assert_raises(ArgumentError) do + Developer.select("id").unscope(5) + end + end + def test_order_in_default_scope_should_not_prevail expected = Developer.all.merge!(:order => 'salary').to_a.collect { |dev| dev.salary } received = DeveloperOrderedBySalary.all.merge!(:order => 'salary').to_a.collect { |dev| dev.salary } diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index bfecc0d1e9..1147b9a09e 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -261,22 +261,22 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_includes_inet_shorthand_definition output = standard_dump - if %r{create_table "postgresql_network_address"} =~ output - assert_match %r{t.inet "inet_address"}, output + if %r{create_table "postgresql_network_addresses"} =~ output + assert_match %r{t.inet\s+"inet_address",\s+default: "192.168.1.1"}, output end end def test_schema_dump_includes_cidr_shorthand_definition output = standard_dump - if %r{create_table "postgresql_network_address"} =~ output - assert_match %r{t.cidr "cidr_address"}, output + if %r{create_table "postgresql_network_addresses"} =~ output + assert_match %r{t.cidr\s+"cidr_address",\s+default: "192.168.1.0/24"}, output end end def test_schema_dump_includes_macaddr_shorthand_definition output = standard_dump - if %r{create_table "postgresql_network_address"} =~ output - assert_match %r{t.macaddr "macaddr_address"}, output + if %r{create_table "postgresql_network_addresses"} =~ output + assert_match %r{t.macaddr\s+"mac_address",\s+default: "ff:ff:ff:ff:ff:ff"}, output end end diff --git a/activerecord/test/cases/serialization_test.rb b/activerecord/test/cases/serialization_test.rb index eb9cb91e32..c46060a646 100644 --- a/activerecord/test/cases/serialization_test.rb +++ b/activerecord/test/cases/serialization_test.rb @@ -18,6 +18,10 @@ class SerializationTest < ActiveRecord::TestCase } end + def test_include_root_in_json_is_false_by_default + assert_equal false, ActiveRecord::Base.include_root_in_json, "include_root_in_json should be false by default but was not" + end + def test_serialize_should_be_reversible FORMATS.each do |format| @serialized = Contact.new.send("to_#{format}") diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index bb034848e1..777a2b70dd 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -113,6 +113,18 @@ class TimestampTest < ActiveRecord::TestCase assert_not_equal previously_owner_updated_at, pet.owner.updated_at end + def test_saving_a_new_record_belonging_to_invalid_parent_with_touch_should_not_raise_exception + klass = Class.new(Owner) do + def self.name; 'Owner'; end + validate { errors.add(:base, :invalid) } + end + + pet = Pet.new(owner: klass.new) + pet.save! + + assert pet.owner.new_record? + end + def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute klass = Class.new(ActiveRecord::Base) do def self.name; 'Pet'; end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 83b50030bd..d8271ac8d1 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -145,9 +145,9 @@ _SQL execute <<_SQL CREATE TABLE postgresql_network_addresses ( id SERIAL PRIMARY KEY, - cidr_address CIDR, - inet_address INET, - mac_address MACADDR + cidr_address CIDR default '192.168.1.0/24', + inet_address INET default '192.168.1.1', + mac_address MACADDR default 'ff:ff:ff:ff:ff:ff' ); _SQL diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index d789b6cb7a..cd9835259a 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -540,8 +540,6 @@ ActiveRecord::Schema.define do create_table :price_estimates, :force => true do |t| t.string :estimate_of_type t.integer :estimate_of_id - t.string :thing_type - t.integer :thing_id t.integer :price end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 97b3344db0..c47cb75274 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,9 @@ +## Rails 4.0.0 (unreleased) ## + +* Fix deletion of empty directories in ActiveSupport::Cache::FileStore. + + *Charles Jones* + ## Rails 4.0.0.beta1 (February 25, 2013) ## * Prevent `DateTime#change` from truncating the second fraction, when seconds diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 819980eac9..a28310032a 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency 'i18n', '~> 0.6.2' + s.add_dependency('i18n', '~> 0.6', '>= 0.6.4') s.add_dependency 'multi_json', '~> 1.3' s.add_dependency 'tzinfo', '~> 0.3.33' s.add_dependency 'minitest', '~> 4.2' diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 8e265ad863..0c55aa8a32 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -150,9 +150,9 @@ module ActiveSupport # Delete empty directories in the cache. def delete_empty_directories(dir) - return if dir == cache_path + return if File.realpath(dir) == File.realpath(cache_path) if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty? - File.delete(dir) rescue nil + Dir.delete(dir) rescue nil delete_empty_directories(File.dirname(dir)) end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e3e1845868..f2d9df6d13 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -255,7 +255,6 @@ module ActiveSupport # a method is created that calls the before_foo method # on the object. def _compile_filter(filter) - method_name = "_callback_#{@kind}_#{next_id}" case filter when Array filter.map {|f| _compile_filter(f)} @@ -264,11 +263,13 @@ module ActiveSupport when String "(#{filter})" when Proc + method_name = "_callback_#{@kind}_#{next_id}" @klass.send(:define_method, method_name, &filter) return method_name if filter.arity <= 0 method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else + method_name = "_callback_#{@kind}_#{next_id}" @klass.send(:define_method, "#{method_name}_object") { filter } _normalize_legacy_filter(kind, filter) diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 5f85cedcf5..dc033ed11b 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -42,7 +42,7 @@ class ERB # html_escape_once('<< Accept & Checkout') # # => "<< Accept & Checkout" def html_escape_once(s) - result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] } + result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) s.html_safe? ? result.html_safe : result end @@ -60,7 +60,7 @@ class ERB # json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}') # # => {name:john,created_at:2010-04-28T01:39:31Z,id:1} def json_escape(s) - result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] } + result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE) s.html_safe? ? result.html_safe : result end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 5158bbc196..ede08e23d5 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -672,6 +672,18 @@ class FileStoreTest < ActiveSupport::TestCase end end + def test_delete_does_not_delete_empty_parent_dir + sub_cache_dir = File.join(cache_dir, 'subdir/') + sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir) + assert_nothing_raised(Exception) do + assert sub_cache_store.write('foo', 'bar') + assert sub_cache_store.delete('foo') + end + assert File.exist?(cache_dir), "Parent of top level cache dir was deleted!" + assert File.exist?(sub_cache_dir), "Top level cache dir was deleted!" + assert Dir.entries(sub_cache_dir).reject {|f| ActiveSupport::Cache::FileStore::EXCLUDED_DIRS.include?(f)}.empty? + end + def test_log_exception_when_cache_read_fails File.expects(:exist?).raises(StandardError, "failed") @cache.send(:read_entry, "winston", {}) diff --git a/guides/.document b/guides/.document new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/guides/.document diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index a9595d1f15..b0e52847e1 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,6 @@ +## Rails 4.0.0 (unreleased) ## +* Change Service pages(404, etc). *Stanislav Sobolev* + ## Rails 4.0.0.beta1 (unreleased) ## * Split Validations and Callbacks guide into two. *Steve Klabnik* diff --git a/guides/code/getting_started/public/404.html b/guides/code/getting_started/public/404.html index 3d875c342e..ae7b8649ae 100644 --- a/guides/code/getting_started/public/404.html +++ b/guides/code/getting_started/public/404.html @@ -2,17 +2,48 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/guides/code/getting_started/public/422.html b/guides/code/getting_started/public/422.html index 3f1bfb3417..0b64eb4ae9 100644 --- a/guides/code/getting_started/public/422.html +++ b/guides/code/getting_started/public/422.html @@ -2,17 +2,48 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50,50,50,0.17); + } </style> </head> @@ -22,5 +53,6 @@ <h1>The change you wanted was rejected.</h1> <p>Maybe you tried to change something you didn't have access to.</p> </div> + <p>If you are the application owner check the logs for more information.</p> </body> </html> diff --git a/guides/code/getting_started/public/500.html b/guides/code/getting_started/public/500.html index 012977d3d2..9641851e74 100644 --- a/guides/code/getting_started/public/500.html +++ b/guides/code/getting_started/public/500.html @@ -2,17 +2,48 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow: 0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index da155628f3..5861fc3d54 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -6,6 +6,7 @@ In this guide you will learn how controllers work and how they fit into the requ After reading this guide, you will know: * How to follow the flow of a request through a controller. +* How to restrict parameters passed to your controller. * Why and how to store data in the session or cookies. * How to work with filters to execute code during request processing. * How to use Action Controller's built-in HTTP authentication. @@ -170,6 +171,144 @@ These options will be used as a starting point when generating URLs, so it's pos If you define `default_url_options` in `ApplicationController`, as in the example above, it would be used for all URL generation. The method can also be defined in one specific controller, in which case it only affects URLs generated there. +### Strong Parameters + +With strong parameters Action Controller parameters are forbidden to +be used in Active Model mass assignments until they have been +whitelisted. This means you'll have to make a conscious choice about +which attributes to allow for mass updating and thus prevent +accidentally exposing that which shouldn't be exposed. + +In addition, parameters can be marked as required and flow through a +predefined raise/rescue flow to end up as a 400 Bad Request with no +effort. + +```ruby +class PeopleController < ActionController::Base + # This will raise an ActiveModel::ForbiddenAttributes exception + # because it's using mass assignment without an explicit permit + # step. + def create + Person.create(params[:person]) + end + + # This will pass with flying colors as long as there's a person key + # in the parameters, otherwise it'll raise a + # ActionController::MissingParameter exception, which will get + # caught by ActionController::Base and turned into that 400 Bad + # Request reply. + def update + person = current_account.people.find(params[:id]) + person.update_attributes!(person_params) + redirect_to person + end + + private + # Using a private method to encapsulate the permissible parameters + # is just a good pattern since you'll be able to reuse the same + # permit list between create and update. Also, you can specialize + # this method with per-user checking of permissible attributes. + def person_params + params.require(:person).permit(:name, :age) + end +end +``` + +#### Permitted Scalar Values + +Given + +```ruby +params.permit(:id) +``` + +the key `:id` will pass the whitelisting if it appears in `params` and +it has a permitted scalar value associated. Otherwise the key is going +to be filtered out, so arrays, hashes, or any other objects cannot be +injected. + +The permitted scalar types are `String`, `Symbol`, `NilClass`, +`Numeric`, `TrueClass`, `FalseClass`, `Date`, `Time`, `DateTime`, +`StringIO`, `IO`, `ActionDispatch::Http::UploadedFile` and +`Rack::Test::UploadedFile`. + +To declare that the value in `params+ must be an array of permitted +scalar values map the key to an empty array: + +```ruby +params.permit(:id => []) +``` + +To whitelist an entire hash of parameters, the `permit!+ method can be +used + +```ruby +params.require(:log_entry).permit! +``` + +This will mark the `:log_entry` parameters hash and any subhash of it +permitted. Extreme care should be taken when using `permit!` as it +will allow all current and future model attributes to be +mass-assigned. + +#### Nested Parameters + +You can also use permit on nested parameters, like: + +```ruby +params.permit(:name, {:emails => []}, + :friends => [ :name, + { :family => [ :name ], :hobbies => [] }]) +``` + +This declaration whitelists the `name`, `emails` and `friends` +attributes. It is expected that `emails` will be an array of permitted +scalar values and that `friends` will be an array of resources with +specific attributes : they should have a `name` attribute (any +permitted scalar values allowed), a `hobbies` attribute as an array of +permitted scalar values, and a `family` attribute which is restricted +to having a `name` (any permitted scalar values allowed, too). + +#### More Examples + +You want to also use the permitted attributes in the `new` +action. This raises the problem that you can't use `require` on the +root-key because normally it does not exist when calling `new`: + +```ruby +# using `fetch` you can supply a default and use +# the Strong Parameters API from there. +params.fetch(:blog, {}).permit(:title, :author) +``` + +`accepts_nested_attributes_for` allows you update and destroy the +associated records. This is based on the `id` and `_destroy` +parameters: + +```ruby +# permit :id and :_destroy +params.require(:author).permit(:name, books_attributes: [:title, :id, :_destroy]) +``` + +#### Outside the Scope of Strong Parameters + +The strong parameter API was designed with the most common use cases +in mind. It is not meant as a silver bullet to handle all your +whitelisting problems. However you can easily mix the API with your +own code to adapt to your situation. + +Imagine a situation where you want to whitelist an attribute +containing a hash with any keys. Using strong parameters you can't +allow a hash with any keys but you can use a simple assignment to get +the job done: + +```ruby +def product_params + params.require(:product).permit(:name).tap do |whitelisted| + whitelisted[:data] = params[:product][:data] + end +end +``` Session ------- diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index a5058aa749..8720aae169 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -507,7 +507,6 @@ The following configuration options are best made in one of the environment file | Configuration | Description | |---------------|-------------| -|`template_root`|Determines the base from which template references will be made.| |`logger`|Generates information on the mailing run if available. Can be set to `nil` for no logging. Compatible with both Ruby's own `Logger` and `Log4r` loggers.| |`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default "localhost" setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain`, `:login`, `:cram_md5`.</li><li>`:enable_starttls_auto` - Set this to `false` if there is a problem with your server certificate that you cannot resolve.</li></ul>| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i -t`.</li></ul>| diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 5d82541da9..0d0813c56a 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -692,6 +692,27 @@ The SQL that would be executed: SELECT * FROM posts WHERE id > 10 LIMIT 20 ``` +### `unscope` + +The `except` method does not work when the relation is merged. For example: + +```ruby +Post.comments.except(:order) +``` + +will still have an order if the order comes from a default scope on Comment. In order to remove all ordering, even from relations which are merged in, use unscope as follows: + +```ruby +Post.order('id DESC').limit(20).unscope(:order) = Post.limit(20) +Post.order('id DESC').limit(20).unscope(:order, :limit) = Post.all +``` + +You can additionally unscope specific where clauses. For example: + +```ruby +Post.where(:id => 10).limit(1).unscope(:where => :id, :limit).order('id DESC') = Post.order('id DESC') +``` + ### `only` You can also override conditions using the `only` method. For example: diff --git a/guides/source/migrations.md b/guides/source/migrations.md index d738d847e9..89ae564c24 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -179,6 +179,27 @@ class AddDetailsToProducts < ActiveRecord::Migration end ``` +If the migration name is of the form "CreateXXX" and is +followed by a list of column names and types then a migration creating the table +XXX with the columns listed will be generated. For example: + +```bash +$ rails generate migration CreateProducts name:string part_number:string +``` + +generates + +```ruby +class CreateProducts < ActiveRecord::Migration + def change + create_table :products do |t| + t.string :name + t.string :part_number + end + end +end +``` + As always, what has been generated for you is just a starting point. You can add or remove from it as you see fit by editing the `db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file. diff --git a/guides/source/routing.md b/guides/source/routing.md index 8c8ac34862..d7a4a237ed 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -797,6 +797,16 @@ You should put the `root` route at the top of the file, because it is the most p NOTE: The `root` route only routes `GET` requests to the action. +You can also use root inside namespaces and scopes as well. For example: + +```ruby +namespace :admin do + root to: "admin#index" +end + +root to: "home#index" +``` + ### Unicode character routes You can specify unicode character routes directly. For example: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 52f5232efc..57945a256b 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -47,6 +47,18 @@ Rails 4.0 no longer supports loading plugins from `vendor/plugins`. You must rep * Rails 4.0 has removed `attr_accessible` and `attr_protected` feature in favor of Strong Parameters. You can use the [Protected Attributes gem](https://github.com/rails/protected_attributes) to a smoothly upgrade path. +* Rails 4.0 requires that scopes use a callable object such as a Proc or lambda: + +```ruby + scope :active, where(active: true) + + # becomes + scope :active, -> { where active: true } +``` + +* Rails 4.0 has deprecated `ActiveRecord::Fixtures` in favor of `ActiveRecord::FixtureSet`. +* Rails 4.0 has deprecated `ActiveRecord::TestCase` in favor of `ActiveSupport::TestCase`. + ### Active Resource Rails 4.0 extracted Active Resource to its own gem. If you still need the feature you can add the [Active Resource gem](https://github.com/rails/activeresource) in your Gemfile. @@ -66,7 +78,16 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur ### Action Pack -* There is an upgrading cookie store `UpgradeSignatureToEncryptionCookieStore` which helps you upgrading apps that use `CookieStore` to the new default `EncryptedCookieStore`. To use this `CookieStore` set `Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: '_myapp_session'` in `config/initializers/session_store.rb`. Additionally, add `Myapp::Application.config.secret_key_base = 'some secret'` in `config/initializers/secret_token.rb`. Do not remove `Myapp::Application.config.secret_token = 'some secret'`. +* Rails 4.0 introduces a new `UpgradeSignatureToEncryptionCookieStore` cookie store. This is useful for upgrading apps using the old default `CookieStore` to the new default `EncryptedCookieStore`. To use this transitional cookie store, you'll want to leave your existing `secret_token` in place, add a new `secret_key_base`, and change your `session_store` like so: + +```ruby + # config/initializers/session_store.rb + Myapp::Application.config.session_store :upgrade_signature_to_encryption_cookie_store, key: 'existing session key' + + # config/initializers/secret_token.rb + Myapp::Application.config.secret_token = 'existing secret token' + Myapp::Application.config.secret_key_base = 'new secret key base' +``` * Rails 4.0 removed the `ActionController::Base.asset_path` option. Use the assets pipeline feature. @@ -74,6 +95,12 @@ Rails 4.0 extracted Active Resource to its own gem. If you still need the featur * Rails 4.0 has removed Action and Page caching from Action Pack. You will need to add the `actionpack-action_caching` gem in order to use `caches_action` and the `actionpack-page_caching` to use `caches_pages` in your controllers. +* Rails 4.0 has removed the XML parameters parser. You will need to add the `actionpack-xml_parser` gem if you require this feature. + +* Rails 4.0 changes the default memcached client from `memcache-client` to `dalli`. To upgrade, simply add `gem 'dalli'` to your `Gemfile`. + +* Rails 4.0 deprecates the `dom_id` and `dom_class` methods. You will need to include the `ActionView::RecordIdentifier` module in controllers requiring this feature. + * Rails 4.0 changed how `assert_generates`, `assert_recognizes`, and `assert_routing` work. Now all these assertions raise `Assertion` instead of `ActionController::RoutingError`. * Rails 4.0 also changed the way unicode character routes are drawn. Now you can draw unicode character routes directly. If you already draw such routes, you must change them, for example: @@ -88,6 +115,19 @@ becomes get 'こんにちは', controller: 'welcome', action: 'index' ``` +* Rails 4.0 requires that routes using `match` must specify the request method. For example: + +```ruby + # Rails 3.x + match "/" => "root#index" + + # becomes + match "/" => "root#index", via: :get + + # or + get "/" => "root#index" +``` + * Rails 4.0 has removed ActionDispatch::BestStandardsSupport middleware, !DOCTYPE html already triggers standards mode per http://msdn.microsoft.com/en-us/library/jj676915(v=vs.85).aspx and ChromeFrame header has been moved to `config.action_dispatch.default_headers` Remember you must also remove any references to the middleware from your application code, for example: @@ -101,6 +141,21 @@ Also check your environment settings for `config.action_dispatch.best_standards_ * In Rails 4.0, precompiling assets no longer automatically copies non-JS/CSS assets from `vendor/assets` and `lib/assets`. Rails application and engine developers should put these assets in `app/assets` or configure `config.assets.precompile`. +* In Rails 4.0, `ActionController::UnknownFormat` is raised when the action doesn't handle the request format. By default, the exception is handled by responding with 406 Not Acceptable, but you can override that now. In Rails 3, 406 Not Acceptable was always returned. No overrides. + +* In Rails 4.0, a generic `ActionDispatch::ParamsParser::ParseError` exception is raised when `ParamsParser` fails to parse request params. You will want to rescue this exception instead of the low-level `MultiJson::DecodeError`, for example. + +* In Rails 4.0, `SCRIPT_NAME` is properly nested when engines are mounted on an app that's served from a URL prefix. You no longer have to set `default_url_options[:script_name]` to work around overwritten URL prefixes. + +* Rails 4.0 deprecated `ActionController::Integration` in favor of `ActionDispatch::Integration`. +* Rails 4.0 deprecated `ActionController::IntegrationTest` in favor of `ActionDispatch::IntegrationTest`. +* Rails 4.0 deprecated `ActionController::PerformanceTest` in favor of `ActionDispatch::PerformanceTest`. +* Rails 4.0 deprecated `ActionController::AbstractRequest` in favor of `ActionDispatch::Request`. +* Rails 4.0 deprecated `ActionController::Request` in favor of `ActionDispatch::Request`. +* Rails 4.0 deprecated `ActionController::AbstractResponse` in favor of `ActionDispatch::Response`. +* Rails 4.0 deprecated `ActionController::Response` in favor of `ActionDispatch::Response`. +* Rails 4.0 deprecated `ActionController::Routing` in favor of `ActionDispatch::Routing`. + ### Active Support Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. @@ -109,6 +164,18 @@ Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already The order in which helpers from more than one directory are loaded has changed in Rails 4.0. Previously, they were gathered and then sorted alphabetically. After upgrading to Rails 4.0, helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use the `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the ordering, you should check if correct methods are available after upgrade. If you would like to change the order in which engines are loaded, you can use `config.railties_order=` method. +### Active Record Observer and Action Controller Sweeper + +Active Record Observer and Action Controller Sweeper have been extracted to the `rails-observers` gem. You will need to add the `rails-observers` gem if you require these features. + +### sprockets-rails + +* `assets:precompile:primary` has been removed. Use `assets:precompile` instead. + +### sass-rails + +* `asset_url` with two arguments is deprecated. For example: `asset-url("rails.png", image)` becomes `asset-url("rails.png")` + Upgrading from Rails 3.1 to Rails 3.2 ------------------------------------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 5efdbfecc6..4f7cb8254f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,4 +1,8 @@ +## Rails 4.0.0 (unreleased) ## + + ## Rails 4.0.0.beta1 (February 25, 2013) ## +* Change Service pages(404, etc). *Stanislav Sobolev* * Improve `rake stats` for JavaScript and CoffeeScript: ignore block comments and calculates number of functions. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5af7de720c..25cc36ff5d 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -305,22 +305,8 @@ module Rails def default_middleware_stack #:nodoc: ActionDispatch::MiddlewareStack.new.tap do |middleware| app = self - if rack_cache = config.action_dispatch.rack_cache - begin - require 'rack/cache' - rescue LoadError => error - error.message << ' Be sure to add rack-cache to your Gemfile' - raise - end - - if rack_cache == true - rack_cache = { - metastore: "rails:/", - entitystore: "rails:/", - verbose: false - } - end + if rack_cache = load_rack_cache require "action_dispatch/http/rack_cache" middleware.use ::Rack::Cache, rack_cache end @@ -337,12 +323,14 @@ module Rails middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control end - middleware.use ::Rack::Lock unless config.cache_classes + middleware.use ::Rack::Lock unless allow_concurrency? middleware.use ::Rack::Runtime middleware.use ::Rack::MethodOverride middleware.use ::ActionDispatch::RequestId - middleware.use ::Rails::Rack::Logger, config.log_tags # must come after Rack::MethodOverride to properly log overridden methods - middleware.use ::ActionDispatch::ShowExceptions, config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + + # Must come after Rack::MethodOverride to properly log overridden methods + middleware.use ::Rails::Rack::Logger, config.log_tags + middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app middleware.use ::ActionDispatch::DebugExceptions, app middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies @@ -368,6 +356,40 @@ module Rails end end + def allow_concurrency? + if config.allow_concurrency.nil? + config.cache_classes + else + config.allow_concurrency + end + end + + def load_rack_cache + rack_cache = config.action_dispatch.rack_cache + return unless rack_cache + + begin + require 'rack/cache' + rescue LoadError => error + error.message << ' Be sure to add rack-cache to your Gemfile' + raise + end + + if rack_cache == true + { + metastore: "rails:/", + entitystore: "rails:/", + verbose: false + } + else + rack_cache + end + end + + def show_exceptions_app + config.exceptions_app || ActionDispatch::PublicExceptions.new(Rails.public_path) + end + def build_original_fullpath(env) #:nodoc: path_info = env["PATH_INFO"] query_string = env["QUERY_STRING"] diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 17763b39c5..31fc80e544 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -5,7 +5,7 @@ require 'rails/engine/configuration' module Rails class Application class Configuration < ::Rails::Engine::Configuration - attr_accessor :asset_host, :assets, :autoflush_log, + attr_accessor :allow_concurrency, :asset_host, :assets, :autoflush_log, :cache_classes, :cache_store, :consider_all_requests_local, :console, :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, @@ -20,6 +20,7 @@ module Rails def initialize(*) super self.encoding = "utf-8" + @allow_concurrency = nil @consider_all_requests_local = false @filter_parameters = [] @filter_redirect = [] @@ -98,14 +99,15 @@ module Rails end # Loads and returns the configuration of the database. - # First, looks at If ENV['DATABASE_URL'] if it's not present it uses the #paths["config/database"] - # The contents of the file are processed via ERB before being sent through YAML::load. def database_configuration - if ENV['DATABASE_URL'] - {Rails.env => ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.connection_url_to_hash(ENV['DATABASE_URL']).stringify_keys} + yaml = paths["config/database"].first + if File.exists?(yaml) + require "erb" + YAML.load ERB.new(IO.read(yaml)).result + elsif ENV['DATABASE_URL'] + nil else - require 'erb' - YAML.load ERB.new(IO.read(paths["config/database"].first)).result + raise "Could not load database configuration. No such file - #{yaml}" end rescue Psych::SyntaxError => e raise "YAML syntax error occurred while parsing #{paths["config/database"].first}. " \ diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 46a6485c44..579af8c6a5 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -1,4 +1,5 @@ require 'rails/railtie' +require 'rails/engine/railties' require 'active_support/core_ext/module/delegation' require 'pathname' require 'rbconfig' @@ -467,7 +468,7 @@ module Rails end def railties - @railties ||= self.class::Railties.new + @railties ||= Railties.new end # Returns a module with all the helpers defined for the engine. diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb index e15c963971..daae72270f 100644 --- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb +++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb @@ -7,6 +7,5 @@ </p> <% end -%> - <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> | <%%= link_to 'Back', <%= index_helper %>_path %> diff --git a/railties/lib/rails/generators/rails/app/templates/public/404.html b/railties/lib/rails/generators/rails/app/templates/public/404.html index 3d875c342e..0ee82d3722 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/404.html +++ b/railties/lib/rails/generators/rails/app/templates/public/404.html @@ -2,17 +2,48 @@ <html> <head> <title>The page you were looking for doesn't exist (404)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow:0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/railties/lib/rails/generators/rails/app/templates/public/422.html b/railties/lib/rails/generators/rails/app/templates/public/422.html index 3f1bfb3417..f1f32b83ae 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/422.html +++ b/railties/lib/rails/generators/rails/app/templates/public/422.html @@ -2,17 +2,48 @@ <html> <head> <title>The change you wanted was rejected (422)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow:0 3px 8px rgba(50,50,50,0.17); + } </style> </head> @@ -22,5 +53,6 @@ <h1>The change you wanted was rejected.</h1> <p>Maybe you tried to change something you didn't have access to.</p> </div> + <p>If you are the application owner check the logs for more information.</p> </body> </html> diff --git a/railties/lib/rails/generators/rails/app/templates/public/500.html b/railties/lib/rails/generators/rails/app/templates/public/500.html index 012977d3d2..9417de0cc0 100644 --- a/railties/lib/rails/generators/rails/app/templates/public/500.html +++ b/railties/lib/rails/generators/rails/app/templates/public/500.html @@ -2,17 +2,48 @@ <html> <head> <title>We're sorry, but something went wrong (500)</title> - <style> - body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; } - div.dialog { - width: 25em; - padding: 0 4em; - margin: 4em auto 0 auto; - border: 1px solid #ccc; - border-right-color: #999; - border-bottom-color: #999; - } - h1 { font-size: 100%; color: #f00; line-height: 1.5em; } + <style> + body + { + background-color: #efefef; + color: #2E2F30; + text-align: center; + font-family: arial,sans-serif; + } + div.dialog + { + width: 25em; + margin: 4em auto 0 auto; + border: 1px solid #CCC; + border-right-color: #999; + border-left-color: #999; + border-bottom-color: #BBB; + border-top: #B00100 solid 4px; + border-top-left-radius: 9px; + border-top-right-radius: 9px; + background-color: white; + padding: 7px 4em 0 4em; + } + h1{ + font-size: 100%; + color: #730E15; + line-height: 1.5em; + } + body>p + { + width: 33em; + margin: 0 auto 1em; + padding: 1em 0; + background-color: #f7f7f7; + border: 1px solid #CCC; + border-right-color: #999; + border-bottom-color: #999; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-top-color: #DADADA; + color: #666; + box-shadow:0 3px 8px rgba(50,50,50,0.17); + } </style> </head> diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index af00748037..5fe01d0961 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -94,6 +94,11 @@ task default: :test end end + def test_dummy_assets + template "rails/javascripts.js", "#{dummy_path}/app/assets/javascripts/application.js", force: true + template "rails/stylesheets.css", "#{dummy_path}/app/assets/stylesheets/application.css", force: true + end + def test_dummy_clean inside dummy_path do remove_file ".gitignore" @@ -112,7 +117,7 @@ task default: :test def stylesheets if mountable? - copy_file "#{app_templates_dir}/app/assets/stylesheets/application.css", + copy_file "rails/stylesheets.css", "app/assets/stylesheets/#{name}/application.css" elsif full? empty_directory_with_keep_file "app/assets/stylesheets/#{name}" @@ -123,8 +128,8 @@ task default: :test return if options.skip_javascript? if mountable? - template "#{app_templates_dir}/app/assets/javascripts/application.js.tt", - "app/assets/javascripts/#{name}/application.js" + template "rails/javascripts.js", + "app/assets/javascripts/#{name}/application.js" elsif full? empty_directory_with_keep_file "app/assets/javascripts/#{name}" end @@ -263,6 +268,7 @@ task default: :test build(:generate_test_dummy) store_application_definition! build(:test_dummy_config) + build(:test_dummy_assets) build(:test_dummy_clean) # ensure that bin/rails has proper dummy_path build(:bin, true) diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec index e956d13d8a..f7c12e67dd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec +++ b/railties/lib/rails/generators/rails/plugin_new/templates/%name%.gemspec @@ -19,9 +19,6 @@ Gem::Specification.new do |s| <% end -%> <%= '# ' if options.dev? || options.edge? -%>s.add_dependency "rails", "~> <%= Rails::VERSION::STRING %>" -<% if engine? && !options[:skip_javascript] -%> - # s.add_dependency "<%= "#{options[:javascript]}-rails" %>" -<% end -%> <% unless options[:skip_active_record] -%> s.add_development_dependency "<%= gem_for_database %>" diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile index a8b5bfaf3f..3f2b78f2fd 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile +++ b/railties/lib/rails/generators/rails/plugin_new/templates/Gemfile @@ -2,9 +2,6 @@ source "https://rubygems.org" <% if options[:skip_gemspec] -%> <%= '# ' if options.dev? || options.edge? -%>gem "rails", "~> <%= Rails::VERSION::STRING %>" -<% if engine? && !options[:skip_javascript] -%> -# gem "<%= "#{options[:javascript]}-rails" %>" -<% end -%> <% else -%> # Declare your gem's dependencies in <%= name %>.gemspec. # Bundler will treat runtime dependencies like base dependencies, and @@ -12,11 +9,6 @@ source "https://rubygems.org" gemspec <% end -%> -<% unless options[:javascript] == 'jquery' -%> -# jquery-rails is used by the dummy application -gem "jquery-rails" - -<% end -%> <% if options[:skip_gemspec] -%> group :development do gem "<%= gem_for_database %>" diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js new file mode 100644 index 0000000000..084d5d1c49 --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js @@ -0,0 +1,13 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require_tree . diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css b/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css new file mode 100644 index 0000000000..3192ec897b --- /dev/null +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/stylesheets.css @@ -0,0 +1,13 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the top of the + * compiled file, but it's generally better to create a new file per style scope. + * + *= require_self + *= require_tree . + */ diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 72281a2fef..73e89086a5 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -56,7 +56,7 @@ class <%= controller_class_name %>Controller < ApplicationController @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> end - # Never trust parameters from the scary internet, only allow the white list through. + # Only allow a trusted parameter "white list" through. def <%= "#{singular_table_name}_params" %> <%- if attributes_names.empty? -%> params[<%= ":#{singular_table_name}" %>] diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 73ef2046c0..d8076c7151 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -96,6 +96,12 @@ module ApplicationTests assert !middleware.include?("Rack::Lock") end + test "removes lock if allow concurrency is set" do + add_to_config "config.allow_concurrency = true" + boot! + assert !middleware.include?("Rack::Lock") + end + test "removes static asset server if serve_static_assets is disabled" do add_to_config "config.serve_static_assets = false" boot! diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 820b838702..9e711f25bd 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -166,19 +166,6 @@ module ApplicationTests require "#{app_path}/config/environment" db_test_load_structure end - - test 'db:test:load_structure with database_url' do - old_rails_env = ENV["RAILS_ENV"] - ENV["RAILS_ENV"] = 'test' - - begin - require "#{app_path}/config/environment" - set_database_url - db_test_load_structure - ensure - ENV["RAILS_ENV"] = old_rails_env - end - end end end end diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 62d9d1f06a..d876597944 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -172,8 +172,19 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end - def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove - migration = "create_books" + def test_create_table_migration + run_generator ["create_books", "title:string", "content:text"] + assert_migration "db/migrate/create_books.rb" do |content| + assert_method :change, content do |change| + assert_match(/create_table :books/, change) + assert_match(/ t\.string :title/, change) + assert_match(/ t\.text :content/, change) + end + end + end + + def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove_or_create + migration = "delete_books" run_generator [migration, "title:string", "content:text"] assert_migration "db/migrate/#{migration}.rb" do |content| @@ -182,7 +193,7 @@ class MigrationGeneratorTest < Rails::Generators::TestCase end end end - + def test_properly_identifies_usage_file assert generator_class.send(:usage_path) end diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 904c1db57e..34441ef679 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -63,13 +63,24 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_directory "test/integration/" assert_no_file "test" - assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + assert_file "Rakefile" do |contents| + assert_no_match(/APP_RAKEFILE/, contents) + end end def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app'] + assert_file "Rakefile", /APP_RAKEFILE/ + end + + def test_generating_adds_dummy_app_without_javascript_and_assets_deps + run_generator [destination_root] + + assert_file "test/dummy/app/assets/stylesheets/application.css" - assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile"))) + assert_file "test/dummy/app/assets/javascripts/application.js" do |contents| + assert_no_match(/jquery/, contents) + end end def test_ensure_that_plugin_options_are_not_passed_to_app_generator @@ -112,7 +123,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_ensure_that_skip_active_record_option_is_passed_to_app_generator run_generator [destination_root, "--skip_active_record"] assert_no_file "test/dummy/config/database.yml" - assert_no_match(/ActiveRecord/, File.read(File.join(destination_root, "test/test_helper.rb"))) + assert_file "test/test_helper.rb" do |contents| + assert_no_match /ActiveRecord/, contents + end end def test_ensure_that_database_option_is_passed_to_app_generator @@ -134,8 +147,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase def test_skipping_javascripts_without_mountable_option run_generator assert_no_file "app/assets/javascripts/bukkits/application.js" - assert_no_file "vendor/assets/javascripts/jquery.js" - assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_javascripts_generation @@ -143,33 +154,9 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/javascripts/bukkits/application.js" end - def test_jquery_is_the_default_javascript_library - run_generator [destination_root, "--mountable"] - assert_file "app/assets/javascripts/bukkits/application.js" do |contents| - assert_match %r{^//= require jquery}, contents - assert_match %r{^//= require jquery_ujs}, contents - end - assert_file 'bukkits.gemspec' do |contents| - assert_match(/jquery-rails/, contents) - end - end - - def test_other_javascript_libraries - run_generator [destination_root, "--mountable", '-j', 'prototype'] - assert_file "app/assets/javascripts/bukkits/application.js" do |contents| - assert_match %r{^//= require prototype}, contents - assert_match %r{^//= require prototype_ujs}, contents - end - assert_file 'bukkits.gemspec' do |contents| - assert_match(/prototype-rails/, contents) - end - end - def test_skip_javascripts run_generator [destination_root, "--skip-javascript", "--mountable"] assert_no_file "app/assets/javascripts/bukkits/application.js" - assert_no_file "vendor/assets/javascripts/jquery.js" - assert_no_file "vendor/assets/javascripts/jquery_ujs.js" end def test_template_from_dir_pwd @@ -318,16 +305,6 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase assert_no_match('gemspec', contents) assert_match(/gem "rails", "~> #{Rails::VERSION::STRING}"/, contents) assert_match(/group :development do\n gem "sqlite3"\nend/, contents) - assert_match(/# gem "jquery-rails"/, contents) - assert_no_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents) - end - end - - def test_skipping_gemspec_in_full_mode_with_javascript_option - run_generator [destination_root, "--skip-gemspec", "--full", "--javascript=prototype"] - assert_file "Gemfile" do |contents| - assert_match(/# gem "prototype-rails"/, contents) - assert_match(/# jquery-rails is used by the dummy application\ngem "jquery-rails"/, contents) end end |