diff --git a/Gemfile b/Gemfile index 661ef25a57cddb72d74357d4d5b54f6810cb1916..04562fe1d9053413a1b16a6eb454a3ac624750b9 100644 --- a/Gemfile +++ b/Gemfile @@ -60,11 +60,6 @@ platforms :jruby do gem 'json' gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.7' - # This is needed by now to let tests work on JRuby - # TODO: When the JRuby guys merge jruby-openssl in - # jruby this will be removed - gem 'jruby-openssl' - group :db do gem 'activerecord-jdbcmysql-adapter', '>= 1.2.7' gem 'activerecord-jdbcpostgresql-adapter', '>= 1.2.7' diff --git a/RAILS_VERSION b/RAILS_VERSION index 0db186f5d2e43e1d0de42be3764b5afac9f655b2..fcdb2e109f68cff5600955a73908885fe8599bb4 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -4.0.0.rc1 +4.0.0 diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 975f61fa58595cb676ec5dcbdd69e10836522220..495a87ad723b8c49f49902f459ab6393b654b33f 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,7 +1,4 @@ -## Rails 4.0.0 (unreleased) ## - - -## Rails 4.0.0.beta1 (February 25, 2013) ## +## Rails 4.0.0 (June 25, 2013) ## * Allow passing interpolations to `#default_i18n_subject`, e.g.: diff --git a/actionmailer/README.rdoc b/actionmailer/README.rdoc index 4d78d6142a426dc3c5ac5d014651b8ddd42af3ed..a14a6ba18f0ced978fc306d6926deb8b951d17aa 100644 --- a/actionmailer/README.rdoc +++ b/actionmailer/README.rdoc @@ -67,12 +67,12 @@ simply call the method and optionally call +deliver+ on the return value. Calling the method returns a Mail Message object: - message = Notifier.welcome # => Returns a Mail::Message object - message.deliver # => delivers the email + message = Notifier.welcome("david@loudthinking.com") # => Returns a Mail::Message object + message.deliver # => delivers the email Or you can just chain the methods together like: - Notifier.welcome.deliver # Creates the email and sends it immediately + Notifier.welcome("david@loudthinking.com").deliver # Creates the email and sends it immediately == Setting defaults @@ -119,8 +119,7 @@ trivial case like this: rails runner 'Mailman.receive(STDIN.read)' However, invoking Rails in the runner for each mail to be received is very resource intensive. A single -instance of Rails should be run within a daemon, if it is going to be utilized to process more than just -a limited number of email. +instance of Rails should be run within a daemon, if it is going to process more than just a limited amount of email. == Configuration diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index ff74185e37080149d3136f5ac66aec44e50583b5..1fff958ceb04ae818fd8ff56e192bfa519d1675a 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -334,8 +334,8 @@ module ActionMailer # and starts to use it. # * :openssl_verify_mode - When using TLS, you can set how OpenSSL checks the certificate. This is # really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name - # of an OpenSSL verify constant ('none', 'peer', 'client_once','fail_if_no_peer_cert') or directly the - # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER,...). + # of an OpenSSL verify constant ('none', 'peer', 'client_once', 'fail_if_no_peer_cert') or directly the + # constant (OpenSSL::SSL::VERIFY_NONE, OpenSSL::SSL::VERIFY_PEER, ...). # # * sendmail_settings - Allows you to override options for the :sendmail delivery method. # * :location - The location of the sendmail executable. Defaults to /usr/sbin/sendmail. @@ -596,9 +596,9 @@ def attachments # class method: # # class Notifier < ActionMailer::Base - # self.default from: 'no-reply@test.lindsaar.net', - # bcc: 'email_logger@test.lindsaar.net', - # reply_to: 'bounces@test.lindsaar.net' + # default from: 'no-reply@test.lindsaar.net', + # bcc: 'email_logger@test.lindsaar.net', + # reply_to: 'bounces@test.lindsaar.net' # end # # If you need other headers not listed above, you can either pass them in diff --git a/actionmailer/lib/action_mailer/version.rb b/actionmailer/lib/action_mailer/version.rb index ab67f629299b51e27063cd862455906160681fa1..caf05349e5e0710a0500fb11b2cfb628f1155ef8 100644 --- a/actionmailer/lib/action_mailer/version.rb +++ b/actionmailer/lib/action_mailer/version.rb @@ -1,7 +1,7 @@ module ActionMailer # Returns the version of the currently loaded ActionMailer as a Gem::Version def self.version - Gem::Version.new "4.0.0.rc1" + Gem::Version.new "4.0.0" end module VERSION #:nodoc: diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ new file mode 100644 index 0000000000000000000000000000000000000000..946d99ede50e305ee3e1b95eaf5bcdc3ec06de2d --- /dev/null +++ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.html.erb~ @@ -0,0 +1,10 @@ + + + HTML formatted message to <%= @recipient %>. + + + + + HTML formatted message to <%= @recipient %>. + + diff --git a/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak new file mode 100644 index 0000000000000000000000000000000000000000..6940419d47a1eee4b6bb40398a6983b459861e7c --- /dev/null +++ b/actionmailer/test/fixtures/test_mailer/implicitly_multipart_example.rhtml.bak @@ -0,0 +1 @@ +Ignored when searching for implicitly multipart parts. diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index 20b6671283c8d1bbf3f25614e24b1f74fa97fea1..6584bf519505d8e8c89d027a87adda6e46e6c32b 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -38,7 +38,7 @@ def attachment_with_content(hash = {}) end def attachment_with_hash - attachments['invoice.jpg'] = { data: "\312\213\254\232)b", + attachments['invoice.jpg'] = { data: ::Base64.encode64("\312\213\254\232)b"), mime_type: "image/x-jpg", transfer_encoding: "base64" } mail diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e30029fb6d4d52e7e0916fed0b8519d28d746eef..3c72f4ac78ca650e9ab0ca9c697cd77eb42a77eb 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,4 +1,38 @@ -## Rails 4.0.0 (unreleased) ## +## Rails 4.0.0 (June 25, 2013) ## + +* Merge `:action` from routing scope and assign endpoint if both `:controller` + and `:action` are present. The endpoint assignment only occurs if there is + no `:to` present in the options hash so should only affect routes using the + shorthand syntax (i.e. endpoint is inferred from the the path). + + Fixes #9856 + + *Yves Senn*, *Andrew White* + +* Use a case insensitive URI Regexp for #asset_path. + + This fix a problem where the same asset path using different case are generating + different URIs. + + Before: + + image_tag("HTTP://google.com") + # => "\"Google\"" + image_tag("http://google.com") + # => "\"Google\"" + + After: + + image_tag("HTTP://google.com") + # => "\"Google\"" + image_tag("http://google.com") + # => "\"Google\"" + + *David Celis* + +* Fix an issue where partials with a number in the filename weren't being digested for cache dependencies. + + *Bryan Ricker* * Add support for passing custom url options other than `:host` and custom status and flash options to `force_ssl`. @@ -181,9 +215,6 @@ *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* * New applications use an encrypted session store by default. @@ -1058,6 +1089,14 @@ *Andrew White* +* In the routes DSL the `:via` option of `match` is now mandatory. + + For routes that respond to one single verb it is recommended to use the more specific + macros `get`, `post`, etc. instead. You can still map all HTTP verbs to one action + with `match`, but it has to be explictly configured using `:via => :all`. + + *José Valim and Yehuda Katz* + * Add `index` method to FormBuilder class. *Jorge Bejar* * Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino* @@ -1106,8 +1145,6 @@ *dlee* -* Integration tests support the `OPTIONS` method. *Jeremy Kemper* - * `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate" is added to the Cache-Control header. *fxn* diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 505f3b4e6157dfb9a04125cbed33184f41ab20e1..754249cbc84c9a8aa5cbe703f65157d3d88def30 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -32,7 +32,8 @@ def url_options if (same_origin = _routes.equal?(env["action_dispatch.routes"])) || (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) || - (original_script_name = env['SCRIPT_NAME']) + (original_script_name = env['ORIGINAL_SCRIPT_NAME']) + @_url_options.dup.tap do |options| if original_script_name options[:original_script_name] = original_script_name diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 41a286f0ed44072934c885a841f9af3de3b627dc..186be380f2f9dce525bad8c9125a9f44c03330eb 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -499,12 +499,6 @@ def head(action, *args) process(action, "HEAD", *args) end - # Simulate a OPTIONS request with the given parameters and set/volley the response. - # See +get+ for more details. - def options(action, *args) - process(action, "OPTIONS", *args) - end - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 246d9c121adaa0816844bd1630dce016cc87afed..20c24ddd859c3ecd03f680c2f74688b46f8b2a1d 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -65,7 +65,7 @@ def normalize_encode_params(params) new_hash = {} params.each do |k, v| - new_key = k.is_a?(String) ? k.dup.force_encoding("UTF-8").encode! : k + new_key = k.is_a?(String) ? k.dup.force_encoding(Encoding::UTF_8).encode! : k new_hash[new_key] = case v when Hash diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 5b914f293d7a09ce18bfcd7dc509c4ddd33b17de..d055acb296d4873ab30692afb58fe821fc7a7a19 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -77,7 +77,7 @@ def cookie_jar # domain and subdomains. # # * :expires - The time at which this cookie expires, as a \Time object. - # * :secure - Whether this cookie is a only transmitted to HTTPS servers. + # * :secure - Whether this cookie is only transmitted to HTTPS servers. # Default is +false+. # * :httponly - Whether this cookie is accessible via scripting or # only HTTP. Defaults to +false+. diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 0a19381c896d2f9deab8cfa5d3f1e1af0d832781..1de3d1453099bbdef7f0848f60e9b8df241a9e9d 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -13,6 +13,7 @@ class ExceptionWrapper 'ActionController::NotImplemented' => :not_implemented, 'ActionController::UnknownFormat' => :not_acceptable, 'ActionController::InvalidAuthenticityToken' => :unprocessable_entity, + 'ActionDispatch::ParamsParser::ParseError' => :bad_request, 'ActionController::BadRequest' => :bad_request, 'ActionController::ParameterMissing' => :bad_request ) diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 53bedaa40a298e51e270497a68a79c3f20dc1080..cbb2d475b1e03d2c3231f7cd773761391a4cc155 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -7,11 +7,10 @@ def initialize(public_path) end def call(env) - exception = env["action_dispatch.exception"] status = env["PATH_INFO"][1..-1] request = ActionDispatch::Request.new(env) content_type = request.formats.first - body = { :status => status, :error => exception.message } + body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status.to_i, Rack::Utils::HTTP_STATUS_CODES[500]) } render(status, content_type, body) end @@ -19,7 +18,7 @@ def call(env) private def render(status, content_type, body) - format = content_type && "to_#{content_type.to_sym}" + format = "to_#{content_type.to_sym}" if content_type if format && body.respond_to?(format) render_format(status, content_type, body.public_send(format)) else diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 891c87ac27316589758302480dcf8f65c37f3d1c..bc5d03dc1098481caf828df0a64b3552b4ab39a8 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -34,6 +34,12 @@ padding: 0.5em 1.5em; } + h1 { + margin: 0.2em 0; + line-height: 1.1em; + font-size: 2em; + } + h2 { color: #C52F24; line-height: 25px; diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index d251de33df0de1dcbd8537fab23a9f880a2b9e23..cffb814e1e750377ef12c4928aff13a893279e13 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -69,7 +69,7 @@ def action end def internal? - controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}} + controller.to_s =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}} end def engine? diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 1611812ab472271c14352ae19cb406b8cae48989..df2818bf36c36dc3e5c514fb4cc4b7afbaa7cfd7 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -11,8 +11,8 @@ module Routing class Mapper URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port] SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module, - :controller, :path_names, :constraints, :defaults, - :shallow, :blocks, :options] + :controller, :action, :path_names, :constraints, + :shallow, :blocks, :defaults, :options] class Constraints #:nodoc: def self.new(app, constraints, request = Rack::Request) @@ -299,7 +299,7 @@ def default_action end end - # Invokes Rack::Mount::Utils.normalize path and ensure that + # Invokes Journey::Router::Utils.normalize_path and ensure that # (:locale) becomes (/:locale) instead of /(:locale). Except # for root cases, where the latter is the correct one. def self.normalize_path(path) @@ -869,6 +869,10 @@ def merge_controller_scope(parent, child) #:nodoc: child end + def merge_action_scope(parent, child) #:nodoc: + child + end + def merge_path_names_scope(parent, child) #:nodoc: merge_options_scope(parent, child) end @@ -1378,6 +1382,10 @@ def match(path, *rest) raise ArgumentError, "Unknown scope #{on.inspect} given to :on" end + if @scope[:controller] && @scope[:action] + options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}" + end + paths.each do |_path| route_options = options.dup route_options[:path] ||= _path if _path.is_a?(String) diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 342b6ec23d1090b450c4fe24339b3172e0624c3b..3ae9f92c0bac8ff82859420a251f5bffc754dc4b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -218,6 +218,7 @@ def handle_positional_args(t, args, options, keys) keys -= t.url_options.keys if t.respond_to?(:url_options) keys -= options.keys end + keys -= inner_options.keys result.merge!(Hash[keys.zip(args)]) end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 56c31255f39a2956246a04f179b94b3ded6057fb..70b42ccd1b36bb1038d01a3a59f8d7e570dd1b60 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -62,12 +62,6 @@ def head(path, parameters = nil, headers_or_env = nil) process :head, path, parameters, headers_or_env end - # Performs a OPTIONS request with the given parameters. See +#get+ for - # more details. - def options(path, parameters = nil, headers_or_env = nil) - process :options, path, parameters, headers_or_env - end - # Performs an XMLHttpRequest request with the given parameters, mirroring # a request from the Prototype library. # @@ -342,7 +336,7 @@ def reset! @integration_session = Integration::Session.new(app) end - %w(get post patch put head delete options cookies assigns + %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless integration_session diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index b16dbd4f15975b338caf5f77f9b19621cd3aef85..c48b12b9b8fffdd0434c16db5d3e088401c3c08d 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -1,7 +1,7 @@ module ActionPack # Returns the version of the currently loaded ActionPack as a Gem::Version def self.version - Gem::Version.new "4.0.0.rc1" + Gem::Version.new "4.0.0" end module VERSION #:nodoc: diff --git a/actionpack/lib/action_view/dependency_tracker.rb b/actionpack/lib/action_view/dependency_tracker.rb index a2a555dfcb6deb6f1f73bd4c0eca0e5251874c92..45d17be6055325b4d85a8c1f9001760108b6eaf9 100644 --- a/actionpack/lib/action_view/dependency_tracker.rb +++ b/actionpack/lib/action_view/dependency_tracker.rb @@ -39,7 +39,7 @@ class ERBTracker render\s* # render, followed by optional whitespace \(? # start an optional parenthesis for the render call (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture - ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture + ([@a-z"'][@\w\/\."']+) # the template name itself -- 2nd capture /x def self.call(name, template) diff --git a/actionpack/lib/action_view/helpers/asset_url_helper.rb b/actionpack/lib/action_view/helpers/asset_url_helper.rb index 71b78cf0b54edfc7488e5fb957c0e50cb90ff70b..0b957adb91174c53a3b4883c83f13bec02a6dd71 100644 --- a/actionpack/lib/action_view/helpers/asset_url_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_url_helper.rb @@ -105,7 +105,7 @@ module Helpers # ) # module AssetUrlHelper - URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//} + URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i # Computes the path to asset in public directory. If :type # options is set, a file extension will be appended and scoped @@ -193,7 +193,6 @@ def compute_asset_host(source = "", options = {}) request = self.request if respond_to?(:request) host = config.asset_host if defined? config.asset_host host ||= request.base_url if request && options[:protocol] == :request - return unless host if host.respond_to?(:call) arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity @@ -204,6 +203,8 @@ def compute_asset_host(source = "", options = {}) host = host % (Zlib.crc32(source) % 4) end + return unless host + if host =~ URI_REGEXP host else diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 8abd5d6e9c524104897694987a37b284f6399b71..c10566a87d572567ba5a46afe7b57eeb4d4c10e5 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -81,7 +81,7 @@ def form_tag(url_for_options = {}, options = {}, &block) # ==== Options # * :multiple - If set to true the selection will allow multiple choices. # * :disabled - If set to true, the user will not be able to use this input. - # * :include_blank - If set to true, an empty option will be create + # * :include_blank - If set to true, an empty option will be created. # * :prompt - Create a prompt option with blank value and the text asking user to select something # * Any other key creates standard HTML attributes for the tag. # diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index d61cc0f304bb7d8393cf06eda5df89377056f42b..f9d5b97fe3655d52c6dec8c5ed64509043e2ba3b 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,5 +1,6 @@ require 'thread_safe' require 'active_support/core_ext/module/remove_method' +require 'active_support/core_ext/module/attribute_accessors' module ActionView # = Action View Lookup Context diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index c3bdf74d93f8eba3ac1f44a1f8f62f5113042490..f7ec6d71b3e778224207610adc96918b2651a2cf 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -117,12 +117,6 @@ def test_head @session.head(path,params,headers) end - def test_options - path = "/index"; params = "blah"; headers = {:location => 'blah'} - @session.expects(:process).with(:options,path,params,headers) - @session.options(path,params,headers) - end - def test_xml_http_request_get path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( @@ -183,16 +177,6 @@ def test_xml_http_request_head @session.xml_http_request(:head,path,params,headers) end - def test_xml_http_request_options - path = "/index"; params = "blah"; headers = {:location => 'blah'} - headers_after_xhr = headers.merge( - "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", - "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" - ) - @session.expects(:process).with(:options,path,params,headers_after_xhr) - @session.xml_http_request(:options,path,params,headers) - end - def test_xml_http_request_override_accept path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"} headers_after_xhr = headers.merge( @@ -250,7 +234,7 @@ def test_integration_methods_called @integration_session.stubs(:generic_url_rewriter) @integration_session.stubs(:process) - %w( get post head patch put delete options ).each do |verb| + %w( get post head patch put delete ).each do |verb| assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } end end diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 69bf4b7720b83efe2d3f34ab1ec0e7f0441073ab..ff23b220404755cb73cfa0595059750757f3233e 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -75,7 +75,7 @@ def test_render_json_exception get "/", {}, 'HTTP_ACCEPT' => 'application/json' assert_response :internal_server_error assert_equal 'application/json', response.content_type.to_s - assert_equal({ :status => '500', :error => 'boom!' }.to_json, response.body) + assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_json, response.body) end def test_render_xml_exception @@ -83,7 +83,7 @@ def test_render_xml_exception get "/", {}, 'HTTP_ACCEPT' => 'application/xml' assert_response :internal_server_error assert_equal 'application/xml', response.content_type.to_s - assert_equal({ :status => '500', :error => 'boom!' }.to_xml, response.body) + assert_equal({ :status => '500', :error => 'Internal Server Error' }.to_xml, response.body) end def test_render_fallback_exception diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 38b9794b4d9c677cb7777eca3ec623d2da456bfe..7c27458f46660b5acedff1a486e8fd4c09a33178 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -201,11 +201,6 @@ def test_head_params_as_sting assert_raise(NoMethodError) { head :test_params, "document body", :id => 10 } end - def test_options - options :test_params - assert_equal 200, @response.status - end - def test_process_without_flash process :set_flash assert_equal '><', flash['test'] diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index 7d3fc84089da084184aa91678937aff72a3994a2..8d0a845f1519c431693e48cdc50044ffe3bd7f6e 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -50,7 +50,7 @@ def teardown output = StringIO.new json = "[\"person]\": {\"name\": \"David\"}}" post "/parse", json, {'CONTENT_TYPE' => 'application/json', 'action_dispatch.show_exceptions' => true, 'action_dispatch.logger' => ActiveSupport::Logger.new(output)} - assert_response :error + assert_response :bad_request output.rewind && err = output.read assert err =~ /Error occurred while parsing request parameters/ end diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 234ae5764f0cd343ab63ec0337a016a06ccd99dd..4f97d28d2b03ee592546edb6919a2cdc4d8a6031 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -234,6 +234,15 @@ def test_routes_can_be_filtered " PUT /posts/:id(.:format) posts#update", " DELETE /posts/:id(.:format) posts#destroy"], output end + + def test_regression_route_with_controller_regexp + output = draw do + get ':controller(/:action)', controller: /api\/[^\/]+/, format: false + end + + assert_equal ["Prefix Verb URI Pattern Controller#Action", + " GET /:controller(/:action) (?-mix:api\\/[^\\/]+)#:action"], output + end end end end diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb index d57b1a56375d62f06a653adda0bfc04068ec9aa6..0e488d2b8878ccc6ecc7caf91b150b6eeb917d33 100644 --- a/actionpack/test/dispatch/routing/route_set_test.rb +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -69,6 +69,17 @@ def call(env) end end + test "explicit keys win over implicit keys" do + draw do + resources :foo do + resources :bar, to: SimpleApp.new('foo#show') + end + end + + assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(1, 2) + assert_equal '/foo/1/bar/2', url_helpers.foo_bar_path(2, foo_id: 1) + end + private def clear! @set.clear! diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 5b42ca0f21bc7f4d985c21c9d9f6e1ca157e0b5f..3133a141351c855b102cefa653955a4031c52770 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1253,6 +1253,19 @@ def test_match_shorthand_inside_nested_namespaces_and_scopes_with_controller assert_equal 'api/v3/products#list', @response.body end + def test_controller_option_with_nesting_and_leading_slash + draw do + scope '/job', controller: 'job' do + scope ':id', action: 'manage_applicant' do + get "/active" + end + end + end + + get '/job/5/active' + assert_equal 'job#manage_applicant', @response.body + end + def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper draw do resources :replies do diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 98bbcd954bea48211d68e34053f6b08193e62fd3..38bd234f3729e60defe08dcab2dc02425c80596c 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -8,6 +8,8 @@ def call(env) case req.path when "/not_found" raise AbstractController::ActionNotFound + when "/bad_params" + raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new) when "/method_not_allowed" raise ActionController::MethodNotAllowed when "/unknown_http_method" @@ -35,6 +37,10 @@ def call(env) get "/", {}, {'action_dispatch.show_exceptions' => true} assert_response 500 assert_equal "500 error fixture\n", body + + get "/bad_params", {}, {'action_dispatch.show_exceptions' => true} + assert_response 400 + assert_equal "400 error fixture\n", body get "/not_found", {}, {'action_dispatch.show_exceptions' => true} assert_response 404 diff --git a/actionpack/test/fixtures/digestor/messages/show.html.erb b/actionpack/test/fixtures/digestor/messages/show.html.erb index 51b3b61e8ef9a20deae9e1eebc7fbcbd102e4436..130e67109ebf468d1bc534f6ca348bbabf0fd60f 100644 --- a/actionpack/test/fixtures/digestor/messages/show.html.erb +++ b/actionpack/test/fixtures/digestor/messages/show.html.erb @@ -7,7 +7,8 @@ <%= render @message.history.events %> <%# render "something_missing" %> +<%# render "something_missing_1" %> <% # Template Dependency: messages/form -%> \ No newline at end of file +%> diff --git a/actionpack/test/fixtures/public/400.html b/actionpack/test/fixtures/public/400.html new file mode 100644 index 0000000000000000000000000000000000000000..03be6bedafaed2a55673806f7e71a16b76eafba5 --- /dev/null +++ b/actionpack/test/fixtures/public/400.html @@ -0,0 +1 @@ +400 error fixture diff --git a/actionpack/test/fixtures/test/hello_world.erb~ b/actionpack/test/fixtures/test/hello_world.erb~ new file mode 100644 index 0000000000000000000000000000000000000000..21934a1c959161c6e0b71c944fd9b6640419e404 --- /dev/null +++ b/actionpack/test/fixtures/test/hello_world.erb~ @@ -0,0 +1 @@ +Don't pick me! \ No newline at end of file diff --git a/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ b/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ new file mode 100644 index 0000000000000000000000000000000000000000..d009950384409abaaf156f4db51a655af294b800 --- /dev/null +++ b/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ @@ -0,0 +1 @@ +Don't render me! \ No newline at end of file diff --git a/actionpack/test/fixtures/test/malformed/malformed.erb~ b/actionpack/test/fixtures/test/malformed/malformed.erb~ new file mode 100644 index 0000000000000000000000000000000000000000..d009950384409abaaf156f4db51a655af294b800 --- /dev/null +++ b/actionpack/test/fixtures/test/malformed/malformed.erb~ @@ -0,0 +1 @@ +Don't render me! \ No newline at end of file diff --git a/actionpack/test/fixtures/test/malformed/malformed.html.erb~ b/actionpack/test/fixtures/test/malformed/malformed.html.erb~ new file mode 100644 index 0000000000000000000000000000000000000000..d009950384409abaaf156f4db51a655af294b800 --- /dev/null +++ b/actionpack/test/fixtures/test/malformed/malformed.html.erb~ @@ -0,0 +1 @@ +Don't render me! \ No newline at end of file diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 11614a45dc513bdd1135c63f1e20aef25373db74..2bf96cbe849e804c2601a205f9694c11396220b0 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -48,6 +48,9 @@ def url_for(*args) %(asset_path("style.min")) => %(/style.min), %(asset_path("style.min.css")) => %(/style.min.css), + %(asset_path("http://www.outside.com/image.jpg")) => %(http://www.outside.com/image.jpg), + %(asset_path("HTTP://www.outside.com/image.jpg")) => %(HTTP://www.outside.com/image.jpg), + %(asset_path("style", type: :stylesheet)) => %(/stylesheets/style.css), %(asset_path("xmlhr", type: :javascript)) => %(/javascripts/xmlhr.js), %(asset_path("xml.png", type: :image)) => %(/images/xml.png) @@ -443,8 +446,8 @@ def test_image_alt [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| assert_equal 'Rails', image_alt("#{prefix}rails.png") assert_equal 'Rails', image_alt("#{prefix}rails-9c0a079bdd7701d7e729bd956823d153.png") - assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png") - assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png") + assert_equal 'Long file name with hyphens', image_alt("#{prefix}long-file-name-with-hyphens.png") + assert_equal 'Long file name with underscores', image_alt("#{prefix}long_file_name_with_underscores.png") end end @@ -530,6 +533,17 @@ def test_should_not_modify_source_string assert_equal copy, source end + def test_image_path_with_asset_host_proc_returning_nil + @controller.config.asset_host = Proc.new do |source| + unless source.end_with?("tiff") + "cdn.example.com" + end + end + + assert_equal "/images/file.tiff", image_path("file.tiff") + assert_equal "http://cdn.example.com/images/file.png", image_path("file.png") + end + def test_caching_image_path_with_caching_and_proc_asset_host_using_request @controller.config.asset_host = Proc.new do |source, request| if request.ssl? diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb index e29cecabc03c5ff71af342d718f404dc0a2a2639..06735c30d380bdd161888f3671af31b503829ad6 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionpack/test/template/digestor_test.rb @@ -83,6 +83,12 @@ def test_logging_of_missing_template end end + def test_logging_of_missing_template_ending_with_number + assert_logged "Couldn't find template for digesting: messages/something_missing_1.html" do + digest("messages/show") + end + end + def test_nested_template_directory assert_digest_difference("messages/show") do change_template("messages/actions/_move") diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index a1f3d081db20882af4eb38807c95aec103c2350f..b0acf99f8299ff5a7902409ec58928b560dd4842 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,4 +1,9 @@ -## Rails 4.0.0 (unreleased) ## +## Rails 4.0.0 (June 25, 2013) ## + +* Fix regression in has_secure_password. When a password is set, but a + confirmation is an empty string, it would incorrectly save. + + *Steve Klabnik* and *Phillip Calvin* * Add `ActiveModel::Errors#full_messages_for`, to return all the error messages for a given attribute. @@ -68,8 +73,6 @@ *Yves Senn* -## Rails 4.0.0.beta1 (February 25, 2013) ## - * Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the absence of attributes. diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 1be2913f0b182a0169457acb7c90f45bc059affb..46b446dc085d0a0f35312a3d8475ada013add4a1 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -98,7 +98,7 @@ def test_errors_aref private def model - assert @model.respond_to?(:to_model), "The object should respond_to to_model" + assert @model.respond_to?(:to_model), "The object should respond to to_model" @model.to_model end diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index 62383a03e8c9a2959ed49b1f3ddcfeef3213d36e..f048dda5c6103aba8adc12751eff64f24041da6b 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -79,6 +79,8 @@ def initialize(params={}) params.each do |attr, value| self.public_send("#{attr}=", value) end if params + + super() end # Indicates if the model is persisted. Default is +false+. diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index de8a641924a067d953c71caa53c5152f3559936a..393eb9f2a191848c312c6ea8521ae614efe4b1c7 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -56,8 +56,9 @@ def has_secure_password(options = {}) include InstanceMethodsOnActivation if options.fetch(:validations, true) - validates_confirmation_of :password + validates_confirmation_of :password, if: lambda { |m| m.password.present? } validates_presence_of :password, :on => :create + validates_presence_of :password_confirmation, if: lambda { |m| m.password.present? } before_create { raise "Password digest missing on new record" if password_digest.blank? } end @@ -106,9 +107,7 @@ def password=(unencrypted_password) end def password_confirmation=(unencrypted_password) - unless unencrypted_password.blank? - @password_confirmation = unencrypted_password - end + @password_confirmation = unencrypted_password end end end diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index b0afb7513d9caa271bc0746ec6afc1752c9a2a35..4d0196455f766f75a63b5d9c47db3c2fd46ccb0b 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,7 +1,7 @@ module ActiveModel # Returns the version of the currently loaded ActiveModel as a Gem::Version def self.version - Gem::Version.new "4.0.0.rc1" + Gem::Version.new "4.0.0" end module VERSION #:nodoc: diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index 588d8e661e251a735245c766c9caa87c169d7d56..f13e51cecb8ddee997b78ad0b781926f1231f672 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -3,7 +3,30 @@ class ModelTest < ActiveModel::TestCase include ActiveModel::Lint::Tests + module DefaultValue + def self.included(klass) + klass.class_eval { attr_accessor :hello } + end + + def initialize(*args) + @attr ||= 'default value' + super + end + end + class BasicModel + include DefaultValue + include ActiveModel::Model + attr_accessor :attr + end + + class BasicModelWithReversedMixins + include ActiveModel::Model + include DefaultValue + attr_accessor :attr + end + + class SimpleModel include ActiveModel::Model attr_accessor :attr end @@ -13,15 +36,21 @@ def setup end def test_initialize_with_params - object = BasicModel.new(:attr => "value") - assert_equal object.attr, "value" + object = BasicModel.new(attr: "value") + assert_equal "value", object.attr + end + + def test_initialize_with_params_and_mixins_reversed + object = BasicModelWithReversedMixins.new(attr: "value") + assert_equal "value", object.attr end def test_initialize_with_nil_or_empty_hash_params_does_not_explode assert_nothing_raised do BasicModel.new() - BasicModel.new nil + BasicModel.new(nil) BasicModel.new({}) + SimpleModel.new(attr: 'value') end end @@ -29,4 +58,18 @@ def test_persisted_is_always_false object = BasicModel.new(:attr => "value") assert object.persisted? == false end + + def test_mixin_inclusion_chain + object = BasicModel.new + assert_equal 'default value', object.attr + end + + def test_mixin_initializer_when_args_exist + object = BasicModel.new(hello: 'world') + assert_equal 'world', object.hello + end + + def test_mixin_initializer_when_args_dont_exist + assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') } + end end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 02cd3b8a93d0c2e588ee82f53754056e41e9249e..0b900d934d37c279db48ee75cf8cf516eeab352d 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -94,4 +94,13 @@ class SecurePasswordTest < ActiveModel::TestCase @user.password_confirmation = "" assert @user.valid?(:update), "user should be valid" end + + test "will not save if confirmation is blank but password is not" do + @user.password = "password" + @user.password_confirmation = "" + assert_not @user.valid?(:create) + + @user.password_confirmation = "password" + assert @user.valid?(:create) + end end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 5ba7a6fa7580ce966606b9ca9c2f9d448c3ab4a2..c5f1aadbb7d4e596905413d34bed99bc1b68319a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,60 @@ -## Rails 4.0.0 (unreleased) ## +## Rails 4.0.0 (June 25, 2013) ## + +* Fix `add_column` with `array` option when using PostgreSQL. Fixes #10432 + +* Do not overwrite manually built records during one-to-one nested attribute assignment + + For one-to-one nested associations, if you build the new (in-memory) + child object yourself before assignment, then the NestedAttributes + module will not overwrite it, e.g.: + + class Member < ActiveRecord::Base + has_one :avatar + accepts_nested_attributes_for :avatar + + def avatar + super || build_avatar(width: 200) + end + end + + member = Member.new + member.avatar_attributes = {icon: 'sad'} + member.avatar.width # => 200 + + *Olek Janiszewski* + +* fixes bug introduced by #3329. Now, when autosaving associations, + deletions happen before inserts and saves. This prevents a 'duplicate + unique value' database error that would occur if a record being created had + the same value on a unique indexed field as that of a record being destroyed. + + *Adam Anderson* + + +* Fix pending migrations error when loading schema and `ActiveRecord::Base.table_name_prefix` + is not blank. + + Call `assume_migrated_upto_version` on connection to prevent it from first + being picked up in `method_missing`. + + In the base class, `Migration`, `method_missing` expects the argument to be a + table name, and calls `proper_table_name` on the arguments before sending to + `connection`. If `table_name_prefix` or `table_name_suffix` is used, the schema + version changes to `prefix_version_suffix`, breaking `rake test:prepare`. + + Fixes #10411. + + *Kyle Stevens* + +* Mute `psql` output when running rake db:schema:load. + + *Godfrey Chan* + +* Trigger a save on `has_one association=(associate)` when the associate contents have changed. + + Fix #8856. + + *Chris Thompson* * Allow to use databases.rake tasks without having `Rails.application`. @@ -534,9 +590,6 @@ # 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* @@ -928,6 +981,11 @@ *Aaron Stone* +* Allow setting of all libpq connection parameters through the PostgreSQL adapter. See also: + http://www.postgresql.org/docs/9.2/static/libpq-connect.html#LIBPQ-PARAMKEYWORDS + + *Lars Kanis* + * Allow `Relation#where` with no arguments to be chained with new `not` query method. Example: diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index db0553ea76a0422cb2170e7a3e9fba46d28ccb8c..710babe02455c01cccb8eea99da6359d88dd680c 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -164,6 +164,13 @@ def marshal_load(data) @reflection = @owner.class.reflect_on_association(reflection_name) end + def initialize_attributes(record) #:nodoc: + skip_assign = [reflection.foreign_key, reflection.type].compact + attributes = create_scope.except(*(record.changed - skip_assign)) + record.assign_attributes(attributes) + set_inverse_instance(record) + end + private def find_target? @@ -233,10 +240,7 @@ def stale_state def build_record(attributes) reflection.build_association(attributes) do |record| - skip_assign = [reflection.foreign_key, reflection.type].compact - attributes = create_scope.except(*(record.changed - skip_assign)) - record.assign_attributes(attributes) - set_inverse_instance(record) + initialize_attributes(record) end end end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 98bd010f702600a2d47c179743e7c43d3c003ffa..920038a54338b1a4171afc4879305271b13ca075 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -25,9 +25,8 @@ def replace(record, save = true) raise_on_type_mismatch!(record) if record load_target - # If target and record are nil, or target is equal to record, - # we don't need to have transaction. - if (target || record) && target != record + return self.target if !(target || record) + if (target != record) || record.changed? transaction_if(save) do remove_target!(options[:dependent]) if target && !target.destroyed? diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 7f1ebab4cdc9e43f5c16bbef8c95db5c1b6fb7e9..336750fdce359b7c9c62567a9f8c0e4631af3edb 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -63,7 +63,11 @@ def initialize(column) end def type_cast(value) - value.unserialized_value + if value.state == :serialized + value.unserialized_value @column.type_cast value.value + else + value.unserialized_value + end end def type @@ -72,17 +76,17 @@ def type end class Attribute < Struct.new(:coder, :value, :state) # :nodoc: - def unserialized_value - state == :serialized ? unserialize : value + def unserialized_value(v = value) + state == :serialized ? unserialize(v) : value end def serialized_value state == :unserialized ? serialize : value end - def unserialize + def unserialize(v) self.state = :unserialized - self.value = coder.load(value) + self.value = coder.load(v) end def serialize diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 44323ce9db6ad13f7bbd5abcfdfdb9c68cc64296..356d407bb13eb29b30eac0635e891848a9cf3719 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -17,7 +17,8 @@ module ActiveRecord # be destroyed directly. They will however still be marked for destruction. # # Note that autosave: false is not same as not declaring :autosave. - # When the :autosave option is not present new associations are saved. + # When the :autosave option is not present then new association records are + # saved but the updated association records are not saved. # # == Validation # @@ -300,7 +301,7 @@ def validate_collection_association(reflection) def association_valid?(reflection, record) return true if record.destroyed? || record.marked_for_destruction? - unless valid = record.valid?(validation_context) + unless valid = record.valid? if reflection.options[:autosave] record.errors.each do |attribute, message| attribute = "#{reflection.name}.#{attribute}" 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 566550cbe289ba5daa0b14601ab58d1861f754bb..c982d65d651289c45af729303de32f2885e96c92 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -65,8 +65,7 @@ def columns; @columns_hash.values; end # Appends a primary key definition to the table definition. # Can be called multiple times, but this is probably not a good idea. def primary_key(name, type = :primary_key, options = {}) - options[:primary_key] = true - column(name, type, options) + column(name, type, options.merge(:primary_key => true)) end # Returns a ColumnDefinition for the column with name +name+. @@ -270,6 +269,7 @@ def new_column_definition(name, type, options) # :nodoc: end column.limit = limit + column.array = options[:array] if column.respond_to?(:array) column.precision = options[:precision] column.scale = options[:scale] column.default = options[:default] 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 9c0c4e3ef0e41aae3d4ee0145b983218af90e40e..890f0a9148039b2bd5dd1719c100958961228f6d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -214,8 +214,8 @@ def create_table(table_name, options = {}) # its block form to do so yourself: # # create_join_table :products, :categories do |t| - # t.index :products - # t.index :categories + # t.index :product_id + # t.index :category_id # end # # ====== Add a backend specific option to the generated SQL (MySQL) 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 76c501dec599bea531699ab8b6e2271bc24347ea..d098ded77c4bd8231591a033fc1a20c24b09bc47 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -364,7 +364,9 @@ def empty_insert_statement_value # and creates it again using the provided +options+. def recreate_database(name, options = {}) drop_database(name) - create_database(name, options) + sql = create_database(name, options) + reconnect! + sql end # Create a new MySQL database with optional :charset and :collation. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bf403c3ae07325a5086b4a2b9a3d9cb62d069c4d..6040eeed005bfb2bfb31c74c289b4f5d46602d5d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -330,9 +330,38 @@ def json(name, options = {}) class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods + # Defines the primary key field. + # Use of the native PostgreSQL UUID type is supported, and can be used + # by defining your tables as such: + # + # create_table :stuffs, id: :uuid do |t| + # t.string :content + # t.timestamps + # end + # + # By default, this will use the +uuid_generate_v4()+ function from the + # +uuid-ossp+ extension, which MUST be enabled on your databse. To enable + # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your + # migrations To use a UUID primary key without +uuid-ossp+ enabled, you can + # set the +:default+ option to nil: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: nil + # t.uuid :foo_id + # t.timestamps + # end + # + # You may also pass a different UUID generation function from +uuid-ossp+ + # or another library. + # + # Note that setting the UUID primary key default value to +nil+ + # will require you to assure that you always provide a UUID value + # before saving a record (as primary keys cannot be nil). This might be + # done via the SecureRandom.uuid method and a +before_save+ callback, + # for instance. def primary_key(name, type = :primary_key, options = {}) return super unless type == :uuid - options[:default] ||= 'uuid_generate_v4()' + options[:default] = options.fetch(:default, 'uuid_generate_v4()') options[:primary_key] = true column name, type, options end diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index a3bc56d6004b5187e63cc53dccc416522fbdcc3e..6a499366444ab74f50591abb8571b61167cf0c08 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -19,7 +19,7 @@ def finish(name, id, payload) # On the other hand, we want to monitor the performance of our real database # queries, not the performance of the access to the query cache. IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE) - EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)/i + EXPLAINED_SQLS = /\A\s*(select|update|delete|insert)\b/i def ignore_payload?(payload) payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) || payload[:sql] !~ EXPLAINED_SQLS end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 6c020e1d579189493303e8bbea31c7184417eff8..40c4f8569708490e7412dd57be03a3fe8dfbea70 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -357,11 +357,14 @@ class Migration class CheckPending def initialize(app) @app = app + @last_check = 0 end def call(env) - ActiveRecord::Base.logger.silence do + mtime = ActiveRecord::Migrator.last_migration.mtime.to_i + if @last_check < mtime ActiveRecord::Migration.check_pending! + @last_check = mtime end @app.call(env) end @@ -699,6 +702,10 @@ def basename File.basename(filename) end + def mtime + File.mtime filename + end + delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration private @@ -714,6 +721,16 @@ def load_migration end + class NullMigration < MigrationProxy #:nodoc: + def initialize + super(nil, 0, nil, nil) + end + + def mtime + 0 + end + end + class Migrator#:nodoc: class << self attr_writer :migrations_paths @@ -784,7 +801,11 @@ def needs_migration? end def last_version - migrations(migrations_paths).last.try(:version)||0 + last_migration.version + end + + def last_migration #:nodoc: + migrations(migrations_paths).last || NullMigration.new end def proper_table_name(name) diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index d607f49e2b0f6d14a907dc9a29fb9382d8961647..021832de46ea0f2bbe87e38c6d36ed57700e5b62 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -229,6 +229,23 @@ class TooManyRecords < ActiveRecordError # belongs_to :member, inverse_of: :posts # validates_presence_of :member # end + # + # For one-to-one nested associations, if you build the new (in-memory) + # child object yourself before assignment, then this module will not + # overwrite it, e.g.: + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar + # + # def avatar + # super || build_avatar(width: 200) + # end + # end + # + # member = Member.new + # member.avatar_attributes = {icon: 'sad'} + # member.avatar.width # => 200 module ClassMethods REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } @@ -356,20 +373,28 @@ def _destroy def assign_nested_attributes_for_one_to_one_association(association_name, attributes) options = self.nested_attributes_options[association_name] attributes = attributes.with_indifferent_access + existing_record = send(association_name) - if (options[:update_only] || !attributes['id'].blank?) && (record = send(association_name)) && - (options[:update_only] || record.id.to_s == attributes['id'].to_s) - assign_to_or_mark_for_destruction(record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) + if (options[:update_only] || !attributes['id'].blank?) && existing_record && + (options[:update_only] || existing_record.id.to_s == attributes['id'].to_s) + assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy]) unless call_reject_if(association_name, attributes) elsif attributes['id'].present? raise_nested_attributes_record_not_found!(association_name, attributes['id']) elsif !reject_new_record?(association_name, attributes) - method = "build_#{association_name}" - if respond_to?(method) - send(method, attributes.except(*UNASSIGNABLE_KEYS)) + assignable_attributes = attributes.except(*UNASSIGNABLE_KEYS) + + if existing_record && existing_record.new_record? + existing_record.assign_attributes(assignable_attributes) + association(association_name).initialize_attributes(existing_record) else - raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + method = "build_#{association_name}" + if respond_to?(method) + send(method, assignable_attributes) + else + raise ArgumentError, "Cannot build association `#{association_name}'. Are you trying to build a polymorphic one-to-one association?" + end end end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 59f8e90e7a3932534e1b0f2f002226f1ec531d51..1714baefe198625e3739228b3851ab9021c4d0b2 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -99,6 +99,9 @@ def persisted? # before_* callbacks return +false+ the action is cancelled and # +save+ returns +false+. See ActiveRecord::Callbacks for further # details. + # + # Attributes marked as readonly are silently ignored if the record is + # being updated. def save(*) create_or_update rescue ActiveRecord::RecordInvalid @@ -118,6 +121,9 @@ def save(*) # the before_* callbacks return +false+ the action is cancelled # and save! raises ActiveRecord::RecordNotSaved. See # ActiveRecord::Callbacks for further details. + # + # Attributes marked as readonly are silently ignored if the record is + # being updated. def save!(*) create_or_update || raise(RecordNotSaved) end @@ -204,6 +210,8 @@ def becomes!(klass) # * updated_at/updated_on column is updated if that column is available. # * Updates all the attributes that are dirty in this object. # + # This method raises an +ActiveRecord::ActiveRecordError+ if the + # attribute is marked as readonly. def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) @@ -325,10 +333,44 @@ def toggle!(attribute) toggle(attribute).update_attribute(attribute, self[attribute]) end - # Reloads the attributes of this object from the database. - # The optional options argument is passed to find when reloading so you - # may do e.g. record.reload(lock: true) to reload the same record with - # an exclusive row lock. + # Reloads the record from the database. + # + # This method modifies the receiver in-place. Attributes are updated, and + # caches busted, in particular the associations cache. + # + # If the record no longer exists in the database ActiveRecord::RecordNotFound + # is raised. Otherwise, in addition to the in-place modification the method + # returns +self+ for convenience. + # + # The optional :lock flag option allows you to lock the reloaded record: + # + # reload(lock: true) # reload with pessimistic locking + # + # Reloading is commonly used in test suites to test something is actually + # written to the database, or when some action modifies the corresponding + # row in the database but not the object in memory: + # + # assert account.deposit!(25) + # assert_equal 25, account.credit # check it is updated in memory + # assert_equal 25, account.reload.credit # check it is also persisted + # + # Another commom use case is optimistic locking handling: + # + # def with_optimistic_retry + # begin + # yield + # rescue ActiveRecord::StaleObjectError + # begin + # # Reload lock_version in particular. + # reload + # rescue ActiveRecord::RecordNotFound + # # If the record is gone there is nothing to do. + # else + # retry + # end + # end + # end + # def reload(options = nil) clear_aggregation_cache clear_association_cache diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index e36888d4a831f810dea13498790d43d51610fafb..afb0be7b74987fc50b450cbc2449b24d94c00340 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -37,16 +37,21 @@ class Railtie < Rails::Railtie # :nodoc: rake_tasks do require "active_record/base" - ActiveRecord::Tasks::DatabaseTasks.env = Rails.env - ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first ActiveRecord::Tasks::DatabaseTasks.seed_loader = Rails.application - ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration - ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a - ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' + ActiveRecord::Tasks::DatabaseTasks.env = Rails.env + + namespace :db do + task :load_config do + ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first + ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration + ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a + ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) - if engine.paths['db/migrate'].existent - ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a + if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if engine.paths['db/migrate'].existent + ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a + end + end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index bb9e390c8fbaa09a16a56a7b4eaf6039bf6b01a8..6b5d47af3f87c3d9fa8bf1b8673c0cb2e1bb323a 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -321,9 +321,13 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent schema.rb file" task :load_schema => 'db:test:purge' do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) - ActiveRecord::Schema.verbose = false - db_namespace["schema:load"].invoke + begin + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + db_namespace["schema:load"].invoke + ensure + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) + end end # desc "Recreate the test database from an existent structure.sql file" @@ -358,7 +362,7 @@ db_namespace = namespace :db do end # desc 'Check for pending migrations and load the test schema' - task :prepare do + task :prepare => :load_config do unless ActiveRecord::Base.configurations.blank? db_namespace['test:load'].invoke end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 56462d355b6cb7522433838e5b74cc9bd2221081..8222fce1e670e1e6ef0d97b3b2e5b56cd4fa94ba 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -141,34 +141,58 @@ def first_or_initialize(attributes = nil, &block) # :nodoc: first || new(attributes, &block) end - # Finds the first record with the given attributes, or creates a record with the attributes - # if one is not found. + # Finds the first record with the given attributes, or creates a record + # with the attributes if one is not found: # - # ==== Examples - # # Find the first user named Penélope or create a new one. + # # Find the first user named "Penélope" or create a new one. # User.find_or_create_by(first_name: 'Penélope') - # # => + # # => # # - # # Find the first user named Penélope or create a new one. + # # Find the first user named "Penélope" or create a new one. # # We already have one so the existing record will be returned. # User.find_or_create_by(first_name: 'Penélope') - # # => + # # => # # - # # Find the first user named Scarlett or create a new one with a particular last name. + # # Find the first user named "Scarlett" or create a new one with + # # a particular last name. # User.create_with(last_name: 'Johansson').find_or_create_by(first_name: 'Scarlett') - # # => + # # => # # - # # Find the first user named Scarlett or create a new one with a different last name. - # # We already have one so the existing record will be returned. + # This method accepts a block, which is passed down to +create+. The last example + # above can be alternatively written this way: + # + # # Find the first user named "Scarlett" or create a new one with a + # # different last name. # User.find_or_create_by(first_name: 'Scarlett') do |user| - # user.last_name = "O'Hara" + # user.last_name = 'Johansson' # end - # # => + # # => # + # + # This method always returns a record, but if creation was attempted and + # failed due to validation errors it won't be persisted, you get what + # +create+ returns in such situation. + # + # Please note *this method is not atomic*, it runs first a SELECT, and if + # there are no results an INSERT is attempted. If there are other threads + # or processes there is a race condition between both calls and it could + # be the case that you end up with two similar records. + # + # Whether that is a problem or not depends on the logic of the + # application, but in the particular case in which rows have a UNIQUE + # constraint an exception may be raised, just retry: + # + # begin + # CreditAccount.find_or_create_by(user_id: user.id) + # rescue ActiveRecord::RecordNotUnique + # retry + # end + # def find_or_create_by(attributes, &block) find_by(attributes) || create(attributes, &block) end - # Like find_or_create_by, but calls create! so an exception is raised if the created record is invalid. + # Like find_or_create_by, but calls create! so an exception + # is raised if the created record is invalid. def find_or_create_by!(attributes, &block) find_by(attributes) || create!(attributes, &block) end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 1b6239eb385be06223c6c601078443d71bd718fa..8d6740246cd2a394dde6f154e8d6cb693a91d660 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -14,14 +14,14 @@ module Delegation # :nodoc: delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, :to => :klass - module ClassSpecificRelation + module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern included do @delegation_mutex = Mutex.new end - module ClassMethods + module ClassMethods # :nodoc: def name superclass.name end @@ -70,7 +70,7 @@ def method_missing(method, *args, &block) end end - module ClassMethods + module ClassMethods # :nodoc: @@subclasses = ThreadSafe::Cache.new(:initial_capacity => 2) def new(klass, *args) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 3259dbbd80f3fd8ecab3c83d672e106582f35490..4bfd0167a4d6c110875dfd871bb0f322206a137c 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -43,7 +43,7 @@ def define(info, &block) # :nodoc: unless info[:version].blank? initialize_schema_migrations_table - assume_migrated_upto_version(info[:version], migrations_paths) + connection.assume_migrated_upto_version(info[:version], migrations_paths) end end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index 0b1b030516a060ea23246e448b9d0e925654bc80..4413330fab7f602cb071169862474ab92bbdeda9 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -59,7 +59,7 @@ def structure_dump(filename) def structure_load(filename) set_psql_env - Kernel.system("psql -f #{filename} #{configuration['database']}") + Kernel.system("psql -q -f #{filename} #{configuration['database']}") end private diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 7f1972ccf91133ad55e9d5f43cbc952e56e62e21..b4785d3ba45a58f5c6fd1759906f2e377a641b26 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -2,15 +2,15 @@ module ActiveRecord module Validations class AssociatedValidator < ActiveModel::EachValidator #:nodoc: def validate_each(record, attribute, value) - if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?(record.validation_context) }.any? + if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any? record.errors.add(attribute, :invalid, options.merge(:value => value)) end end end module ClassMethods - # Validates whether the associated object or objects are all valid - # themselves. Works with any kind of association. + # Validates whether the associated object or objects are all valid. + # Works with any kind of association. # # class Book < ActiveRecord::Base # has_many :pages diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index a670e18aeeac88f18ed49430d7b270cb2e7ae0f1..bb97bfcc821d4e60abea2d7ddca588e6df2bb6ee 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,7 +1,7 @@ module ActiveRecord # Returns the version of the currently loaded ActiveRecord as a Gem::Version def self.version - Gem::Version.new "4.0.0.rc1" + Gem::Version.new "4.0.0" end module VERSION #:nodoc: diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index e6d0183b119efa94e247cb3312cefc46f4556e81..0878925a6ca7ec45df811d1a9b2606eb70da0183 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -2,25 +2,24 @@ class ActiveSchemaTest < ActiveRecord::TestCase def setup - ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do + @connection = ActiveRecord::Base.remove_connection + ActiveRecord::Base.establish_connection(@connection) + + ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute - remove_method :execute def execute(sql, name = nil) return sql end end end def teardown - ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do - remove_method :execute - alias_method :execute, :execute_without_stub - end + ActiveRecord::Base.remove_connection + ActiveRecord::Base.establish_connection(@connection) end def test_add_index # add_index calls index_name_exists? which can't work since execute is stubbed - ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*| - false - end + def (ActiveRecord::Base.connection).index_name_exists?(*); false; end + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :length => nil) @@ -58,8 +57,6 @@ def test_add_index expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) - - ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?) end def test_drop_table @@ -121,22 +118,20 @@ def test_remove_timestamps private def with_real_execute - #we need to actually modify some data, so we make execute point to the original method - ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do + ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_with_stub, :execute remove_method :execute alias_method :execute, :execute_without_stub end + yield ensure - #before finishing, we restore the alias to the mock-up method - ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do + ActiveRecord::Base.connection.singleton_class.class_eval do remove_method :execute alias_method :execute, :execute_with_stub end end - def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 8a2a7ef2698a88883f4a35babd5893d895b7901e..4ccf568406c29027cadf019d222ad5edcef46de5 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -2,25 +2,24 @@ class ActiveSchemaTest < ActiveRecord::TestCase def setup - ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + @connection = ActiveRecord::Base.remove_connection + ActiveRecord::Base.establish_connection(@connection) + + ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_without_stub, :execute - remove_method :execute def execute(sql, name = nil) return sql end end end def teardown - ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do - remove_method :execute - alias_method :execute, :execute_without_stub - end + ActiveRecord::Base.remove_connection + ActiveRecord::Base.establish_connection(@connection) end def test_add_index # add_index calls index_name_exists? which can't work since execute is stubbed - ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*| - false - end + def (ActiveRecord::Base.connection).index_name_exists?(*); false; end + expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) " assert_equal expected, add_index(:people, :last_name, :length => nil) @@ -58,8 +57,6 @@ def test_add_index expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree) - - ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?) end def test_drop_table @@ -121,22 +118,20 @@ def test_remove_timestamps private def with_real_execute - #we need to actually modify some data, so we make execute point to the original method - ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + ActiveRecord::Base.connection.singleton_class.class_eval do alias_method :execute_with_stub, :execute remove_method :execute alias_method :execute, :execute_without_stub end + yield ensure - #before finishing, we restore the alias to the mock-up method - ActiveRecord::ConnectionAdapters::Mysql2Adapter.class_eval do + ActiveRecord::Base.connection.singleton_class.class_eval do remove_method :execute alias_method :execute, :execute_with_stub end end - def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) end diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index d7d77f96e20d69007aa7311b8ec181106dff37a7..489efac932b20177cda0f4d9d0c489989082d6c5 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -15,6 +15,7 @@ def setup @connection.transaction do @connection.create_table('bytea_data_type') do |t| t.binary 'payload' + t.binary 'serialized' end end end @@ -84,4 +85,20 @@ def test_write_nil assert_equal(nil, record.payload) assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) end + + class Serializer + def load(str); str; end + def dump(str); str; end + end + + def test_serialize + klass = Class.new(ByteaDataType) { + serialize :serialized, Serializer.new + } + obj = klass.new + obj.serialized = "hello world" + obj.save! + obj.reload + assert_equal "hello world", obj.serialized + end end diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 8c173722869aa8e4b1bc043d61326f9ec728e26e..b5d7ea603e32c8c6ee21f890c0b12640be053429 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -281,7 +281,6 @@ def test_tsrange_values tz = ::ActiveRecord::Base.default_timezone assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)..Time.send(tz, 2011, 1, 1, 14, 30, 0), @first_range.ts_range assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Time.send(tz, 2011, 1, 1, 14, 30, 0), @second_range.ts_range - assert_equal Time.send(tz, 2010, 1, 1, 14, 30, 0)...Float::INFINITY, @third_range.ts_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.ts_range) assert_equal nil, @empty_range.ts_range end @@ -290,7 +289,6 @@ def test_tstzrange_values skip "PostgreSQL 9.2 required for range datatypes" unless @connection.supports_ranges? assert_equal Time.parse('2010-01-01 09:30:00 UTC')..Time.parse('2011-01-01 17:30:00 UTC'), @first_range.tstz_range assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Time.parse('2011-01-01 17:30:00 UTC'), @second_range.tstz_range - assert_equal Time.parse('2010-01-01 09:30:00 UTC')...Float::INFINITY, @third_range.tstz_range assert_equal(-Float::INFINITY...Float::INFINITY, @fourth_range.tstz_range) assert_equal nil, @empty_range.tstz_range end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index c0c0e8898cb12dfde52d74483980aa2610ee4904..57cea20adeec68ee075f89f44847fa8877258b7d 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -51,3 +51,65 @@ def test_auto_create_uuid assert_not_nil u.other_uuid end end + +class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase + class UUID < ActiveRecord::Base + self.table_name = 'pg_uuids' + end + + def setup + @connection = ActiveRecord::Base.connection + + @connection.reconnect! + + @connection.transaction do + @connection.create_table('pg_uuids', id: false) do |t| + t.primary_key :id, :uuid, default: nil + t.string 'name' + end + end + end + + def teardown + @connection.execute 'drop table if exists pg_uuids' + end + + def test_id_allows_default_override_via_nil + col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default + FROM pg_attribute a + LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum + WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first + assert_nil col_desc["default"] + end +end + +class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase + class UUID < ActiveRecord::Base + self.table_name = 'pg_uuids' + end + + def setup + @connection = ActiveRecord::Base.connection + + @connection.reconnect! + + @connection.transaction do + @connection.create_table('pg_uuids', id: false) do |t| + t.primary_key :id, :uuid, default: nil + t.string 'name' + end + end + end + + def teardown + @connection.execute 'drop table if exists pg_uuids' + end + + def test_id_allows_default_override_via_nil + col_desc = @connection.execute("SELECT pg_get_expr(d.adbin, d.adrelid) as default + FROM pg_attribute a + LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum + WHERE a.attname='id' AND a.attrelid = 'pg_uuids'::regclass").first + assert_nil col_desc["default"] + end +end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 244e0b7179d5413c2758f0e8f60f04d5ee8c8e36..500df52cd8a9e901ea15846cd08a0cc0cfe5f26a 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -12,6 +12,8 @@ def setup def teardown @connection.drop_table :fruits rescue nil + @connection.drop_table :nep_fruits rescue nil + @connection.drop_table :nep_schema_migrations rescue nil ActiveRecord::SchemaMigration.delete_all rescue nil end @@ -30,6 +32,24 @@ def test_schema_define assert_equal 7, ActiveRecord::Migrator::current_version end + def test_schema_define_w_table_name_prefix + table_name = ActiveRecord::SchemaMigration.table_name + ActiveRecord::Base.table_name_prefix = "nep_" + ActiveRecord::SchemaMigration.table_name = "nep_#{table_name}" + ActiveRecord::Schema.define(:version => 7) do + create_table :fruits do |t| + t.column :color, :string + t.column :fruit_size, :string # NOTE: "size" is reserved in Oracle + t.column :texture, :string + t.column :flavor, :string + end + end + assert_equal 7, ActiveRecord::Migrator::current_version + ensure + ActiveRecord::Base.table_name_prefix = "" + ActiveRecord::SchemaMigration.table_name = table_name + end + def test_schema_raises_an_error_for_invalid_column_type assert_raise NoMethodError do ActiveRecord::Schema.define(:version => 8) do diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 4ed09a3bf7008f392ac31ac03536a7a7092cc75d..0e48fbca9c1c341e53e16eaf8e08ea2bfd397344 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -522,4 +522,20 @@ def test_has_one_transaction account = Account.find(2) assert_queries { company.account = account } end + + def test_has_one_assignment_triggers_save_on_change + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + ship = pirate.build_ship(name: 'old name') + ship.save! + + ship.name = 'new name' + assert ship.changed? + assert_queries(2) do + # One query for updating name and second query for updating pirate_id + pirate.ship = ship + end + + assert_equal 'new name', pirate.ship.reload.name + end + end diff --git a/activerecord/test/cases/attribute_methods/serialization_test.rb b/activerecord/test/cases/attribute_methods/serialization_test.rb new file mode 100644 index 0000000000000000000000000000000000000000..75de773961c6a3ed00b0b526fb427178cc49096e --- /dev/null +++ b/activerecord/test/cases/attribute_methods/serialization_test.rb @@ -0,0 +1,29 @@ +require "cases/helper" + +module ActiveRecord + module AttributeMethods + class SerializationTest < ActiveSupport::TestCase + class FakeColumn < Struct.new(:name) + def type; :integer; end + def type_cast(s); "#{s}!"; end + end + + class NullCoder + def load(v); v; end + end + + def test_type_cast_serialized_value + value = Serialization::Attribute.new(NullCoder.new, "Hello world", :serialized) + type = Serialization::Type.new(FakeColumn.new) + assert_equal "Hello world!", type.type_cast(value) + end + + def test_type_cast_unserialized_value + value = Serialization::Attribute.new(nil, "Hello world", :unserialized) + type = Serialization::Type.new(FakeColumn.new) + type.type_cast(value) + assert_equal "Hello world", type.type_cast(value) + end + end + end +end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb index fb53a92c8956441b277cbf8bf465b7fd29f6b1d0..b00e2744b91ab386844b34bacbff3afc6a4aed16 100644 --- a/activerecord/test/cases/explain_subscriber_test.rb +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -43,6 +43,11 @@ def test_collects_nothing_if_the_statement_is_not_whitelisted assert queries.empty? end + def test_collects_nothing_if_the_statement_is_only_partially_matched + SUBSCRIBER.finish(nil, nil, name: 'SQL', sql: 'select_db yo_mama') + assert queries.empty? + end + def teardown ActiveRecord::ExplainRegistry.reset end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index cad759bba982bdb44dc050241eec075f7414b29d..8efe733337eb7b7316c3d2158a6a24ce87c047ae 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -74,6 +74,35 @@ def test_create_table_with_defaults assert_equal "hello", five.default unless mysql end + def test_add_column_with_array + if current_adapter?(:PostgreSQLAdapter) + connection.create_table :testings + connection.add_column :testings, :foo, :string, :array => true + + columns = connection.columns(:testings) + array_column = columns.detect { |c| c.name == "foo" } + + assert array_column.array + else + skip "array option only supported in PostgreSQLAdapter" + end + end + + def test_create_table_with_array_column + if current_adapter?(:PostgreSQLAdapter) + connection.create_table :testings do |t| + t.string :foo, :array => true + end + + columns = connection.columns(:testings) + array_column = columns.detect { |c| c.name == "foo" } + + assert array_column.array + else + skip "array option only supported in PostgreSQLAdapter" + end + end + def test_create_table_with_limits connection.create_table :testings do |t| t.column :foo, :string, :limit => 255 diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 193ffb26e3d2b72e826482bd19752700b5fa2203..e99312c24542fa3879f83dcd6c96ace6bdcaa40e 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -849,4 +849,13 @@ def test_copying_migrations_to_empty_directory ensure clear end + + def test_check_pending_with_stdlib_logger + old, ActiveRecord::Base.logger = ActiveRecord::Base.logger, ::Logger.new($stdout) + quietly do + assert_nothing_raised { ActiveRecord::Migration::CheckPending.new(Proc.new {}).call({}) } + end + ensure + ActiveRecord::Base.logger = old + end end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index b6e140b912195b75e78bf3d566258fc971a90eec..165b7454b77871126e2ce97ba48c76b167947adb 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -131,6 +131,20 @@ def test_reject_if_with_a_proc_which_returns_true_always_for_has_one assert_equal 's1', ship.reload.name end + def test_reuse_already_built_new_record + pirate = Pirate.new + ship_built_first = pirate.build_ship + pirate.ship_attributes = { name: 'Ship 1' } + assert_equal ship_built_first.object_id, pirate.ship.object_id + end + + def test_do_not_allow_assigning_foreign_key_when_reusing_existing_new_record + pirate = Pirate.create!(catchphrase: "Don' botharrr talkin' like one, savvy?") + pirate.build_ship + pirate.ship_attributes = { name: 'Ship 1', pirate_id: pirate.id + 1 } + assert_equal pirate.id, pirate.ship.pirate_id + end + def test_reject_if_with_a_proc_which_returns_true_always_for_has_many Man.accepts_nested_attributes_for :interests, :reject_if => proc {|attributes| true } man = Man.create(name: "John") diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index 7e7a469edd7b4ed5e69066405199c8fd9e9a5fe3..f31896bc7f46b614fbf47cfa003a8f53bb098279 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -227,7 +227,7 @@ def setup def test_structure_load filename = "awesome-file.sql" - Kernel.expects(:system).with("psql -f #{filename} my-app-db") + Kernel.expects(:system).with("psql -q -f #{filename} my-app-db") ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) end diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index 7ac34bc71eae41c697dc228e1e576b745e856882..7e92a2b127f394cfbfd27f4b1b49fd0382a0df40 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -118,21 +118,4 @@ def test_validates_presence_of_belongs_to_association__existing_parent end end - def test_validates_associated_models_in_the_same_context - Topic.validates_presence_of :title, :on => :custom_context - Topic.validates_associated :replies - Reply.validates_presence_of :title, :on => :custom_context - - t = Topic.new('title' => '') - r = t.replies.new('title' => '') - - assert t.valid? - assert !t.valid?(:custom_context) - - t.title = "Longer" - assert !t.valid?(:custom_context), "Should NOT be valid if the associated object is not valid in the same context." - - r.title = "Longer" - assert t.valid?(:custom_context), "Should be valid if the associated object is not valid in the same context." - end end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 21add846a86da18bdd2ece8a6ef12d8ec92a1667..39820dfaae55586ec5bd48c84596d46e8b665062 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,8 +1,16 @@ -## Rails 4.0.0 (unreleased) ## +## Rails 4.0.0 (June 25, 2013) ## -* Fix skipping of filters defined by objects in `ActiveSupport::Callbacks::Callback`. +* Override `Time.at` to support the passing of Time-like values when called with a single argument. - *Ben McRedmond* + *Andrew White* + +* Allow Date to be compared with Time (like it was possible to compare Time with Date). + + *DHH* + +* Deprecate multiple parameters support of `Object#in?`. + + *Brian Morearty + Carlos Antonio da Silva* * An `ActiveSupport::Subscriber` class has been extracted from `ActiveSupport::LogSubscriber`, allowing you to use the event attachment @@ -32,8 +40,6 @@ *Charles Jones* -## Rails 4.0.0.beta1 (February 25, 2013) ## - * Improve singularizing a singular for multiple cases. Fixes #2608 #1825 #2395. @@ -134,19 +140,6 @@ *Kelly Stannard* -* It's now possible to compare `Date`, `DateTime`, `Time` and `TimeWithZone` - with `Float::INFINITY`. This allows to create date/time ranges with one infinite bound. - Example: - - range = Range.new(Date.today, Float::INFINITY) - - Also it's possible to check inclusion of date/time in range with conversion. - - range.include?(Time.now + 1.year) # => true - range.include?(DateTime.now + 1.year) # => true - - *Alexander Grebennik* - * Remove meaningless `ActiveSupport::FrozenObjectError`, which was just an alias of `RuntimeError`. *Akira Matsuda* diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 893c2500d76b43774a07b8161f6a34ffb4a0da44..1e8f0df8700d7946a402d921df2b9af392433fff 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -133,13 +133,7 @@ def next_id end def matches?(_kind, _filter) - if @_is_object_filter - _filter_matches = @filter.to_s.start_with?(_method_name_for_object_filter(_kind, _filter, false)) - else - _filter_matches = (@filter == _filter) - end - - @kind == _kind && _filter_matches + @kind == _kind && @filter == _filter end def duplicates?(other) @@ -242,16 +236,6 @@ def recompile_options! @compiled_options = conditions.flatten.join(" && ") end - def _method_name_for_object_filter(kind, filter, append_next_id = true) - class_name = filter.kind_of?(Class) ? filter.to_s : filter.class.to_s - class_name.gsub!(/<|>|#/, '') - class_name.gsub!(/\/|:/, "_") - - method_name = "_callback_#{kind}_#{class_name}" - method_name << "_#{next_id}" if append_next_id - method_name - end - # Filters support: # # Arrays:: Used in conditions. This is used to specify @@ -273,8 +257,6 @@ def _method_name_for_object_filter(kind, filter, append_next_id = true) # a method is created that calls the before_foo method # on the object. def _compile_filter(filter) - @_is_object_filter = false - case filter when Array filter.map {|f| _compile_filter(f)} @@ -289,8 +271,7 @@ def _compile_filter(filter) method_name << (filter.arity == 1 ? "(self)" : " self, Proc.new ") else - method_name = _method_name_for_object_filter(kind, filter) - @_is_object_filter = true + 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/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 12457688700a2a3a7e924d4ed7d6c1ad36cded67..152eb02218d60f3e6c07b9f4d4a5e053e43847af 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -15,12 +15,12 @@ class Array # # * If the argument responds to +to_ary+ the method is invoked. Kernel#Array # moves on to try +to_a+ if the returned value is +nil+, but Array.wrap returns - # such a +nil+ right away. + # +nil+ right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, Kernel#Array # raises an exception, while Array.wrap does not, it just returns the value. - # * It does not call +to_a+ on the argument, though special-cases +nil+ to return an empty array. + # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+. # - # The last point is particularly worth comparing for some enumerables: + # The second point is easily explained with some enumerables: # # Array(foo: :bar) # => [[:foo, :bar]] # Array.wrap(foo: :bar) # => [{:foo=>:bar}] @@ -29,10 +29,10 @@ class Array # # [*object] # - # which for +nil+ returns [], and calls to Array(object) otherwise. + # which returns [] for +nil+, but calls to Array(object) otherwise. # - # Thus, in this case the behavior may be different for +nil+, and the differences with - # Kernel#Array explained above apply to the rest of objects. + # The differences with Kernel#Array explained above + # apply to the rest of objects. def self.wrap(object) if object.nil? [] diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 5f13f5f70feb1192ad419469189508fd43e1617e..465fedda8012420f8aaaf84b169e2b941ec0156d 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -2,5 +2,4 @@ require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/date/zones' -require 'active_support/core_ext/date/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 106a65610c5082e3adae7d8e432fd9b2a49c4116..06e4847e8296dea4e2d0a7dcff02a691f0c3405b 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -119,4 +119,15 @@ def change(options) options.fetch(:day, day) ) end + + # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there. + def compare_with_coercion(other) + if other.is_a?(Time) + self.to_datetime <=> other + else + compare_without_coercion(other) + end + end + alias_method :compare_without_coercion, :<=> + alias_method :<=>, :compare_with_coercion end diff --git a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb deleted file mode 100644 index ca5d79394258c298620c3a44aa1d5ee64a1f2052..0000000000000000000000000000000000000000 --- a/activesupport/lib/active_support/core_ext/date/infinite_comparable.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class Date - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index 024af91738bdb16d1f08e48f899ccb91e4730e81..e8a27b9f38bcedef8effec7dfd5df1c20a93e3f0 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -2,4 +2,3 @@ require 'active_support/core_ext/date_time/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/date_time/zones' -require 'active_support/core_ext/date_time/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index 9f0864d9bbc17cc6cbb517a3ae16fbe33660eda4..3e22f1942c5257914026e624202251905093e628 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -154,4 +154,11 @@ def utc? def utc_offset (offset * 86400).to_i end + + # Layers additional behavior on DateTime#<=> so that Time and + # ActiveSupport::TimeWithZone instances can be compared with a DateTime. + def <=>(other) + super other.to_datetime + end + end diff --git a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb deleted file mode 100644 index 8a282b19f27f1ad2b40d23c61f2ef46692fdcada..0000000000000000000000000000000000000000 --- a/activesupport/lib/active_support/core_ext/date_time/infinite_comparable.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class DateTime - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/core_ext/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/infinite_comparable.rb deleted file mode 100644 index b78b2deaadb7a95037dca46ca9e32040b77720ef..0000000000000000000000000000000000000000 --- a/activesupport/lib/active_support/core_ext/infinite_comparable.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'active_support/concern' -require 'active_support/core_ext/module/aliasing' -require 'active_support/core_ext/object/try' - -module InfiniteComparable - extend ActiveSupport::Concern - - included do - alias_method_chain :<=>, :infinity - end - - define_method :'<=>_with_infinity' do |other| - if other.class == self.class - public_send :'<=>_without_infinity', other - else - infinite = try(:infinite?) - other_infinite = other.try(:infinite?) - - # inf <=> inf - if infinite && other_infinite - infinite <=> other_infinite - # not_inf <=> inf - elsif other_infinite - -other_infinite - # inf <=> not_inf - elsif infinite - infinite - else - conversion = "to_#{self.class.name.downcase}" - other = other.public_send(conversion) if other.respond_to?(conversion) - public_send :'<=>_without_infinity', other - end - end - end -end diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index c7a8348b1d4d53048e06ac23e5749cbeb257d31a..56c79c04bdabaedb9a071a4cbc0c2f343adab96b 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/aliasing' + module Marshal class << self def load_with_autoloading(source) diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index d5cfc2ece471c761f0f48249f8a735bddb851163..a6bc0624beea8756b942af7fed47c2d07b4acbf0 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,4 +1,3 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/numeric/conversions' -require 'active_support/core_ext/numeric/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb deleted file mode 100644 index b5f1b0487bb059d7aea0bed167decba9d5497f61..0000000000000000000000000000000000000000 --- a/activesupport/lib/active_support/core_ext/numeric/infinite_comparable.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class Float - include InfiniteComparable -end - -class BigDecimal - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index 3fec465ec0e83a3fc81a84fb770571175975e9c9..5ea5f84884de85736aeada6b02b0e2624c2d7740 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -1,17 +1,18 @@ +require 'active_support/deprecation' + class Object - # Returns true if this object is included in the argument(s). Argument must be - # any object which responds to +#include?+ or optionally, multiple arguments can be passed in. Usage: + # Returns true if this object is included in the argument. Argument must be + # any object which responds to +#include?+. Usage: # - # characters = ['Konata', 'Kagami', 'Tsukasa'] - # 'Konata'.in?(characters) # => true + # characters = ["Konata", "Kagami", "Tsukasa"] + # "Konata".in?(characters) # => true # - # character = 'Konata' - # character.in?('Konata', 'Kagami', 'Tsukasa') # => true - # - # This will throw an ArgumentError if a single argument is passed in and it doesn't respond + # This will throw an ArgumentError if the argument doesn't respond # to +#include?+. def in?(*args) if args.length > 1 + ActiveSupport::Deprecation.warn "Calling #in? with multiple arguments is" \ + " deprecated, please pass in an object that responds to #include? instead." args.include? self else another_object = args.first diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb index 3af66aaf2f17c4013ff765e733e60f40acaaa337..3a07401c8a2a5b00ee6addd7885ff11d43b3ee69 100644 --- a/activesupport/lib/active_support/core_ext/range/include_range.rb +++ b/activesupport/lib/active_support/core_ext/range/include_range.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/aliasing' + class Range # Extends the default Range#include? to support range comparisons. # (1..5).include?(1..5) # => true diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index af6b589b7142114658393a18d8d6b0b7e4db8aa9..32cffe237d6b9b959ab1f3efa1f097c887c5ff0c 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -3,4 +3,3 @@ require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/zones' -require 'active_support/core_ext/time/infinite_comparable' diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index a3ce7dbe3fba94b37d26fc8d7d2f6d5220960c96..c65f20c2d51fdd938ca6f0c07366d564cd8c3860 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -65,6 +65,18 @@ def local_time(*args) def current ::Time.zone ? ::Time.zone.now : ::Time.now end + + # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime + # instances can be used when called with a single argument + def at_with_coercion(*args) + if args.size == 1 && args.first.acts_like?(:time) + at_without_coercion(args.first.to_i) + else + at_without_coercion(*args) + end + end + alias_method :at_without_coercion, :at + alias_method :at, :at_with_coercion end # Seconds since midnight: Time.now.seconds_since_midnight diff --git a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb b/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb deleted file mode 100644 index 63795885f5bc4442dc8f6f0f3da469f9a3853fc3..0000000000000000000000000000000000000000 --- a/activesupport/lib/active_support/core_ext/time/infinite_comparable.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'active_support/core_ext/infinite_comparable' - -class Time - include InfiniteComparable -end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 485dc91063263abbbe3866ae3b738362f459d4a0..a03a66b96b6a7ee7b0018622478f3e107b8ede25 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -25,7 +25,7 @@ def method_missing(called, *args, &block) end end - # This DeprecatedObjectProxy transforms object to depracated object. + # This DeprecatedObjectProxy transforms object to deprecated object. # # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!") # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance) @@ -52,7 +52,7 @@ def warn(callstack, called, args) end # This DeprecatedInstanceVariableProxy transforms instance variable to - # depracated instance variable. + # deprecated instance variable. # # class Example # def initialize(deprecator) @@ -93,7 +93,7 @@ def warn(callstack, called, args) end end - # This DeprecatedConstantProxy transforms constant to depracated constant. + # This DeprecatedConstantProxy transforms constant to deprecated constant. # # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST') # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance) diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 1b20592e4c1952878ad98fa6778bbc8168ee89f0..0a81a8393d5ac2b959464a1fa60b90238fff91c2 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -91,7 +91,7 @@ def self.[](*args) # # This value can be later fetched using either +:key+ or +'key'+. def []=(key, value) - regular_writer(convert_key(key), convert_value(value)) + regular_writer(convert_key(key), convert_value(value, for: :assignment)) end alias_method :store, :[]= @@ -229,7 +229,11 @@ def to_options!; self end # Convert to a regular hash with string keys. def to_hash - Hash.new(default).merge!(self) + _new_hash= {} + each do |key, value| + _new_hash[convert_key(key)] = convert_value(value, for: :to_hash) + end + Hash.new(default).merge!(_new_hash) end protected @@ -237,12 +241,18 @@ def convert_key(key) key.kind_of?(Symbol) ? key.to_s : key end - def convert_value(value) + def convert_value(value, options = {}) if value.is_a? Hash - value.nested_under_indifferent_access + if options[:for] == :to_hash + value.to_hash + else + value.nested_under_indifferent_access + end elsif value.is_a?(Array) - value = value.dup if value.frozen? - value.map! { |e| convert_value(e) } + unless options[:for] == :assignment + value = value.dup + end + value.map! { |e| convert_value(e, options) } else value end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 7588fdb67c49421f45ce288d9390f269dc88155b..99fe03e6d0206b44c334c5f88ad9aa9a8d0bfc1f 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -79,6 +79,13 @@ class Evented #:nodoc: def initialize(pattern, delegate) @pattern = pattern @delegate = delegate + @can_publish = delegate.respond_to?(:publish) + end + + def publish(name, *args) + if @can_publish + @delegate.publish name, *args + end end def start(name, id, payload) diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index d70d9715380a6e9fa9c8f3723a7b5f9576b97ebc..e16b73a036c6f80858794806ef4899380daef9f6 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -40,6 +40,10 @@ def marshal_load(calls) def method_missing(name, *args) @calls << [name, args] end + + def info_signal + Signal.list['INFO'] + end end module Isolation diff --git a/activesupport/lib/active_support/version.rb b/activesupport/lib/active_support/version.rb index 2dc040d9a8ef7f6d8a82b92a6e6b45b081435642..66272d0e99e2ba815d7fd54913e759801ef591ae 100644 --- a/activesupport/lib/active_support/version.rb +++ b/activesupport/lib/active_support/version.rb @@ -1,7 +1,7 @@ module ActiveSupport # Returns the version of the currently loaded ActiveSupport as a Gem::Version def self.version - Gem::Version.new "4.0.0.rc1" + Gem::Version.new "4.0.0" end module VERSION #:nodoc: diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 5afc2094e84bfca6b8f1054eb4d7f7761414659f..13f2e3cdaf20ffe6edd2e07a0f06e0ca1097188d 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -66,16 +66,6 @@ def history end end - class CallbackClass - def self.before(model) - model.history << [:before_save, :class] - end - - def self.after(model) - model.history << [:after_save, :class] - end - end - class Person < Record [:before_save, :after_save].each do |callback_method| callback_method_sym = callback_method.to_sym @@ -83,7 +73,6 @@ class Person < Record send(callback_method, callback_string(callback_method_sym)) send(callback_method, callback_proc(callback_method_sym)) send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ''))) - send(callback_method, CallbackClass) send(callback_method) { |model| model.history << [callback_method_sym, :block] } end @@ -97,7 +86,6 @@ class PersonSkipper < Person skip_callback :save, :after, :before_save_method, :unless => :yes skip_callback :save, :after, :before_save_method, :if => :no skip_callback :save, :before, :before_save_method, :unless => :no - skip_callback :save, :before, CallbackClass , :if => :yes def yes; true; end def no; false; end end @@ -442,7 +430,6 @@ def test_skip_person [:before_save, :object], [:before_save, :block], [:after_save, :block], - [:after_save, :class], [:after_save, :object], [:after_save, :proc], [:after_save, :string], @@ -462,10 +449,8 @@ def test_save_person [:before_save, :string], [:before_save, :proc], [:before_save, :object], - [:before_save, :class], [:before_save, :block], [:after_save, :block], - [:after_save, :class], [:after_save, :object], [:after_save, :proc], [:after_save, :string], @@ -730,10 +715,8 @@ def test_skip_writer [:before_save, :string], [:before_save, :proc], [:before_save, :object], - [:before_save, :class], [:before_save, :block], [:after_save, :block], - [:after_save, :class], [:after_save, :object], [:after_save, :proc], [:after_save, :string], diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index f3fa96ec6fb859b509f469140b0d540ee22b7a16..aa5e4461fd07185b0d6f2e1ece502e9f018ceeef 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -48,6 +48,10 @@ def test_to_time end end + def test_compare_to_time + assert Date.yesterday < Time.now + end + def test_to_datetime assert_equal DateTime.civil(2005, 2, 21), Date.new(2005, 2, 21).to_datetime assert_equal 0, Date.new(2005, 2, 21).to_datetime.offset # use UTC offset @@ -357,11 +361,6 @@ def test_can_freeze_twice Date.today.freeze.freeze end end - - def test_compare_with_infinity - assert_equal(-1, Date.today <=> Float::INFINITY) - assert_equal(1, Date.today <=> -Float::INFINITY) - end end class DateExtConversionsTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 7be578599b040005b180952ba13ad4f61029b41d..3e76b8a9b16b3a6e839881a7f34a4f0f4e023c54 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -335,10 +335,3 @@ def with_env_tz(new_tz = 'US/Eastern') old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end - -class DateTimeExtBehaviorTest < ActiveSupport::TestCase - def test_compare_with_infinity - assert_equal(-1, DateTime.now <=> Float::INFINITY) - assert_equal(1, DateTime.now <=> -Float::INFINITY) - end -end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 30d95b75bc45bbf5dc4e724e0cead7bf3a364566..39bd0a2dd490ace32379e6881fb4fc3f523672e8 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -490,6 +490,10 @@ def test_indifferent_to_hash roundtrip = mixed_with_default.with_indifferent_access.to_hash assert_equal @strings, roundtrip assert_equal '1234', roundtrip.default + new_to_hash = @nested_mixed.with_indifferent_access.to_hash + assert_not new_to_hash.instance_of?(HashWithIndifferentAccess) + assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess) + assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess) end def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access @@ -499,9 +503,21 @@ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_acces assert_equal [1], hash[:a] end + def test_with_indifferent_access_has_no_side_effects_on_existing_hash + hash = {content: [{:foo => :bar, 'bar' => 'baz'}]} + hash.with_indifferent_access + + assert_equal [:foo, "bar"], hash[:content].first.keys + end + def test_indifferent_hash_with_array_of_hashes hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}.with_indifferent_access assert_equal "1", hash[:urls][:url].first[:address] + + hash = hash.to_hash + assert_not hash.instance_of?(HashWithIndifferentAccess) + assert_not hash["urls"].instance_of?(HashWithIndifferentAccess) + assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess) end def test_should_preserve_array_subclass_when_value_is_array diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 3744d50864b0851b683fc4990305d833ccdcc118..f22ae3ccac46b5406aac599678bd9fe2777af890 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -441,57 +441,3 @@ def test_to_s__injected_on_proper_types assert_equal '1 Million', BigDecimal("1000010").to_s(:human) end end - -class NumericExtBehaviorTest < ActiveSupport::TestCase - def setup - @inf = BigDecimal.new('Infinity') - end - - def test_compare_infinity_with_date - assert_equal(-1, -Float::INFINITY <=> Date.today) - assert_equal(1, Float::INFINITY <=> Date.today) - assert_equal(-1, -@inf <=> Date.today) - assert_equal(1, @inf <=> Date.today) - end - - def test_compare_infinty_with_infinty - assert_equal(-1, -Float::INFINITY <=> Float::INFINITY) - assert_equal(1, Float::INFINITY <=> -Float::INFINITY) - assert_equal(0, Float::INFINITY <=> Float::INFINITY) - assert_equal(0, -Float::INFINITY <=> -Float::INFINITY) - - assert_equal(-1, -Float::INFINITY <=> BigDecimal::INFINITY) - assert_equal(1, Float::INFINITY <=> -BigDecimal::INFINITY) - assert_equal(0, Float::INFINITY <=> BigDecimal::INFINITY) - assert_equal(0, -Float::INFINITY <=> -BigDecimal::INFINITY) - - assert_equal(-1, -BigDecimal::INFINITY <=> Float::INFINITY) - assert_equal(1, BigDecimal::INFINITY <=> -Float::INFINITY) - assert_equal(0, BigDecimal::INFINITY <=> Float::INFINITY) - assert_equal(0, -BigDecimal::INFINITY <=> -Float::INFINITY) - end - - def test_compare_infinity_with_time - assert_equal(-1, -Float::INFINITY <=> Time.now) - assert_equal(1, Float::INFINITY <=> Time.now) - assert_equal(-1, -@inf <=> Time.now) - assert_equal(1, @inf <=> Time.now) - end - - def test_compare_infinity_with_datetime - assert_equal(-1, -Float::INFINITY <=> DateTime.now) - assert_equal(1, Float::INFINITY <=> DateTime.now) - assert_equal(-1, -@inf <=> DateTime.now) - assert_equal(1, @inf <=> DateTime.now) - end - - def test_compare_infinity_with_twz - time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone) - - assert_equal(-1, -Float::INFINITY <=> twz) - assert_equal(1, Float::INFINITY <=> twz) - assert_equal(-1, -@inf <=> twz) - assert_equal(1, @inf <=> twz) - end -end diff --git a/activesupport/test/core_ext/object/inclusion_test.rb b/activesupport/test/core_ext/object/inclusion_test.rb index 22888333f5c83a9656d3cc63d13b46c2fef69d36..5c5c7c1879330daed656da98fbf1119a2f12548a 100644 --- a/activesupport/test/core_ext/object/inclusion_test.rb +++ b/activesupport/test/core_ext/object/inclusion_test.rb @@ -3,15 +3,19 @@ class InTest < ActiveSupport::TestCase def test_in_multiple_args - assert :b.in?(:a,:b) - assert !:c.in?(:a,:b) + assert_deprecated do + assert :b.in?(:a,:b) + assert !:c.in?(:a,:b) + end end - + def test_in_multiple_arrays - assert [1,2].in?([1,2],[2,3]) - assert ![1,2].in?([1,3],[2,1]) + assert_deprecated do + assert [1,2].in?([1,2],[2,3]) + assert ![1,2].in?([1,3],[2,1]) + end end - + def test_in_array assert 1.in?([1,2]) assert !3.in?([1,2]) @@ -53,7 +57,7 @@ def test_in_module assert A.in?(C) assert !A.in?(A) end - + def test_no_method_catching assert_raise(ArgumentError) { 1.in?(1) } end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 6e94d5e10d0e6ec3c6d3b4faa978669b35c09a5c..3e2355ae233591a2510e79c842f2d1abf350fc4a 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -1,7 +1,6 @@ require 'abstract_unit' require 'active_support/time' require 'active_support/core_ext/range' -require 'active_support/core_ext/numeric' class RangeTest < ActiveSupport::TestCase def test_to_s_from_dates @@ -17,7 +16,6 @@ def test_to_s_from_times def test_date_range assert_instance_of Range, DateTime.new..DateTime.new assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new - assert_instance_of Range, DateTime.new..DateTime::Infinity.new end def test_overlaps_last_inclusive @@ -92,28 +90,4 @@ def test_no_overlaps_on_time time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00) assert !time_range_1.overlaps?(time_range_2) end - - def test_infinite_bounds - time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - - time = Time.now - date = Date.today - datetime = DateTime.now - twz = ActiveSupport::TimeWithZone.new(time, time_zone) - - infinity1 = Float::INFINITY - infinity2 = BigDecimal.new('Infinity') - - [infinity1, infinity2].each do |infinity| - [time, date, datetime, twz].each do |bound| - [time, date, datetime, twz].each do |value| - assert Range.new(bound, infinity).include?(value + 10.years) - assert Range.new(-infinity, bound).include?(value - 10.years) - - assert !Range.new(bound, infinity).include?(value - 10.years) - assert !Range.new(-infinity, bound).include?(value + 10.years) - end - end - end - end end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 2864d7a57fd37ad7e7ef36ddcc64a27ca18edc9a..eefcdbb1b3e92c2d52617e1ef8540f9081c93efb 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -741,6 +741,28 @@ def test_compare_with_time_with_zone assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end + def test_at_with_datetime + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0)) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0), 0)) } + end + end + + def test_at_with_time_with_zone + assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) + + # Only test this if the underlying Time.at raises a TypeError + begin + Time.at_without_coercion(Time.now, 0) + rescue TypeError + assert_raise(TypeError) { assert_equal(Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC']), 0)) } + end + end + def test_eql? assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) @@ -855,10 +877,3 @@ def test_last_quarter_on_31st assert_equal Time.local(2004, 2, 29), Time.local(2004, 5, 31).last_quarter end end - -class TimeExtBehaviorTest < ActiveSupport::TestCase - def test_compare_with_infinity - assert_equal(-1, Time.now <=> Float::INFINITY) - assert_equal(1, Time.now <=> -Float::INFINITY) - end -end diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 98a87ab9e645ae77da0acd0d87c5fb95e0666857..3ce3297874537584d3a1ee72964c84eb7f9e78fd 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1118,13 +1118,3 @@ def with_tz_default(tz = nil) Time.zone = old_tz end end - -class TimeWithZoneExtBehaviorTest < ActiveSupport::TestCase - def test_compare_with_infinity - time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - twz = ActiveSupport::TimeWithZone.new(Time.now, time_zone) - - assert_equal(-1, twz <=> Float::INFINITY) - assert_equal(1, twz <=> -Float::INFINITY) - end -end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index d1454902e58f279aa10259641eec38c4c5afb785..a2d1d0dc3964c1505aacf9b37061025eda8afaac 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -62,21 +62,25 @@ class TestJSONDecoding < ActiveSupport::TestCase backends.each do |backend| TESTS.each do |json, expected| test "json decodes #{json} with the #{backend} backend" do + prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = true silence_warnings do ActiveSupport::JSON.with_backend backend do assert_equal expected, ActiveSupport::JSON.decode(json) end end + ActiveSupport.parse_json_times = prev end end test "json decodes time json with time parsing disabled with the #{backend} backend" do + prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = false expected = {"a" => "2007-01-01 01:12:34 Z"} ActiveSupport::JSON.with_backend backend do assert_equal expected, ActiveSupport::JSON.decode(%({"a": "2007-01-01 01:12:34 Z"})) end + ActiveSupport.parse_json_times = prev end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index 12ce250eb36d750425b850b9f774ae09ff9b5feb..8686dcf92940cf289f8042b7aeed0186923c8eb1 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -82,6 +82,8 @@ def sorted_json(json) constants.grep(/Tests$/).each do |class_tests| define_method("test_#{class_tests[0..-6].underscore}") do begin + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ self.class.const_get(class_tests).each do |pair| @@ -89,7 +91,7 @@ def sorted_json(json) end ensure ActiveSupport.escape_html_entities_in_json = false - ActiveSupport.use_standard_json_time_format = false + ActiveSupport.use_standard_json_time_format = prev end end end @@ -172,16 +174,21 @@ def test_hash_should_allow_key_filtering_with_except end def test_time_to_json_includes_local_offset + prev = ActiveSupport.use_standard_json_time_format ActiveSupport.use_standard_json_time_format = true with_env_tz 'US/Eastern' do assert_equal %("2005-02-01T15:15:10-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) end ensure - ActiveSupport.use_standard_json_time_format = false + ActiveSupport.use_standard_json_time_format = prev end def test_hash_with_time_to_json + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.use_standard_json_time_format = false assert_equal '{"time":"2009/01/01 00:00:00 +0000"}', { :time => Time.utc(2009) }.to_json + ensure + ActiveSupport.use_standard_json_time_format = prev end def test_nested_hash_with_float @@ -302,12 +309,12 @@ def test_struct_encoding assert_equal({"name" => "David", "sub" => { "name" => "David", - "date" => "2010/01/01" }}, JSON.parse(json_custom)) + "date" => "2010-01-01" }}, JSON.parse(json_custom)) assert_equal({"name" => "David", "email" => "sample@example.com"}, JSON.parse(json_strings)) - assert_equal({"name" => "David", "date" => "2010/01/01"}, + assert_equal({"name" => "David", "date" => "2010-01-01"}, JSON.parse(json_string_and_date)) end diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 06c7e8a1a7ca0626b3e39c906197b90e7cb1c7fe..509c453b5cb7c318a910317f15efacd0b45af700 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -56,9 +56,14 @@ def test_signed_round_tripping end def test_alternative_serialization_method + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.use_standard_json_time_format = true encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new) message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) - assert_equal encryptor.decrypt_and_verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + assert_equal exp, encryptor.decrypt_and_verify(message) + ensure + ActiveSupport.use_standard_json_time_format = prev end private diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 5adff41653421e05c793d8e247de23f0d4f893b5..a8633f7299a7cdd1dabe5de68732c102006224c5 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -45,9 +45,14 @@ def test_tampered_data_raises end def test_alternative_serialization_method + prev = ActiveSupport.use_standard_json_time_format + ActiveSupport.use_standard_json_time_format = true verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new) message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) }) - assert_equal verifier.verify(message), { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + assert_equal exp, verifier.verify(message) + ensure + ActiveSupport.use_standard_json_time_format = prev end def assert_not_verified(message) diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index d63c59883a865cc15b4a22498a212b99aa036435..53f9afbefe9eec9d17fc5dca284934728c8a13a3 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -81,6 +81,20 @@ def event(*args) end end + class TestSubscriber + attr_reader :starts, :finishes, :publishes + + def initialize + @starts = [] + @finishes = [] + @publishes = [] + end + + def start(*args); @starts << args; end + def finish(*args); @finishes << args; end + def publish(*args); @publishes << args; end + end + class SyncPubSubTest < TestCase def test_events_are_published_to_a_listener @notifier.publish :foo @@ -144,6 +158,14 @@ def test_multiple_log_subscribers assert_equal [[:foo]], @another end + def test_publish_with_subscriber + subscriber = TestSubscriber.new + @notifier.subscribe nil, subscriber + @notifier.publish :foo + + assert_equal [[:foo]], subscriber.publishes + end + private def event(*args) args diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index dfe9f3c11cf9b46c1b643da09f6bb70bb57e8189..a1bdcf239604f429318d1654681c6aeacab31625 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -19,6 +19,9 @@ def options def record(*args) end + + def info_signal + end end def test_standard_error_raised_within_setup_callback_is_puked diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index b0e52847e11f74cd6357d3ad0c1bb90deb940ce4..bf39128ad642cb0f697cc700a5a8b506f16125c6 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,7 +1,6 @@ -## Rails 4.0.0 (unreleased) ## -* Change Service pages(404, etc). *Stanislav Sobolev* +## Rails 4.0.0 (June 25, 2013) ## -## Rails 4.0.0.beta1 (unreleased) ## +* Change Service pages(404, etc). *Stanislav Sobolev* * Split Validations and Callbacks guide into two. *Steve Klabnik* diff --git a/guides/assets/images/challenge.png b/guides/assets/images/getting_started/challenge.png similarity index 100% rename from guides/assets/images/challenge.png rename to guides/assets/images/getting_started/challenge.png diff --git a/guides/assets/images/getting_started/rails_welcome.png b/guides/assets/images/getting_started/rails_welcome.png new file mode 100644 index 0000000000000000000000000000000000000000..569dd846a8ed26379ddc427062759adaf9331601 Binary files /dev/null and b/guides/assets/images/getting_started/rails_welcome.png differ diff --git a/guides/assets/images/rails_welcome.png b/guides/assets/images/rails_welcome.png deleted file mode 100644 index 8ad2d351defdf2bc836a458fa728f09e486c3985..0000000000000000000000000000000000000000 Binary files a/guides/assets/images/rails_welcome.png and /dev/null differ diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 2c633425723f8fa09c103145fb9c8c0fe74f7edd..2e604bec47b3dec0336a165bee4e5517e2bf86b1 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -1,5 +1,5 @@ # Activate the gem you are reporting the issue against. -gem 'activerecord', '3.2.11' +gem 'activerecord', '3.2.13' require 'active_record' require 'minitest/autorun' require 'logger' diff --git a/guides/code/getting_started/Gemfile.lock b/guides/code/getting_started/Gemfile.lock index 823fac5ff72228d31d69717e461c2f9d22753047..888a6b30e2e219558e36bec39da4531f10dc53b4 100644 --- a/guides/code/getting_started/Gemfile.lock +++ b/guides/code/getting_started/Gemfile.lock @@ -92,7 +92,7 @@ GEM multi_json (~> 1.0) hike (1.2.1) i18n (0.6.1) - jbuilder (1.0.2) + jbuilder (1.3.0) activesupport (>= 3.0.0) jquery-rails (2.2.0) railties (>= 3.0, < 5.0) diff --git a/guides/code/getting_started/config/environment.rb b/guides/code/getting_started/config/environment.rb index 2d651110042c86cb9798e16b0f6362cb45ec4760..e7e341c960c8874ede55087a2aadcf140dcaf0a9 100644 --- a/guides/code/getting_started/config/environment.rb +++ b/guides/code/getting_started/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application. +# Load the Rails application. require File.expand_path('../application', __FILE__) -# Initialize the rails application. +# Initialize the Rails application. Blog::Application.initialize! diff --git a/guides/source/2_2_release_notes.md b/guides/source/2_2_release_notes.md index 802455f612e68e4954e09c2c09c81de2e24ba77d..7db4cf07e7b6243b2c3875e68929531f961bc22f 100644 --- a/guides/source/2_2_release_notes.md +++ b/guides/source/2_2_release_notes.md @@ -200,7 +200,7 @@ Active Record association proxies now respect the scope of methods on the proxie * More information: * [Rails 2.2 Change: Private Methods on Association Proxies are Private](http://afreshcup.com/2008/10/24/rails-22-change-private-methods-on-association-proxies-are-private/) -### Other ActiveRecord Changes +### Other Active Record Changes * `rake db:migrate:redo` now accepts an optional VERSION to target that specific migration to redo * Set `config.active_record.timestamped_migrations = false` to have migrations with numeric prefix instead of UTC timestamp. diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index 7aef566e40bc51e4c4e939d7faf1baaed3d15165..3c08f148cf76d2a6eafd3ddd952f678f625949d1 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -134,7 +134,7 @@ Rails 2.3 will introduce the notion of _default scopes_ similar to named scopes, ### Batch Processing -You can now process large numbers of records from an ActiveRecord model with less pressure on memory by using `find_in_batches`: +You can now process large numbers of records from an Active Record model with less pressure on memory by using `find_in_batches`: ```ruby Customer.find_in_batches(:conditions => {:active => true}) do |customer_group| @@ -504,7 +504,7 @@ A lot of folks have adopted the notion of using try() to attempt operations on o ### Swappable Parsers for XMLmini -The support for XML parsing in ActiveSupport has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed: +The support for XML parsing in Active Support has been made more flexible by allowing you to swap in different parsers. By default, it uses the standard REXML implementation, but you can easily specify the faster LibXML or Nokogiri implementations for your own applications, provided you have the appropriate gems installed: ```ruby XmlMini.backend = 'LibXML' diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index 388ba3fa30951bacc9ee520c0b0db2199a74cfa8..ebe884716841eb0d2ced2ea2e0d1b7dccd0fec4c 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -79,7 +79,7 @@ Creating a Rails 3.0 application -------------------------------- ```bash -# You should have the 'rails' rubygem installed +# You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -475,7 +475,7 @@ As well as the following deprecations: * `named_scope` in an Active Record class is deprecated and has been renamed to just `scope`. * In `scope` methods, you should move to using the relation methods, instead of a `:conditions => {}` finder method, for example `scope :since, lambda {|time| where("created_at > ?", time) }`. * `save(false)` is deprecated, in favor of `save(:validate => false)`. -* I18n error messages for ActiveRecord should be changed from :en.activerecord.errors.template to `:en.errors.template`. +* I18n error messages for Active Record should be changed from :en.activerecord.errors.template to `:en.errors.template`. * `model.errors.on` is deprecated in favor of `model.errors[]` * validates_presence_of => validates... :presence => true * `ActiveRecord::Base.colorize_logging` and `config.active_record.colorize_logging` are deprecated in favor of `Rails::LogSubscriber.colorize_logging` or `config.colorize_logging` @@ -580,7 +580,7 @@ Action Mailer has been given a new API with TMail being replaced out with the ne * All mailers are now in `app/mailers` by default. * Can now send email using new API with three methods: `attachments`, `headers` and `mail`. -* ActionMailer now has native support for inline attachments using the `attachments.inline` method. +* Action Mailer now has native support for inline attachments using the `attachments.inline` method. * Action Mailer emailing methods now return `Mail::Message` objects, which can then be sent the `deliver` message to send itself. * All delivery methods are now abstracted out to the Mail gem. * The mail delivery method can accept a hash of all valid mail header fields with their value pair. diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index d3f8abe0c8d47b01085d298bf5fe2574cf89fd38..5c99892e39c14cbbe482248c8f9c26840d9eea58 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -137,7 +137,7 @@ Creating a Rails 3.1 application -------------------------------- ```bash -# You should have the 'rails' rubygem installed +# You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` diff --git a/guides/source/3_2_release_notes.md b/guides/source/3_2_release_notes.md index 68a47be14f730c5d47e04dc84fadf2993d4bfb4f..e036860de25385e526906ec685eea7e7aa0f22b7 100644 --- a/guides/source/3_2_release_notes.md +++ b/guides/source/3_2_release_notes.md @@ -67,7 +67,7 @@ Creating a Rails 3.2 application -------------------------------- ```bash -# You should have the 'rails' rubygem installed +# You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -101,7 +101,7 @@ Rails 3.2 comes with a development mode that's noticeably faster. Inspired by [A ### Automatic Query Explains -Rails 3.2 comes with a nice feature that explains queries generated by ARel by defining an `explain` method in `ActiveRecord::Relation`. For example, you can run something like `puts Person.active.limit(5).explain` and the query ARel produces is explained. This allows to check for the proper indexes and further optimizations. +Rails 3.2 comes with a nice feature that explains queries generated by Arel by defining an `explain` method in `ActiveRecord::Relation`. For example, you can run something like `puts Person.active.limit(5).explain` and the query Arel produces is explained. This allows to check for the proper indexes and further optimizations. Queries that take more than half a second to run are *automatically* explained in the development mode. This threshold, of course, can be changed. @@ -189,7 +189,7 @@ Action Pack * form\_for is changed to use "#{action}\_#{as}" as the css class and id if `:as` option is provided. Earlier versions used "#{as}\_#{action}". -* `ActionController::ParamsWrapper` on ActiveRecord models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`. +* `ActionController::ParamsWrapper` on Active Record models now only wrap `attr_accessible` attributes if they were set. If not, only the attributes returned by the class method `attribute_names` will be wrapped. This fixes the wrapping of nested attributes by adding them to `attr_accessible`. * Log "Filter chain halted as CALLBACKNAME rendered or redirected" every time a before callback halts. diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index 2793d9025fd57b200fccbdb071a5daefb617b1e9..50a3c6fabcbc2eb4471eadc2dee247542082a654 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -22,7 +22,7 @@ Creating a Rails 4.0 application -------------------------------- ``` - You should have the 'rails' rubygem installed + You should have the 'rails' RubyGem installed $ rails new myapp $ cd myapp ``` @@ -113,7 +113,8 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to protect attributes from mass assignment when non-permitted attributes are passed. -* Added `ActiveModel::Model`, a mixin to make Ruby objects work with ActionPack out of box. +* Added `ActiveModel::Model`, a mixin to make Ruby objects work with + Action Pack out of box. ### Deprecations @@ -142,7 +143,7 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/activ * Deprecates the compatibility method Module#local_constant_names, use Module#local_constants instead (which returns symbols). -* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby stdlib. +* BufferedLogger is deprecated. Use ActiveSupport::Logger, or the logger from Ruby standard library. * Deprecate `assert_present` and `assert_blank` in favor of `assert object.blank?` and `assert object.present?` diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index db91425c76dd85cf13d17cc0dba051f9e724a29b..43a59e8d43e90268633a0fa1241f0f1ff0de1a02 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -148,7 +148,7 @@ Also, if you've turned on `config.wrap_parameters` in your initializer or callin And assume that you're sending the data to `CompaniesController`, it would then be wrapped in `:company` key like this: ```ruby -{ :name => "acme", :address => "123 Carrot Street", :company => { :name => "acme", :address => "123 Carrot Street" } } +{ name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } } ``` You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html) @@ -246,7 +246,7 @@ 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 => []) +params.permit(id: []) ``` To whitelist an entire hash of parameters, the `permit!` method can be @@ -266,9 +266,9 @@ mass-assigned. You can also use permit on nested parameters, like: ```ruby -params.permit(:name, {:emails => []}, - :friends => [ :name, - { :family => [ :name ], :hobbies => [] }]) +params.permit(:name, { emails: [] }, + friends: [ :name, + { family: [ :name ], hobbies: [] }]) ``` This declaration whitelists the `name`, `emails` and `friends` diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index ec7b8d4e17a94c591d029035970a01caff38f625..c3513391176cc441679c58930cc68f61fbad4c40 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -1,7 +1,7 @@ Action Mailer Basics ==================== -This guide should provide you with all you need to get started in sending and +This guide provides you with all you need to get started in sending and receiving emails from and to your application, and many internals of Action Mailer. It also covers how to test your mailers. diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 2589accadd489f04abe4bd4cd05f87b2c00d8ff5..80eec428c1b04db374fd58a77b7eedc9feb898e6 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -707,7 +707,7 @@ 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') +Post.where(:id => 10).limit(1).unscope(where: :id, :limit).order('id DESC') = Post.order('id DESC') ``` ### `only` @@ -1301,6 +1301,11 @@ Client.unscoped { Dynamic Finders --------------- +NOTE: Dynamic finders have been deprecated in Rails 4.0 and will be +removed in Rails 4.1. The best practice is to use Active Record scopes +instead. You can find the deprecation gem at +https://github.com/rails/activerecord-deprecated_finders + For every field (also known as an attribute) you define in your table, Active Record provides a finder method. If you have a field called `first_name` on your `Client` model for example, you get `find_by_first_name` for free from Active Record. If you have a `locked` field on the `Client` model, you also get `find_by_locked` and methods. You can specify an exclamation point (`!`) on the end of the dynamic finders to get them to raise an `ActiveRecord::RecordNotFound` error if they do not return any records, like `Client.find_by_name!("Ryan")` diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index df39d3c5dcf358726d494b2387b80f7351f2da30..dfc951f10e5055aa01e7d6a416f578ae01e496bf 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -434,7 +434,7 @@ end Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when `:minimum` is 1 you should -provide a personalized message or use `validates_presence_of` instead. When +provide a personalized message or use `presence: true` instead. When `:in` or `:within` have a lower limit of 1, you should either provide a personalized message or call `presence` prior to `length`. @@ -768,6 +768,7 @@ class Person < ActiveRecord::Base validates :name, presence: true, on: :save end ``` +The last line is in review state and as of now, it is not running in any version of Rails 3.2.x as discussed in this [issue](https://github.com/rails/rails/issues/10248) Strict Validations ------------------ diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 101a4f5b4233efb2478935f1c85934d5ef295a44..6daad33dea737948fcfa1a8240ce318dff8f1e4c 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -10,7 +10,7 @@ After reading this guide, you will know: * What Core Extensions are. * How to load all extensions. * How to cherry-pick just the extensions you want. -* What extensions ActiveSupport provides. +* What extensions Active Support provides. -------------------------------------------------------------------------------- @@ -476,12 +476,11 @@ NOTE: Defined in `active_support/core_ext/kernel/reporting.rb`. ### `in?` -The predicate `in?` tests if an object is included in another object or a list of objects. An `ArgumentError` exception will be raised if a single argument is passed and it does not respond to `include?`. +The predicate `in?` tests if an object is included in another object. An `ArgumentError` exception will be raised if the argument passed does not respond to `include?`. Examples of `in?`: ```ruby -1.in?(1,2) # => true 1.in?([1,2]) # => true "lo".in?("hello") # => true 25.in?(30..50) # => false @@ -2216,7 +2215,9 @@ NOTE: Defined in `active_support/core_ext/array/conversions.rb`. The method `to_formatted_s` acts like `to_s` by default. -If the array contains items that respond to `id`, however, it may be passed the symbol `:db` as argument. That's typically used with collections of ARs. Returned strings are: +If the array contains items that respond to `id`, however, the symbol +`:db` may be passed as argument. That's typically used with +collections of Active Record objects. Returned strings are: ```ruby [].to_formatted_s(:db) # => "null" @@ -2372,7 +2373,8 @@ NOTE: Defined in `active_support/core_ext/array/wrap.rb`. ### Duplicating -The method `Array.deep_dup` duplicates itself and all objects inside recursively with ActiveSupport method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside. +The method `Array.deep_dup` duplicates itself and all objects inside +recursively with Active Support method `Object#deep_dup`. It works like `Array#map` with sending `deep_dup` method to each object inside. ```ruby array = [1, [2, 3]] @@ -2593,7 +2595,8 @@ NOTE: Defined in `active_support/core_ext/hash/deep_merge.rb`. ### Deep duplicating -The method `Hash.deep_dup` duplicates itself and all keys and values inside recursively with ActiveSupport method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside. +The method `Hash.deep_dup` duplicates itself and all keys and values +inside recursively with Active Support method `Object#deep_dup`. It works like `Enumerator#each_with_object` with sending `deep_dup` method to each pair inside. ```ruby hash = { a: 1, b: { c: 2, d: [3, 4] } } diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 43df544e28ae33e3cda401dbe836238ce8779eb1..d0d6c4469c9d7e7f9249e40d76f4c2fc6ee902ae 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -75,7 +75,7 @@ The query string strategy has several disadvantages: 2. **The file name can change between nodes in multi-server environments.**
The default query string in Rails 2.x is based on the modification time of the files. When assets are deployed to a cluster, there is no guarantee that the timestamps will be the same, resulting in different values being used depending on which server handles the request. 3. **Too much cache invalidation**
- When static assets are deployed with each new release of code, the mtime of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. + When static assets are deployed with each new release of code, the mtime(time of last modification) of _all_ these files changes, forcing all remote clients to fetch them again, even when the content of those assets has not changed. Fingerprinting fixes these problems by avoiding query strings, and by ensuring that filenames are consistent based on their content. @@ -416,7 +416,7 @@ You can call this task on the server during deployment to create compiled versio The rake task is: ```bash -$ bundle exec rake assets:precompile +$ RAILS_ENV=production bundle exec rake assets:precompile ``` For faster asset precompiles, you can partially load your application by setting @@ -450,7 +450,7 @@ The default matcher for compiling files includes `application.js`, `application. NOTE. The matcher (and other members of the precompile array; see below) is applied to final compiled file names. This means that anything that compiles to JS/CSS is excluded, as well as raw JS/CSS files; for example, `.coffee` and `.scss` files are **not** automatically included as they compile to JS/CSS. -If you have other manifests or individual stylesheets and JavaScript files to include, you can add them to the `precompile` array: +If you have other manifests or individual stylesheets and JavaScript files to include, you can add them to the `precompile` array in `config/application.rb`: ```ruby config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] @@ -459,7 +459,7 @@ config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js'] Or you can opt to precompile all assets with something like this: ```ruby -# config/environments/production.rb +# config/application.rb config.assets.precompile << Proc.new do |path| if path =~ /\.(css|js)\z/ full_path = Rails.application.assets.resolve(path).to_path @@ -707,7 +707,7 @@ config.assets.cache_store = :memory_store The options accepted by the assets cache store are the same as the application's cache store. ```ruby -config.assets.cache_store = :memory_store, { :size => 32.megabytes } +config.assets.cache_store = :memory_store, { size: 32.megabytes } ``` Adding Assets to Your Gems @@ -815,18 +815,16 @@ end If you use the `assets` group with Bundler, please make sure that your `config/application.rb` has the following Bundler require statement: ```ruby -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - Bundler.require *Rails.groups(:assets => %w(development test)) - # If you want your assets lazily compiled in production, use this line - # Bundler.require(:default, :assets, Rails.env) -end +# If you precompile assets before deploying to production, use this line +Bundler.require *Rails.groups(:assets => %w(development test)) +# If you want your assets lazily compiled in production, use this line +# Bundler.require(:default, :assets, Rails.env) ``` -Instead of the old Rails 3.0 version: +Instead of the generated version: ```ruby -# If you have a Gemfile, require the gems listed there, including any gems +# Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. -Bundler.require(:default, Rails.env) if defined?(Bundler) +Bundler.require(:default, Rails.env) ``` diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index e1fc3d0f533b495a64c265cd0655b1937ddd8b5f..456abaf612f041ac844f55c326f15e9c941f3e62 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -30,13 +30,13 @@ config.action_controller.perform_caching = true Page caching is a Rails mechanism which allows the request for a generated page to be fulfilled by the webserver (i.e. Apache or nginx), without ever having to go through the Rails stack at all. Obviously, this is super-fast. Unfortunately, it can't be applied to every situation (such as pages that need authentication) and since the webserver is literally just serving a file from the filesystem, cache expiration is an issue that needs to be dealt with. -INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching) +INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Action Caching Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy. -INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching) +INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Fragment Caching diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 7b7f5963fd45e67ad0076a2648b1f7a1c8a13e45..e0b44bbf93afd66a6edc9ae7816d28c77c5cc7fd 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -201,7 +201,7 @@ Usage: ... -ActiveRecord options: +Active Record options: [--migration] # Indicates when to generate migration # Default: true diff --git a/guides/source/configuring.md b/guides/source/configuring.md index a0ab707b51bdeef8c4f6b1370fe9de016146f841..9e40165d1510d9e7e5312397ca45824a6e7d8208 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -729,7 +729,7 @@ development: timeout: 5000 ``` -Since the connection pooling is handled inside of ActiveRecord by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit. +Since the connection pooling is handled inside of Active Record by default, all application servers (Thin, mongrel, Unicorn etc.) should behave the same. Initially, the database connection pool is empty and it will create additional connections as the demand for them increases, until it reaches the connection pool limit. Any one request will check out a connection the first time it requires access to the database, after which it will check the connection back in, at the end of the request, meaning that the additional connection slot will be available again for the next request in the queue. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 0be9bb1ced51fc4f7681087a50b306d2b319c279..0bfa646b81a0dddcfdd95c8c5cbcec4adda31fdf 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -182,9 +182,9 @@ Contributing to the Rails Documentation Ruby on Rails has two main sets of documentation: the guides help you in learning about Ruby on Rails, and the API is a reference. -You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/lifo/docrails/translating-rails-guides). +You can help improve the Rails guides by making them more coherent, consistent or readable, adding missing information, correcting factual errors, fixing typos, or bringing it up to date with the latest edge Rails. To get involved in the translation of Rails guides, please see [Translating Rails Guides](https://wiki.github.com/rails/docrails/translating-rails-guides). -If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/lifo/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. +If you're confident about your changes, you can push them directly yourself via [docrails](https://github.com/rails/docrails). Docrails is a branch with an **open commit policy** and public write access. Commits to docrails are still reviewed, but this happens after they are pushed. Docrails is merged with master regularly, so you are effectively editing the Ruby on Rails documentation. If you are unsure of the documentation changes, you can create an issue in the [Rails](https://github.com/rails/rails/issues) issues tracker on GitHub. diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 8f1d2922accb51c15f78d942aeba1a532fd17d32..70055c1d7d317a4f8d55c341b8f38c735808a4dd 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -198,7 +198,9 @@ Adding extra logging like this makes it easy to search for unexpected or unusual ### Tagged Logging -When running multi-user, multi-account applications, it’s often useful to be able to filter the logs using some custom rules. `TaggedLogging` in ActiveSupport helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. +When running multi-user, multi-account applications, it’s often useful +to be able to filter the logs using some custom rules. `TaggedLogging` +in Active Support helps in doing exactly that by stamping log lines with subdomains, request ids, and anything else to aid debugging such applications. ```ruby logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 14ca44d2a1b1bcde06e459f1897db59b12f3e2c4..ef622103f4964609060adcc60302561b7de5fca2 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -24,7 +24,7 @@ Ruby on Rails uses Git for source code control. The [Git homepage](http://git-sc * [Try Git course](http://try.github.io/) is an interactive course that will teach you the basics. * The [official Documentation](http://git-scm.com/documentation) is pretty comprehensive and also contains some videos with the basics of Git * [Everyday Git](http://schacon.github.io/git/everyday.html) will teach you just enough about Git to get by. -* The [PeepCode screencast](https://peepcode.com/products/git) on Git ($9) is easier to follow. +* The [PeepCode screencast](https://peepcode.com/products/git) on Git is easier to follow. * [GitHub](http://help.github.com) offers links to a variety of Git resources. * [Pro Git](http://git-scm.com/book) is an entire book about Git with a Creative Commons license. diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index d49a30d02f494718a50f69434cb953996b2ac2c4..a17112436942e728ed8c1a510f9db01acbf891b1 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -64,7 +64,7 @@ Creating a New Rails Project The best way to use this guide is to follow each step as it happens, no code or step needed to make this example application has been left out, so you can literally follow along step by step. You can get the complete code -[here](https://github.com/lifo/docrails/tree/master/guides/code/getting_started). +[here](https://github.com/rails/docrails/tree/master/guides/code/getting_started). By following along with this guide, you'll create a Rails project called `blog`, a @@ -165,7 +165,7 @@ TIP: Compiling CoffeeScript to JavaScript requires a JavaScript runtime and the This will fire up WEBrick, a webserver built into Ruby by default. To see your application in action, open a browser window and navigate to . You should see the Rails default information page: -![Welcome Aboard screenshot](images/rails_welcome.png) +![Welcome Aboard screenshot](images/getting_started/rails_welcome.png) TIP: To stop the web server, hit Ctrl+C in the terminal window where it's running. To verify the server has stopped you should see your command prompt cursor again. For most UNIX-like systems including Mac OS X this will be a dollar sign `$`. In development mode, Rails does not generally require you to restart the server; changes you make in files will be automatically picked up by the server. @@ -252,29 +252,43 @@ Now that you've seen how to create a controller, an action and a view, let's cre In the Blog application, you will now create a new _resource_. A resource is the term used for a collection of similar objects, such as posts, people or animals. You can create, read, update and destroy items for a resource and these operations are referred to as _CRUD_ operations. -In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this: +Rails provides a `resources` method which can be used to declare a +standard REST resource. Here's how `config/routes.rb` will look like. -![The new post form](images/getting_started/new_post.png) +```ruby +Blog::Application.routes.draw do -It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. + resources :posts -### Laying down the ground work + root to: "welcome#index" +end +``` -The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. If you attempt to navigate to that now — by visiting — Rails will give you a routing error: +If you run `rake routes`, you'll see that all the routes for the +standard RESTful actions. -![A routing error, no route matches /posts/new](images/getting_started/routing_error_no_route_matches.png) +```bash +$ rake routes + posts GET /posts(.:format) posts#index + POST /posts(.:format) posts#create + new_post GET /posts/new(.:format) posts#new +edit_post GET /posts/:id/edit(.:format) posts#edit + post GET /posts/:id(.:format) posts#show + PATCH /posts/:id(.:format) posts#update + PUT /posts/:id(.:format) posts#update + DELETE /posts/:id(.:format) posts#destroy + root / welcome#index +``` -This is because there is nowhere inside the routes for the application — defined inside `config/routes.rb` — that defines this route. By default, Rails has no routes configured at all, besides the root route you defined earlier, and so you must define your routes as you need them. +In the next section, you will add the ability to create new posts in your application and be able to view them. This is the "C" and the "R" from CRUD: creation and reading. The form for doing this will look like this: - To do this, you're going to need to create a route inside `config/routes.rb` file, on a new line between the `do` and the `end` for the `draw` method: +![The new post form](images/getting_started/new_post.png) -```ruby -get "posts/new" -``` +It will look a little basic for now, but that's ok. We'll look at improving the styling for it afterwards. -This route is a super-simple route: it defines a new route that only responds to `GET` requests, and that the route is at `posts/new`. But how does it know where to go without the use of the `:to` option? Well, Rails uses a sensible default here: Rails will assume that you want this route to go to the new action inside the posts controller. +### Laying down the ground work -With the route defined, requests can now be made to `/posts/new` in the application. Navigate to and you'll see another routing error: +The first thing that you are going to need to create a new post within the application is a place to do that. A great place for that would be at `/posts/new`. With the route already defined, requests can now be made to `/posts/new` in the application. Navigate to and you'll see a routing error: ![Another routing error, uninitialized constant PostsController](images/getting_started/routing_error_no_controller.png) @@ -377,14 +391,10 @@ like this is called "create", and so the form should be pointed to that action. Edit the `form_for` line inside `app/views/posts/new.html.erb` to look like this: ```html+erb -<%= form_for :post, url: { action: :create } do |f| %> +<%= form_for :post, url: posts_path do |f| %> ``` -In this example, a `Hash` object is passed to the `:url` option. What Rails will do with this is that it will point the form to the `create` action of the current controller, the `PostsController`, and will send a `POST` request to that route. For this to work, you will need to add a route to `config/routes.rb`, right underneath the one for "posts/new": - -```ruby -post "posts" => "posts#create" -``` +In this example, the `posts_path` helper is passed to the `:url` option. What Rails will do with this is that it will point the form to the `create` action of the current controller, the `PostsController`, and will send a `POST` request to that route. By using the `post` method rather than the `get` method, Rails will define a route that will only respond to POST methods. The POST method is the typical method used by forms all over the web. @@ -521,21 +531,28 @@ and change the `create` action to look like this: ```ruby def create - @post = Post.new(params[:post]) - + @post = Post.new(post_params) + @post.save - redirect_to action: :show, id: @post.id + redirect_to @post end + +private + def post_params + params.require(:post).permit(:title, :text) + end ``` Here's what's going on: every Rails model can be initialized with its respective attributes, which are automatically mapped to the respective database columns. In the first line we do just that (remember that -`params[:post]` contains the attributes we're interested in). Then, +`post_params` contains the attributes we're interested in). Then, `@post.save` is responsible for saving the model in the database. Finally, we redirect the user to the `show` action, which we'll define later. +TIP: Note that `def post_params` is private. This new approach prevents an attacker from setting the model's attributes by manipulating the hash passed to the model. For more information, refer to [this blog post about Strong Parameters](http://weblog.rubyonrails.org/2012/3/21/strong-parameters/). + TIP: As we'll see later, `@post.save` returns a boolean indicating whether the model was saved or not. @@ -543,16 +560,14 @@ whether the model was saved or not. If you submit the form again now, Rails will complain about not finding the `show` action. That's not very useful though, so let's add the -`show` action before proceeding. Open `config/routes.rb` and add the following route: +`show` action before proceeding. ```ruby -get "posts/:id" => "posts#show" +post GET /posts/:id(.:format) posts#show ``` The special syntax `:id` tells rails that this route expects an `:id` -parameter, which in our case will be the id of the post. Note that this -time we had to specify the actual mapping, `posts#show` because -otherwise Rails would not know which action to render. +parameter, which in our case will be the id of the post. As we did before, we need to add the `show` action in `app/controllers/posts_controller.rb` and its respective view. @@ -601,7 +616,7 @@ look like this: @post = Post.new(params[:post].permit(:title, :text)) @post.save - redirect_to action: :show, id: @post.id + redirect_to @post end ``` @@ -613,11 +628,11 @@ Visit and give it a try! ### Listing all posts -We still need a way to list all our posts, so let's do that. As usual, -we'll need a route placed into `config/routes.rb`: +We still need a way to list all our posts, so let's do that. +We'll use a specific route from `config/routes.rb`: ```ruby -get "posts" => "posts#index" +posts GET /posts(.:format) posts#index ``` And an action for that route inside the `PostsController` in the `app/controllers/posts_controller.rb` file: @@ -669,7 +684,7 @@ for posts. Let's add links to the other views as well, starting with adding this "New Post" link to `app/views/posts/index.html.erb`, placing it above the `` tag: ```erb -<%= link_to 'New post', action: :new %> +<%= link_to 'New post', new_post_path %> ``` This link will allow you to bring up the form that lets you create a new post. You should also add a link to this template — `app/views/posts/new.html.erb` — to go back to the `index` action. Do this by adding this underneath the form in this template: @@ -679,7 +694,7 @@ This link will allow you to bring up the form that lets you create a new post. Y ... <% end %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` Finally, add another link to the `app/views/posts/show.html.erb` template to go back to the `index` action as well, so that people who are viewing a single post can go back and view the whole list again: @@ -695,7 +710,7 @@ Finally, add another link to the `app/views/posts/show.html.erb` template to go <%= @post.text %>

-<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` TIP: If you want to link to an action in the same controller, you don't @@ -755,7 +770,7 @@ def create @post = Post.new(params[:post].permit(:title, :text)) if @post.save - redirect_to action: :show, id: @post.id + redirect_to @post else render 'new' end @@ -776,7 +791,7 @@ something went wrong. To do that, you'll modify `app/views/posts/new.html.erb` to check for error messages: ```html+erb -<%= form_for :post, url: { action: :create } do |f| %> +<%= form_for :post, url: posts_path do |f| %> <% if @post.errors.any? %>

<%= pluralize(@post.errors.count, "error") %> prohibited @@ -803,7 +818,7 @@ something went wrong. To do that, you'll modify

<% end %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` A few things are going on. We check if there are any errors with @@ -832,14 +847,6 @@ We've covered the "CR" part of CRUD. Now let's focus on the "U" part, updating p The first step we'll take is adding an `edit` action to `posts_controller`. -Start by adding a route to `config/routes.rb`: - -```ruby -get "posts/:id/edit" => "posts#edit" -``` - -And then add the controller action: - ```ruby def edit @post = Post.find(params[:id]) @@ -853,7 +860,7 @@ it look as follows: ```html+erb

Editing post

-<%= form_for :post, url: { action: :update, id: @post.id }, +<%= form_for :post, url: post_path(@post.id) }, method: :patch do |f| %> <% if @post.errors.any? %>
@@ -881,7 +888,7 @@ method: :patch do |f| %>

<% end %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` This time we point the form to the `update` action, which is not defined yet @@ -893,21 +900,14 @@ via the `PATCH` HTTP method which is the HTTP method you're expected to use to TIP: By default forms built with the _form_for_ helper are sent via `POST`. -Next, we need to add the `update` action. The file -`config/routes.rb` will need just one more line: - -```ruby -patch "posts/:id" => "posts#update" -``` - -And then create the `update` action in `app/controllers/posts_controller.rb`: +Next we need to create the `update` action in `app/controllers/posts_controller.rb`: ```ruby def update @post = Post.find(params[:id]) if @post.update(params[:post].permit(:title, :text)) - redirect_to action: :show, id: @post.id + redirect_to @post else render 'edit' end @@ -941,8 +941,8 @@ appear next to the "Show" link:
- - + + <% end %>
<%= post.title %> <%= post.text %><%= link_to 'Show', action: :show, id: post.id %><%= link_to 'Edit', action: :edit, id: post.id %><%= link_to 'Show', post_path %><%= link_to 'Edit', edit_post_path(post) %>
@@ -955,8 +955,8 @@ the template: ```html+erb ... -<%= link_to 'Back', action: :index %> -| <%= link_to 'Edit', action: :edit, id: @post.id %> +<%= link_to 'Back', posts_path %> +| <%= link_to 'Edit', edit_post_path(@post) %> ``` And here's how our app looks so far: @@ -1016,7 +1016,7 @@ completely: <%= render 'form' %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` Then do the same for the `app/views/posts/edit.html.erb` view: @@ -1026,66 +1026,17 @@ Then do the same for the `app/views/posts/edit.html.erb` view: <%= render 'form' %> -<%= link_to 'Back', action: :index %> +<%= link_to 'Back', posts_path %> ``` -Point your browser to and -try creating a new post. Everything still works. Now try editing the -post and you'll receive the following error: - -![Undefined method post_path](images/getting_started/undefined_method_post_path.png) - -To understand this error, you need to understand how `form_for` works. -When you pass an object to `form_for` and you don't specify a `:url` -option, Rails will try to guess the `action` and `method` options by -checking if the passed object is a new record or not. Rails follows the -REST convention, so to create a new `Post` object it will look for a -route named `posts_path`, and to update a `Post` object it will look for -a route named `post_path` and pass the current object. Similarly, rails -knows that it should create new objects via POST and update them via -PATCH. - -If you run `rake routes` from the console you'll see that we already -have a `posts_path` route, which was created automatically by Rails when we -defined the route for the index action. -However, we don't have a `post_path` yet, which is the reason why we -received an error before. With your server running you can view your routes by visiting [localhost:3000/rails/info/routes](http://localhost:3000/rails/info/routes), or you can generate them from the command line by running `rake routes`: - -```bash -$ rake routes - - posts GET /posts(.:format) posts#index -posts_new GET /posts/new(.:format) posts#new - POST /posts(.:format) posts#create - GET /posts/:id(.:format) posts#show - GET /posts/:id/edit(.:format) posts#edit - PATCH /posts/:id(.:format) posts#update - root / welcome#index -``` - -To fix this, open `config/routes.rb` and modify the `get "posts/:id"` -line like this: - -```ruby -get "posts/:id" => "posts#show", as: :post -``` - -The `:as` option tells the `get` method that we want to make routing helpers -called `post_url` and `post_path` available to our application. These are -precisely the methods that the `form_for` needs when editing a post, and so now -you'll be able to update posts again. - -NOTE: The `:as` option is available on the `post`, `patch`, `put`, `delete` and `match` -routing methods also. - ### Deleting Posts We're now ready to cover the "D" part of CRUD, deleting posts from the -database. Following the REST convention, we're going to add a route for -deleting posts to `config/routes.rb`: +database. Following the REST convention, the route for +deleting posts in the `config/routes.rb` is: ```ruby -delete "posts/:id" => "posts#destroy" +DELETE /posts/:id(.:format) posts#destroy ``` The `delete` routing method should be used for routes that destroy @@ -1105,7 +1056,7 @@ def destroy @post = Post.find(params[:id]) @post.destroy - redirect_to action: :index + redirect_to posts_path end ``` @@ -1132,18 +1083,17 @@ together. <%= post.title %> <%= post.text %> - <%= link_to 'Show', action: :show, id: post.id %> - <%= link_to 'Edit', action: :edit, id: post.id %> - <%= link_to 'Destroy', { action: :destroy, id: post.id }, + <%= link_to 'Show', post_path %> + <%= link_to 'Edit', edit_post_path(post) %> + <%= link_to 'Destroy', post_path(post), method: :delete, data: { confirm: 'Are you sure?' } %> <% end %> ``` -Here we're using `link_to` in a different way. We wrap the -`:action` and `:id` attributes in a hash so that we can pass those two keys in -first as one argument, and then the final two keys as another argument. The `:method` and `:'data-confirm'` +Here we're using `link_to` in a different way. We pass the named route as the first argument, +and then the final two keys as another argument. The `:method` and `:'data-confirm'` options are used as HTML5 attributes so that when the link is clicked, Rails will first show a confirm dialog to the user, and then submit the link with method `delete`. This is done via the JavaScript file `jquery_ujs` which is automatically included @@ -1153,62 +1103,11 @@ generated the application. Without this file, the confirmation dialog box wouldn ![Confirm Dialog](images/getting_started/confirm_dialog.png) Congratulations, you can now create, show, list, update and destroy -posts. In the next section will see how Rails can aid us when creating -REST applications, and how we can refactor our Blog app to take -advantage of it. - -### Going Deeper into REST - -We've now covered all the CRUD actions of a REST app. We did so by -declaring separate routes with the appropriate verbs into -`config/routes.rb`. Here's how that file looks so far: - -```ruby -get "posts" => "posts#index" -get "posts/new" -post "posts" => "posts#create" -get "posts/:id" => "posts#show", as: :post -get "posts/:id/edit" => "posts#edit" -patch "posts/:id" => "posts#update" -delete "posts/:id" => "posts#destroy" -``` - -That's a lot to type for covering a single **resource**. Fortunately, -Rails provides a `resources` method which can be used to declare a -standard REST resource. Here's how `config/routes.rb` looks after the -cleanup: - -```ruby -Blog::Application.routes.draw do - - resources :posts - - root to: "welcome#index" -end -``` - -If you run `rake routes`, you'll see that all the routes that we -declared before are still available: - -```bash -$ rake routes - posts GET /posts(.:format) posts#index - POST /posts(.:format) posts#create - new_post GET /posts/new(.:format) posts#new -edit_post GET /posts/:id/edit(.:format) posts#edit - post GET /posts/:id(.:format) posts#show - PATCH /posts/:id(.:format) posts#update - PUT /posts/:id(.:format) posts#update - DELETE /posts/:id(.:format) posts#destroy - root / welcome#index -``` - -Also, if you go through the motions of creating, updating and deleting -posts the app still works as before. +posts. TIP: In general, Rails encourages the use of resources objects in place -of declaring routes manually. It was only done in this guide as a learning -exercise. For more information about routing, see +of declaring routes manually. +For more information about routing, see [Rails Routing from the Outside In](routing.html). Adding a Second Model @@ -1722,7 +1621,7 @@ class CommentsController < ApplicationController Now if you try to create a new post, you will be greeted with a basic HTTP Authentication challenge -![Basic HTTP Authentication Challenge](images/challenge.png) +![Basic HTTP Authentication Challenge](images/getting_started/challenge.png) What's Next? ------------ diff --git a/guides/source/i18n.md b/guides/source/i18n.md index d187d3a03a0365a82d5b83307d790ff8004baff4..3c4c1e2cb38f201ed021b005dfcd50b2e5a04bb8 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -258,7 +258,7 @@ match '/:locale' => 'dashboard#index' Do take special care about the **order of your routes**, so this route declaration does not "eat" other ones. (You may want to add it directly before the `root :to` declaration.) -NOTE: Have a look at two plugins which simplify work with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master). +NOTE: Have a look at two plugins which simplify work with routes in this way: Sven Fuchs's [routing_filter](https://github.com/svenfuchs/routing-filter/tree/master) and Raul Murciano's [translate_routes](https://github.com/raul/translate_routes/tree/master). ### Setting the Locale from the Client Supplied Information diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 412f2faaaa0b601b0212f75c4e85947ec26456b5..72d56b7feba10402431c91aca0175e01eb507253 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -59,35 +59,33 @@ dependencies of the application. `config/boot.rb` sets `ENV['BUNDLE_GEMFILE']` to the location of this file. If the Gemfile exists, `bundler/setup` is then required. -The gems that a Rails 4 application depends on are as follows: - -TODO: change these when the Rails 4 release is near. - -* abstract (1.0.0) -* actionmailer (4.0.0.beta) -* actionpack (4.0.0.beta) -* activemodel (4.0.0.beta) -* activerecord (4.0.0.beta) -* activesupport (4.0.0.beta) -* arel (2.0.7) -* builder (3.0.0) -* bundler (1.0.6) -* erubis (2.6.6) -* i18n (0.5.0) -* mail (2.2.12) -* mime-types (1.16) -* polyglot (0.3.1) -* rack (1.2.1) -* rack-cache (0.5.3) -* rack-mount (0.6.13) -* rack-test (0.5.6) -* rails (4.0.0.beta) -* railties (4.0.0.beta) -* rake (0.8.7) -* sqlite3-ruby (1.3.2) -* thor (0.14.6) -* treetop (1.4.9) -* tzinfo (0.3.23) +A standard Rails application depends on several gems, specifically: + +* abstract +* actionmailer +* actionpack +* activemodel +* activerecord +* activesupport +* arel +* builder +* bundler +* erubis +* i18n +* mail +* mime-types +* polyglot +* rack +* rack-cache +* rack-mount +* rack-test +* rails +* railties +* rake +* sqlite3-ruby +* thor +* treetop +* tzinfo ### `rails/commands.rb` @@ -131,7 +129,7 @@ when 'server' end ``` -This file will change into the root of the directory (a path two directories back from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class. +This file will change into the Rails root directory (a path two directories up from `APP_PATH` which points at `config/application.rb`), but only if the `config.ru` file isn't found. This then requires `rails/commands/server` which sets up the `Rails::Server` class. ```ruby require 'fileutils' @@ -147,11 +145,11 @@ module Rails ### `actionpack/lib/action_dispatch.rb` Action Dispatch is the routing component of the Rails framework. -It adds functionalities like routing, session, and common middlewares. +It adds functionality like routing, session, and common middlewares. ### `rails/commands/server.rb` -The `Rails::Server` class is defined in this file as inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`: +The `Rails::Server` class is defined in this file by inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`: ```ruby def initialize(*) @@ -441,14 +439,14 @@ inside each of those frameworks, but you're encouraged to try and explore them on your own. For now, just keep in mind that common functionality like Rails engines, -I18n and Rails configuration is all being defined here. +I18n and Rails configuration are all being defined here. ### Back to `config/environment.rb` When `config/application.rb` has finished loading Rails, and defined -your application namespace, you go back to `config/environment.rb`, -where your application is initialized. For example, if you application was called -`Blog`, here you would find `Blog::Application.initialize!`, which is +the application namespace, we go back to `config/environment.rb`, +where the application is initialized. For example, if the application was called +`Blog`, here we would find `Blog::Application.initialize!`, which is defined in `rails/application.rb` ### `railties/lib/rails/application.rb` diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 397dd626383fbbcea663fc8b28142835cdd624be..1dcf383b92e1b6c48b4fa7a5feca12473012d306 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -102,11 +102,11 @@

If you see any typos or factual errors you are confident to - patch, please clone <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> - and push the change yourself. That branch of Rails has public write access. - Commits are still reviewed, but that happens after you've submitted your - contribution. <%= link_to 'docrails', 'https://github.com/lifo/docrails' %> is - cross-merged with master periodically. + patch, please clone the <%= link_to 'rails', 'https://github.com/rails/rails' %> + repository and open a new pull request. You can also ask for commit rights on + <%= link_to 'docrails', 'https://github.com/rails/docrails' %> if you plan to submit + several patches. Commits are reviewed, but that happens after you've submitted your + contribution. This repository is cross-merged with master periodically.

You may also find incomplete content, or stuff that is not up to date. diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index addc0a5430195ca49e51625442807b08236625e6..1ab841b137cb7b7f12fbb876a69a336e8f6b5c71 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1,7 +1,7 @@ Layouts and Rendering in Rails ============================== -This guide covers the basic layout features of Action Controller and Action View. +This guide covers the basic layout features of Action Controller and Action View. After reading this guide, you will know: @@ -283,8 +283,8 @@ Calls to the `render` method generally accept four options: * `:content_type` * `:layout` -* `:status` * `:location` +* `:status` ##### The `:content_type` Option @@ -310,80 +310,86 @@ You can also tell Rails to render with no layout at all: render layout: false ``` -##### The `:status` Option +##### The `:location` Option -Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this: +You can use the `:location` option to set the HTTP `Location` header: ```ruby -render status: 500 -render status: :forbidden +render xml: photo, location: photo_url(photo) ``` -Rails understands both numeric status codes and the corresponding symbols shown below: - -| HTTP Status Code | Symbol | -| ---------------- | -------------------------------- | -| 100 | :continue | -| 101 | :switching_protocols | -| 102 | :processing | -| 200 | :ok | -| 201 | :created | -| 202 | :accepted | -| 203 | :non_authoritative_information | -| 204 | :no_content | -| 205 | :reset_content | -| 206 | :partial_content | -| 207 | :multi_status | -| 226 | :im_used | -| 300 | :multiple_choices | -| 301 | :moved_permanently | -| 302 | :found | -| 303 | :see_other | -| 304 | :not_modified | -| 305 | :use_proxy | -| 306 | :reserved | -| 307 | :temporary_redirect | -| 400 | :bad_request | -| 401 | :unauthorized | -| 402 | :payment_required | -| 403 | :forbidden | -| 404 | :not_found | -| 405 | :method_not_allowed | -| 406 | :not_acceptable | -| 407 | :proxy_authentication_required | -| 408 | :request_timeout | -| 409 | :conflict | -| 410 | :gone | -| 411 | :length_required | -| 412 | :precondition_failed | -| 413 | :request_entity_too_large | -| 414 | :request_uri_too_long | -| 415 | :unsupported_media_type | -| 416 | :requested_range_not_satisfiable | -| 417 | :expectation_failed | -| 418 | :i'm_a_teapot | -| 422 | :unprocessable_entity | -| 423 | :locked | -| 424 | :failed_dependency | -| 426 | :upgrade_required | -| 500 | :internal_server_error | -| 501 | :not_implemented | -| 502 | :bad_gateway | -| 503 | :service_unavailable | -| 504 | :gateway_timeout | -| 505 | :http_version_not_supported | -| 506 | :variant_also_negotiates | -| 507 | :insufficient_storage | -| 510 | :not_extended | - -##### The `:location` Option +##### The `:status` Option -You can use the `:location` option to set the HTTP `Location` header: +Rails will automatically generate a response with the correct HTTP status code (in most cases, this is `200 OK`). You can use the `:status` option to change this: ```ruby -render xml: photo, location: photo_url(photo) +render status: 500 +render status: :forbidden ``` +Rails understands both numeric status codes and the corresponding symbols shown below. + +| Response Class | HTTP Status Code | Symbol | +| ------------------- | ---------------- | -------------------------------- | +| **Informational** | 100 | :continue | +| | 101 | :switching_protocols | +| | 102 | :processing | +| **Success** | 200 | :ok | +| | 201 | :created | +| | 202 | :accepted | +| | 203 | :non_authoritative_information | +| | 204 | :no_content | +| | 205 | :reset_content | +| | 206 | :partial_content | +| | 207 | :multi_status | +| | 208 | :already_reported | +| | 226 | :im_used | +| **Redirection** | 300 | :multiple_choices | +| | 301 | :moved_permanently | +| | 302 | :found | +| | 303 | :see_other | +| | 304 | :not_modified | +| | 305 | :use_proxy | +| | 306 | :reserved | +| | 307 | :temporary_redirect | +| | 308 | :permanent_redirect | +| **Client Error** | 400 | :bad_request | +| | 401 | :unauthorized | +| | 402 | :payment_required | +| | 403 | :forbidden | +| | 404 | :not_found | +| | 405 | :method_not_allowed | +| | 406 | :not_acceptable | +| | 407 | :proxy_authentication_required | +| | 408 | :request_timeout | +| | 409 | :conflict | +| | 410 | :gone | +| | 411 | :length_required | +| | 412 | :precondition_failed | +| | 413 | :request_entity_too_large | +| | 414 | :request_uri_too_long | +| | 415 | :unsupported_media_type | +| | 416 | :requested_range_not_satisfiable | +| | 417 | :expectation_failed | +| | 422 | :unprocessable_entity | +| | 423 | :locked | +| | 424 | :failed_dependency | +| | 426 | :upgrade_required | +| | 423 | :precondition_required | +| | 424 | :too_many_requests | +| | 426 | :request_header_fields_too_large | +| **Server Error** | 500 | :internal_server_error | +| | 501 | :not_implemented | +| | 502 | :bad_gateway | +| | 503 | :service_unavailable | +| | 504 | :gateway_timeout | +| | 505 | :http_version_not_supported | +| | 506 | :variant_also_negotiates | +| | 507 | :insufficient_storage | +| | 508 | :loop_detected | +| | 510 | :not_extended | +| | 511 | :network_authentication_required | + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. @@ -633,7 +639,7 @@ This would detect that there are no books with the specified ID, populate the `@ ### Using `head` To Build Header-Only Responses -The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method takes one parameter, which is interpreted as a hash of header names and values. For example, you can return only an error header: +The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: ```ruby head :bad_request @@ -709,7 +715,7 @@ There are three tag options available for the `auto_discovery_link_tag`: * `:rel` specifies the `rel` value in the link. The default value is "alternate". * `:type` specifies an explicit MIME type. Rails will generate an appropriate MIME type automatically. -* `:title` specifies the title of the link. The default value is the uppercased `:type` value, for example, "ATOM" or "RSS". +* `:title` specifies the title of the link. The default value is the uppercase `:type` value, for example, "ATOM" or "RSS". #### Linking to JavaScript Files with the `javascript_include_tag` diff --git a/guides/source/migrations.md b/guides/source/migrations.md index 086cf434d947a2078976314492c8fd38fe75d48c..a150cf291d1e8c66167eb8d1d0be4168b628f973 100644 --- a/guides/source/migrations.md +++ b/guides/source/migrations.md @@ -150,7 +150,25 @@ class AddPartNumberToProducts < ActiveRecord::Migration end ``` -Similarly, +If you'd like to add an index on the new column, you can do that as well: + +```bash +$ rails generate migration AddPartNumberToProducts part_number:string:index +``` + +will generate + +```ruby +class AddPartNumberToProducts < ActiveRecord::Migration + def change + add_column :products, :part_number, :string + add_index :products, :part_number + end +end +``` + + +Similarly, you can generate a migration to remove a column from the command line: ```bash $ rails generate migration RemovePartNumberFromProducts part_number:string @@ -374,8 +392,8 @@ will create the `product_id` and `category_id` with the `:null` option as ```ruby create_join_table :products, :categories do |t| - t.index :products - t.index :categories + t.index :product_id + t.index :category_id end ``` diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 136dfb4cae153250e163ac7eaa7c9335c5b600ee..d5d1ee0a383eed79e2d194690a3facce1cc19ea6 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -63,6 +63,10 @@ Those guidelines apply also to guides. HTML Guides ----------- +Before generating the guides, make sure that you have the latest version of Bundler installed on your system. As of this writing, you must install Bundler 1.3.5 on your device. + +To install the latest version of Bundler, simply run the `gem install bundler` command + ### Generation To generate all the guides, just `cd` into the `guides` directory, run `bundle install` and execute: diff --git a/guides/source/testing.md b/guides/source/testing.md index 4c0a61bc5e593081835f078d567a7ac1a684773e..b02d0b663c3527feb5c0947716793a98f74d0418 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -85,8 +85,8 @@ ERB allows you to embed Ruby code within templates. The YAML fixture format is p ```erb <% 1000.times do |n| %> user_<%= n %>: - username: <%= "user%03d" % n %> - email: <%= "user%03d@example.com" % n %> + username: <%= "user#{n}" %> + email: <%= "user#{n}@example.com" %> <% end %> ``` @@ -159,9 +159,10 @@ class PostTest < ActiveSupport::TestCase The `PostTest` class defines a _test case_ because it inherits from `ActiveSupport::TestCase`. `PostTest` thus has all the methods available from `ActiveSupport::TestCase`. You'll see those methods a little later in this guide. -Any method defined within a `Test::Unit` test case that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run. +Any method defined within a class inherited from `MiniTest::Unit::TestCase` +(which is the superclass of `ActiveSupport::TestCase`) that begins with `test` (case sensitive) is simply called a test. So, `test_password`, `test_valid_password` and `testValidPassword` all are legal test names and are run automatically when the test case is run. -Rails adds a `test` method that takes a test name and a block. It generates a normal `Test::Unit` test with method names prefixed with `test_`. So, +Rails adds a `test` method that takes a test name and a block. It generates a normal `MiniTest::Unit` test with method names prefixed with `test_`. So, ```ruby test "the truth" do @@ -346,31 +347,38 @@ NOTE: The execution of each test method stops as soon as any error or an asserti Ideally, you would like to include a test for everything which could possibly break. It's a good practice to have at least one test for each of your validations and at least one test for every method in your model. -### Assertions Available +### Available Assertions By now you've caught a glimpse of some of the assertions that are available. Assertions are the worker bees of testing. They are the ones that actually perform the checks to ensure that things are going as planned. -There are a bunch of different types of assertions you can use. Here's the complete list of assertions that ship with `test/unit`, the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required. +There are a bunch of different types of assertions you can use. +Here's an extract of the assertions you can use with `minitest`, the default testing library used by Rails. The `[msg]` parameter is an optional string message you can specify to make your test failure messages clearer. It's not required. | Assertion | Purpose | | ---------------------------------------------------------------- | ------- | -| `assert( boolean, [msg] )` | Ensures that the object/expression is true.| +| `assert( test, [msg] )` | Ensures that `test` is true.| +| `refute( test, [msg] )` | Ensures that `test` is false.| | `assert_equal( expected, actual, [msg] )` | Ensures that `expected == actual` is true.| -| `assert_not_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| +| `refute_equal( expected, actual, [msg] )` | Ensures that `expected != actual` is true.| | `assert_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is true.| -| `assert_not_same( expected, actual, [msg] )` | Ensures that `!expected.equal?(actual)` is true.| +| `refute_same( expected, actual, [msg] )` | Ensures that `expected.equal?(actual)` is false.| | `assert_nil( obj, [msg] )` | Ensures that `obj.nil?` is true.| -| `assert_not_nil( obj, [msg] )` | Ensures that `!obj.nil?` is true.| +| `refute_nil( obj, [msg] )` | Ensures that `obj.nil?` is false.| | `assert_match( regexp, string, [msg] )` | Ensures that a string matches the regular expression.| -| `assert_no_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| -| `assert_in_delta( expecting, actual, delta, [msg] )` | Ensures that the numbers `expecting` and `actual` are within `delta` of each other.| +| `refute_match( regexp, string, [msg] )` | Ensures that a string doesn't match the regular expression.| +| `assert_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are within `delta` of each other.| +| `refute_in_delta( expecting, actual, [delta], [msg] )` | Ensures that the numbers `expected` and `actual` are not within `delta` of each other.| | `assert_throws( symbol, [msg] ) { block }` | Ensures that the given block throws the symbol.| -| `assert_raise( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| +| `assert_raises( exception1, exception2, ... ) { block }` | Ensures that the given block raises one of the given exceptions.| | `assert_nothing_raised( exception1, exception2, ... ) { block }` | Ensures that the given block doesn't raise one of the given exceptions.| -| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is of the `class` type.| +| `assert_instance_of( class, obj, [msg] )` | Ensures that `obj` is an instance of `class`.| +| `refute_instance_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class`.| | `assert_kind_of( class, obj, [msg] )` | Ensures that `obj` is or descends from `class`.| -| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` has a method called `symbol`.| -| `assert_operator( obj1, operator, obj2, [msg] )` | Ensures that `obj1.operator(obj2)` is true.| +| `refute_kind_of( class, obj, [msg] )` | Ensures that `obj` is not an instance of `class` and is not descending from it.| +| `assert_respond_to( obj, symbol, [msg] )` | Ensures that `obj` responds to `symbol`.| +| `refute_respond_to( obj, symbol, [msg] )` | Ensures that `obj` does not respond to `symbol`.| +| `assert_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is true.| +| `refute_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| | `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true. This one is weird eh?| | `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.| @@ -475,7 +483,7 @@ NOTE: Functional tests do not verify whether the specified request type should b ### The Four Hashes of the Apocalypse -After a request has been made by using one of the 5 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use: +After a request has been made using one of the 6 methods (`get`, `post`, etc.) and processed, you will have 4 Hash objects ready for use: * `assigns` - Any objects that are stored as instance variables in actions for use in views. * `cookies` - Any cookies that are set. diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 51d6775c3ea4ec2e0eb696b65debbf94e067725f..1d14656f790fefefd1dd9b88f4122ab76614811c 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -118,7 +118,7 @@ Please read [Pull Request #9978](https://github.com/rails/rails/pull/9978) for d * 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 deprecates the `dom_id` and `dom_class` methods in controllers (they are fine in views). 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`. diff --git a/rails.gemspec b/rails.gemspec index 199346745536a0f4a81b22fae2d4ff2a6ec33b96..4a17beac6956c152aee6eceec8d2873024b74967 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -25,5 +25,5 @@ s.add_dependency 'railties', version s.add_dependency 'bundler', '>= 1.3.0', '< 2.0' - s.add_dependency 'sprockets-rails', '~> 2.0.0.rc4' + s.add_dependency 'sprockets-rails', '~> 2.0.0' end diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f67177a04724ce9a4f82c088af145f13a6d9b4e9..0395cc0a70365962556cad1a31326454e78848c6 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,4 +1,8 @@ -## Rails 4.0.0 (unreleased) ## +## Rails 4.0.0 (June 25, 2013) ## + +* Clearing autoloaded constants triggers routes reloading [Fixes #10685]. + + *Xavier Noria* * Move rails.png into a data-uri. One less file to get generated into a new application. This is also consistent with the removal of index.html. @@ -60,9 +64,6 @@ *Stanislav Sobolev* - -## Rails 4.0.0.beta1 (February 25, 2013) ## - * 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 2d5aa9d9527fa5b302ac27dbbcec6642b835a831..914e4393c405740c719b1be6c4e2f9ac9fa07859 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -93,6 +93,7 @@ def initialized? # dispatches the request to the underlying middleware stack. def call(env) env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) + env["ORIGINAL_SCRIPT_NAME"] = env["SCRIPT_NAME"] super(env) end diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 3ae60312c50724b9bfbe8f694f14d6004eaedc8a..7a1bb1e25c61909f718569970aa6c07b24ae17cd 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -62,17 +62,28 @@ module Finisher ActiveSupport.run_load_hooks(:after_initialize, self) end - # Set app reload just after the finisher hook to ensure - # routes added in the hook are still loaded. + # Set routes reload after the finisher hook to ensure routes added in + # the hook are taken into account. initializer :set_routes_reloader_hook do reloader = routes_reloader reloader.execute_if_updated self.reloaders << reloader - ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } + ActionDispatch::Reloader.to_prepare do + # We configure #execute rather than #execute_if_updated because if + # autoloaded constants are cleared we need to reload routes also in + # case any was used there, as in + # + # mount MailPreview => 'mail_view' + # + # This means routes are also reloaded if i18n is updated, which + # might not be necessary, but in order to be more precise we need + # some sort of reloaders dependency support, to be added. + reloader.execute + end end - # Set app reload just after the finisher hook to ensure - # paths added in the hook are still loaded. + # Set clearing dependencies after the finisher hook to ensure paths + # added in the hook are taken into account. initializer :set_clear_dependencies_hook, group: :all do callback = lambda do ActiveSupport::DescendantsTracker.clear @@ -82,9 +93,17 @@ module Finisher if config.reload_classes_only_on_change reloader = config.file_watcher.new(*watchable_args, &callback) self.reloaders << reloader - # We need to set a to_prepare callback regardless of the reloader result, i.e. - # models should be reloaded if any of the reloaders (i18n, routes) were updated. - ActionDispatch::Reloader.to_prepare(prepend: true){ reloader.execute } + + # Prepend this callback to have autoloaded constants cleared before + # any other possible reloading, in case they need to autoload fresh + # constants. + ActionDispatch::Reloader.to_prepare(prepend: true) do + # In addition to changes detected by the file watcher, if routes + # or i18n have been updated we also need to clear constants, + # that's why we run #execute rather than #execute_if_updated, this + # callback has to clear autoloaded constants after any update. + reloader.execute + end else ActionDispatch::Reloader.to_cleanup(&callback) end diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 0d1286031cf1f2652a70d44061f0d74a8ff3da92..aacde52cfce05321d55091324b8a76e2ed2ed73e 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -5,7 +5,6 @@ "d" => "destroy", "c" => "console", "s" => "server", - "t" => "test", "db" => "dbconsole", "r" => "runner" } @@ -17,7 +16,6 @@ generate Generate new code (short-cut alias: "g") console Start the Rails console (short-cut alias: "c") server Start the Rails server (short-cut alias: "s") - test Running the test file (short-cut alias: "t") dbconsole Start a console for the database specified in config/database.yml (short-cut alias: "db") new Create a new Rails application. "rails new my_app" creates a diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 57dc7e797b73ce330879c9e16ba0090de8cbaa2a..4df522b84e78dd9a3c7b9c9b6ce1e38c7fca4c63 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -116,7 +116,7 @@ def set_default_accessors! def database_gemfile_entry options[:skip_active_record] ? "" : - <<-GEMFILE.strip_heredoc.chomp + <<-GEMFILE.strip_heredoc # Use #{options[:database]} as the database for Active Record gem '#{gem_for_database}' GEMFILE @@ -193,7 +193,7 @@ def assets_gemfile_entry else <<-GEMFILE.strip_heredoc # Use SCSS for stylesheets - gem 'sass-rails', '~> 4.0.0.rc1' + gem 'sass-rails', '~> 4.0.0' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index b2d1be9b515e64a04645ea6d735a4f84e2ffde2c..5877579fc40cebba36c334c9faef572fd5ac8288 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -56,6 +56,7 @@ def gitignore def app directory 'app' + keep_file 'app/assets/images' keep_file 'app/mailers' keep_file 'app/models' diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index ace804ffe61a987a0d6a73f26119e142184719b9..577ff651e563f2feacb8f2034dd0ec311eaf55f8 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -4,13 +4,11 @@ source 'https://rubygems.org' <%= database_gemfile_entry -%> -<%= "gem 'jruby-openssl'\n" if defined?(JRUBY_VERSION) -%> - <%= assets_gemfile_entry %> <%= javascript_gemfile_entry -%> # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder -gem 'jbuilder', '~> 1.0.1' +gem 'jbuilder', '~> 1.2' group :doc do # bundle exec rake doc:rails generates the API under doc/api. diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep b/railties/lib/rails/generators/rails/app/templates/app/assets/images/.keep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index 7342bffd9d484313f4352fb28527f699d7147236..8b91313e5128e47c08fb8d2336c88d039d4e35a5 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -7,8 +7,8 @@ // 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. +// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details +// about supported directives. // <% unless options[:skip_javascript] -%> //= require <%= options[:javascript] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/environment.rb b/railties/lib/rails/generators/rails/app/templates/config/environment.rb index e080ebd74e195db8bf35f61da24a79d465af78ec..832a8619b8b162ba6a1278abf9bde02375ac5ea1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environment.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application. +# Load the Rails application. require File.expand_path('../application', __FILE__) -# Initialize the rails application. +# Initialize the Rails application. <%= app_const %>.initialize! diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 4f1d56cd2f4bdf494f925f646fb477c0a3672a87..f2110c2c70d144f3115fd2623334f5b31fc273ca 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -7,8 +7,8 @@ ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end - <%- unless options.skip_active_record? -%> + # To enable root element in JSON for ActiveRecord objects. # ActiveSupport.on_load(:active_record) do # self.include_root_in_json = true diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index f877fa1f8a7006573a2a21e14e62271a36bb3a26..1794ffa833bb3a508e51a6bd357a4ac58034b212 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -39,6 +39,13 @@ # get 'recent', on: :collection # end # end + + # Example resource route with concerns: + # concern :toggleable do + # post 'toggle' + # end + # resources :posts, concerns: :toggleable + # resources :photos, concerns: :toggleable # Example resource route within a namespace: # namespace :admin do diff --git a/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt index aa87d1b50c39363499ce37740061a695d667f928..c8de9f3e0ff07ffb1f3bf563521600fbd4959c5c 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin_new/templates/bin/rails.tt @@ -1,4 +1,4 @@ -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. +# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. ENGINE_ROOT = File.expand_path('../..', __FILE__) ENGINE_PATH = File.expand_path('../../lib/<%= name -%>/engine', __FILE__) 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 index 084d5d1c49425590ce575a3031d62bee1c1a0228..5bc2e1c8b55a621a15f9f51b12fb04d2c01c73b9 100644 --- a/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js +++ b/railties/lib/rails/generators/rails/plugin_new/templates/rails/javascripts.js @@ -7,7 +7,7 @@ // 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. +// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details +// about supported directives. // //= require_tree . diff --git a/railties/lib/rails/tasks/documentation.rake b/railties/lib/rails/tasks/documentation.rake index 1c3426028d2281d71c28fa496b74f2a93aada656..67270e7088bdc4afcac3161572815a44eec0cf7e 100644 --- a/railties/lib/rails/tasks/documentation.rake +++ b/railties/lib/rails/tasks/documentation.rake @@ -1,61 +1,72 @@ -require 'rdoc/task' -require 'rails/api/task' +begin + require 'rdoc/task' +rescue LoadError + # Rubinius installs RDoc as a gem, and for this interpreter "rdoc/task" is + # available only if the application bundle includes "rdoc" (normally as a + # dependency of the "sdoc" gem.) + # + # If RDoc is not available it is fine that we do not generate the tasks that + # depend on it. Just be robust to this gotcha and go on. +else + require 'rails/api/task' -# Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise -class RDocTaskWithoutDescriptions < RDoc::Task - include ::Rake::DSL + # Monkey-patch to remove redoc'ing and clobber descriptions to cut down on rake -T noise + class RDocTaskWithoutDescriptions < RDoc::Task + include ::Rake::DSL - def define - task rdoc_task_name + def define + task rdoc_task_name - task rerdoc_task_name => [clobber_task_name, rdoc_task_name] + task rerdoc_task_name => [clobber_task_name, rdoc_task_name] - task clobber_task_name do - rm_r rdoc_dir rescue nil - end + task clobber_task_name do + rm_r rdoc_dir rescue nil + end - task :clobber => [clobber_task_name] + task :clobber => [clobber_task_name] - directory @rdoc_dir - task rdoc_task_name => [rdoc_target] - file rdoc_target => @rdoc_files + [Rake.application.rakefile] do - rm_r @rdoc_dir rescue nil - @before_running_rdoc.call if @before_running_rdoc - args = option_list + @rdoc_files - if @external - argstring = args.join(' ') - sh %{ruby -Ivendor vendor/rd #{argstring}} - else - require 'rdoc/rdoc' - RDoc::RDoc.new.document(args) + directory @rdoc_dir + task rdoc_task_name => [rdoc_target] + file rdoc_target => @rdoc_files + [Rake.application.rakefile] do + rm_r @rdoc_dir rescue nil + @before_running_rdoc.call if @before_running_rdoc + args = option_list + @rdoc_files + if @external + argstring = args.join(' ') + sh %{ruby -Ivendor vendor/rd #{argstring}} + else + require 'rdoc/rdoc' + RDoc::RDoc.new.document(args) + end end + self end - self end -end -namespace :doc do - def gem_path(gem_name) - path = $LOAD_PATH.grep(/#{gem_name}[\w.-]*\/lib$/).first - yield File.dirname(path) if path - end + namespace :doc do + def gem_path(gem_name) + path = $LOAD_PATH.grep(/#{gem_name}[\w.-]*\/lib$/).first + yield File.dirname(path) if path + end - RDocTaskWithoutDescriptions.new("app") { |rdoc| - rdoc.rdoc_dir = 'doc/app' - rdoc.template = ENV['template'] if ENV['template'] - rdoc.title = ENV['title'] || "Rails Application Documentation" - rdoc.options << '--line-numbers' - rdoc.options << '--charset' << 'utf-8' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('app/**/*.rb') - rdoc.rdoc_files.include('lib/**/*.rb') - } - Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")" + RDocTaskWithoutDescriptions.new("app") { |rdoc| + rdoc.rdoc_dir = 'doc/app' + rdoc.template = ENV['template'] if ENV['template'] + rdoc.title = ENV['title'] || "Rails Application Documentation" + rdoc.options << '--line-numbers' + rdoc.options << '--charset' << 'utf-8' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('app/**/*.rb') + rdoc.rdoc_files.include('lib/**/*.rb') + } + Rake::Task['doc:app'].comment = "Generate docs for the app -- also available doc:rails, doc:guides (options: TEMPLATE=/rdoc-template.rb, TITLE=\"Custom Title\")" - # desc 'Generate documentation for the Rails framework.' - Rails::API::AppTask.new('rails') + # desc 'Generate documentation for the Rails framework.' + Rails::API::AppTask.new('rails') + end +end - # desc "Generate Rails Guides" +namespace :doc do task :guides do # FIXME: Reaching outside lib directory is a bad idea require File.expand_path('../../../../../guides/rails_guides', __FILE__) diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 2116330b450c136d9a98b587d8d3648f4636a2cb..76a95304c31512d443bd0dca67288d2e2d8941c5 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -47,7 +47,7 @@ namespace :rails do gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: Rails.root File.exists?(Rails.root.join("config", "application.rb")) ? - gen.send(:app_const) : gen.send(:valid_app_const?) + gen.send(:app_const) : gen.send(:valid_const?) gen end end diff --git a/railties/lib/rails/test_unit/railtie.rb b/railties/lib/rails/test_unit/railtie.rb index ab1ebe1f8ca53c297d38cce28aba6f98893349af..75180ff97829951e5d82a507c06543919b62b770 100644 --- a/railties/lib/rails/test_unit/railtie.rb +++ b/railties/lib/rails/test_unit/railtie.rb @@ -1,4 +1,4 @@ -if defined?(Rake) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? +if defined?(Rake.application) && Rake.application.top_level_tasks.grep(/^(default$|test(:|$))/).any? ENV['RAILS_ENV'] ||= 'test' end diff --git a/railties/lib/rails/version.rb b/railties/lib/rails/version.rb index dcbf57a4dfebb9ed5528957f6d3ec9666fa8b611..180c9712a967885546bf4d29390bbac306e7ce9e 100644 --- a/railties/lib/rails/version.rb +++ b/railties/lib/rails/version.rb @@ -3,7 +3,7 @@ module VERSION MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "rc1" + PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 1acf03f35ac3f2551fa8040efe62520b4f223a70..aeab311e7eaa96c410504a8bcd26f6f6df72a19a 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -62,6 +62,7 @@ def teardown require "#{app_path}/config/environment" ActiveRecord::Migrator.stubs(:needs_migration?).returns(true) + ActiveRecord::NullMigration.any_instance.stubs(:mtime).returns(1) get "/foo" assert_equal 500, last_response.status diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb index ad7172c514217744cc21656306fd3220198da48e..662eb649515f760523a7f0d49272ce64bed56ddc 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -179,7 +179,7 @@ def self.counter; 2; end RUBY app_file 'config/routes.rb', <<-RUBY - $counter = 0 + $counter ||= 0 AppTemplate::Application.routes.draw do get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } end @@ -205,6 +205,39 @@ class User assert_equal "2", last_response.body end + test "dependencies reloading is followed by routes reloading" do + add_to_config <<-RUBY + config.cache_classes = false + RUBY + + app_file 'config/routes.rb', <<-RUBY + $counter ||= 1 + $counter *= 2 + AppTemplate::Application.routes.draw do + get '/c', to: lambda { |env| User; [200, {"Content-Type" => "text/plain"}, [$counter.to_s]] } + end + RUBY + + app_file "app/models/user.rb", <<-MODEL + class User + $counter += 1 + end + MODEL + + require 'rack/test' + extend Rack::Test::Methods + + require "#{rails_root}/config/environment" + + get "/c" + assert_equal "3", last_response.body + + app_file "db/schema.rb", "" + + get "/c" + assert_equal "7", last_response.body + end + test "columns migrations also trigger reloading" do add_to_config <<-RUBY config.cache_classes = false diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 5fdf58c8eed2ea1b62917bb0bacc6bee688ee3cf..74c4c4e6ed33fc8dc447779900ef4d7f2aa2cf7a 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -183,9 +183,6 @@ def test_config_jdbcmysql_database run_generator([destination_root, "-d", "jdbcmysql"]) assert_file "config/database.yml", /mysql/ assert_gem "activerecord-jdbcmysql-adapter" - # TODO: When the JRuby guys merge jruby-openssl in - # jruby this will be removed - assert_gem "jruby-openssl" if defined?(JRUBY_VERSION) end def test_config_jdbcsqlite3_database @@ -260,6 +257,11 @@ def test_creation_of_a_test_directory assert_file 'test' end + def test_creation_of_app_assets_images_directory + run_generator + assert_file "app/assets/images" + end + def test_creation_of_vendor_assets_javascripts_directory run_generator assert_file "vendor/assets/javascripts" diff --git a/railties/test/railties/mounted_engine_test.rb b/railties/test/railties/mounted_engine_test.rb index 80559a6e365f17300c8e05fbf9375ca9110312f3..c94937f964b1eeb2dddbb6a12425558ba7bb598f 100644 --- a/railties/test/railties/mounted_engine_test.rb +++ b/railties/test/railties/mounted_engine_test.rb @@ -13,6 +13,7 @@ def setup @simple_plugin = engine "weblog" @plugin = engine "blog" + @metrics_plugin = engine "metrics" app_file 'config/routes.rb', <<-RUBY AppTemplate::Application.routes.draw do @@ -28,6 +29,7 @@ def setup scope "/:user", :user => "anonymous" do mount Blog::Engine => "/blog" end + mount Metrics::Engine => "/metrics" root :to => 'main#index' end RUBY @@ -54,6 +56,34 @@ def index end RUBY + @metrics_plugin.write "lib/metrics.rb", <<-RUBY + module Metrics + class Engine < ::Rails::Engine + isolate_namespace(Metrics) + end + end + RUBY + + @metrics_plugin.write "config/routes.rb", <<-RUBY + Metrics::Engine.routes.draw do + get '/generate_blog_route', to: 'generating#generate_blog_route' + get '/generate_blog_route_in_view', to: 'generating#generate_blog_route_in_view' + end + RUBY + + @metrics_plugin.write "app/controllers/metrics/generating_controller.rb", <<-RUBY + module Metrics + class GeneratingController < ActionController::Base + def generate_blog_route + render text: blog.post_path(1) + end + + def generate_blog_route_in_view + render inline: "<%= blog.post_path(1) -%>" + end + end + end + RUBY @plugin.write "app/models/blog/post.rb", <<-RUBY module Blog @@ -201,6 +231,21 @@ def app get "/somone/blog/application_route_in_view" assert_equal "/", last_response.body + # test generating engine's route from other engine + get "/metrics/generate_blog_route" + assert_equal '/anonymous/blog/posts/1', last_response.body + + get "/metrics/generate_blog_route_in_view" + assert_equal '/anonymous/blog/posts/1', last_response.body + + # test generating engine's route from other engine with default_url_options + get "/metrics/generate_blog_route", {}, 'SCRIPT_NAME' => '/foo' + assert_equal '/foo/anonymous/blog/posts/1', last_response.body + + get "/metrics/generate_blog_route_in_view", {}, 'SCRIPT_NAME' => '/foo' + assert_equal '/foo/anonymous/blog/posts/1', last_response.body + + # test generating application's route from engine with default_url_options get "/someone/blog/generate_application_route", {}, 'SCRIPT_NAME' => '/foo' assert_equal "/foo/", last_response.body diff --git a/version.rb b/version.rb index dcbf57a4dfebb9ed5528957f6d3ec9666fa8b611..180c9712a967885546bf4d29390bbac306e7ce9e 100644 --- a/version.rb +++ b/version.rb @@ -3,7 +3,7 @@ module VERSION MAJOR = 4 MINOR = 0 TINY = 0 - PRE = "rc1" + PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end