aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoshua Peek <josh@joshpeek.com>2011-03-30 21:04:33 -0500
committerJoshua Peek <josh@joshpeek.com>2011-03-30 21:04:33 -0500
commit56a5da89dbcdd6d73f26f5c1be6221b684574b2b (patch)
tree577fbc16d37ed54bbda1a2a7477894caa6a7bfff
parent5df076ad0965dc684afff8a019fd9f92a53ada76 (diff)
parent58c3ec1b7b7ee073edf9c245de5d06426be60a25 (diff)
downloadrails-56a5da89dbcdd6d73f26f5c1be6221b684574b2b.tar.gz
rails-56a5da89dbcdd6d73f26f5c1be6221b684574b2b.tar.bz2
rails-56a5da89dbcdd6d73f26f5c1be6221b684574b2b.zip
Merge branch 'master' into sprockets
Conflicts: railties/lib/rails/application/configuration.rb
-rw-r--r--actionpack/CHANGELOG53
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/base.rb33
-rw-r--r--actionpack/lib/abstract_controller/callbacks.rb2
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb13
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb35
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb28
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb20
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb8
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/routing.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb15
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb5
-rw-r--r--actionpack/lib/action_view/template/resolver.rb4
-rw-r--r--actionpack/test/controller/force_ssl_test.rb83
-rw-r--r--actionpack/test/controller/http_basic_authentication_test.rb16
-rw-r--r--actionpack/test/controller/new_base/render_implicit_action_test.rb5
-rw-r--r--actionpack/test/dispatch/mapper_test.rb (renamed from actionpack/test/action_dispatch/routing/mapper_test.rb)32
-rw-r--r--actionpack/test/dispatch/show_exceptions_test.rb16
-rw-r--r--actionpack/test/template/date_helper_test.rb4
-rw-r--r--actionpack/test/template/form_helper_test.rb21
-rw-r--r--actionpack/test/template/javascript_helper_test.rb1
-rw-r--r--activemodel/lib/active_model/errors.rb4
-rw-r--r--activemodel/lib/active_model/naming.rb15
-rw-r--r--activemodel/lib/active_model/translation.rb2
-rw-r--r--activerecord/CHANGELOG12
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_part.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/primary_key.rb13
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb1
-rw-r--r--activerecord/lib/active_record/base.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb4
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb3
-rw-r--r--activerecord/lib/active_record/persistence.rb14
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/batches.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb35
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb26
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb2
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb6
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/has_one_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/associations/join_model_test.rb12
-rw-r--r--activerecord/test/cases/associations_test.rb4
-rw-r--r--activerecord/test/cases/attribute_methods_test.rb25
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/batches_test.rb10
-rw-r--r--activerecord/test/cases/calculations_test.rb41
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb33
-rw-r--r--activerecord/test/cases/connection_management_test.rb95
-rw-r--r--activerecord/test/cases/connection_pool_test.rb8
-rw-r--r--activerecord/test/cases/dirty_test.rb2
-rw-r--r--activerecord/test/cases/finder_test.rb45
-rw-r--r--activerecord/test/cases/log_subscriber_test.rb27
-rw-r--r--activerecord/test/cases/named_scope_test.rb17
-rw-r--r--activerecord/test/cases/persistence_test.rb86
-rw-r--r--activerecord/test/cases/primary_keys_test.rb9
-rw-r--r--activerecord/test/cases/relation_scoping_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb34
-rw-r--r--activerecord/test/cases/timestamp_test.rb3
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/guides/source/action_controller_overview.textile22
-rw-r--r--railties/guides/source/active_record_querying.textile56
-rw-r--r--railties/guides/source/active_record_validations_callbacks.textile2
-rw-r--r--railties/guides/source/configuring.textile3
-rw-r--r--railties/guides/source/routing.textile12
-rw-r--r--railties/guides/source/security.textile6
-rw-r--r--railties/guides/source/testing.textile4
-rw-r--r--railties/lib/rails/application.rb17
-rw-r--r--railties/lib/rails/application/configuration.rb3
-rw-r--r--railties/lib/rails/engine.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb39
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt18
-rw-r--r--railties/railties.gemspec1
-rw-r--r--railties/test/application/middleware_test.rb6
-rw-r--r--railties/test/railties/engine_test.rb39
81 files changed, 1117 insertions, 196 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5ab92c8cfc..3eba2281c4 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,58 @@
*Rails 3.1.0 (unreleased)*
+* Wildcard route will always matching the optional format segment by default. For example if you have this route:
+
+ map '*pages' => 'pages#show'
+
+ by requesting '/foo/bar.json', your `params[:pages]` will be equals to "foo/bar" with the request format of JSON. If you want the old 3.0.x behavior back, you could supply `:format => false` like this:
+
+ map '*pages' => 'pages#show', :format => false
+
+* Added Base.http_basic_authenticate_with to do simple http basic authentication with a single class method call [DHH]
+
+ class PostsController < ApplicationController
+ USER_NAME, PASSWORD = "dhh", "secret"
+
+ before_filter :authenticate, :except => [ :index ]
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+
+ private
+ def authenticate
+ authenticate_or_request_with_http_basic do |user_name, password|
+ user_name == USER_NAME && password == PASSWORD
+ end
+ end
+ end
+
+ ..can now be written as
+
+ class PostsController < ApplicationController
+ http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
+
+ def index
+ render :text => "Everyone can see me!"
+ end
+
+ def edit
+ render :text => "I'm only accessible if you know the password"
+ end
+ end
+
+* Allow you to add `force_ssl` into controller to force browser to transfer data via HTTPS protocol on that particular controller. You can also specify `:only` or `:except` to specific it to particular action. [DHH and Prem Sichanugrist]
+
+* Allow FormHelper#form_for to specify the :method as a direct option instead of through the :html hash [DHH]
+
+ form_for(@post, remote: true, method: :delete) instead of form_for(@post, remote: true, html: { method: :delete })
+
+* Make JavaScriptHelper#j() an alias for JavaScriptHelper#escape_javascript() -- note this then supersedes the Object#j() method that the JSON gem adds within templates using the JavaScriptHelper [DHH]
+
* Sensitive query string parameters (specified in config.filter_parameters) will now be filtered out from the request paths in the log file. [Prem Sichanugrist, fxn]
* URL parameters which return false for to_param now appear in the query string (previously they were removed) [Andrew White]
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index f6bc5e0d37..651f3b164a 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -26,7 +26,7 @@ Gem::Specification.new do |s|
s.add_dependency('i18n', '~> 0.5.0')
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.7')
- s.add_dependency('rack-mount', '~> 0.6.13')
+ s.add_dependency('rack-mount', '~> 0.7.1')
s.add_dependency('tzinfo', '~> 0.3.23')
s.add_dependency('erubis', '~> 2.6.6')
end
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index c384fd0978..07ff5ad9f3 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -128,20 +128,23 @@ module AbstractController
self.class.action_methods
end
- private
+ # Returns true if the name can be considered an action. This can
+ # be overridden in subclasses to modify the semantics of what
+ # can be considered an action.
+ #
+ # For instance, this is overriden by ActionController to add
+ # the implicit rendering feature.
+ #
+ # ==== Parameters
+ # * <tt>name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
+ def action_method?(name)
+ self.class.action_methods.include?(name)
+ end
- # Returns true if the name can be considered an action. This can
- # be overridden in subclasses to modify the semantics of what
- # can be considered an action.
- #
- # ==== Parameters
- # * <tt>name</tt> - The name of an action to be tested
- #
- # ==== Returns
- # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
- def action_method?(name)
- self.class.action_methods.include?(name)
- end
+ private
# Call the action. Override this in a subclass to modify the
# behavior around processing an action. This, and not #process,
@@ -160,8 +163,8 @@ module AbstractController
# If the action name was not found, but a method called "action_missing"
# was found, #method_for_action will return "_handle_action_missing".
# This method calls #action_missing with the current action name.
- def _handle_action_missing
- action_missing(@_action_name)
+ def _handle_action_missing(*args)
+ action_missing(@_action_name, *args)
end
# Takes an action name and returns the name of the method that will
diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb
index 1943ca4436..95992c2698 100644
--- a/actionpack/lib/abstract_controller/callbacks.rb
+++ b/actionpack/lib/abstract_controller/callbacks.rb
@@ -14,7 +14,7 @@ module AbstractController
# Override AbstractController::Base's process_action to run the
# process_action callbacks around the normal behavior.
def process_action(method_name, *args)
- run_callbacks(:process_action, action_name) do
+ run_callbacks(:process_action, method_name) do
super
end
end
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 5b81cd39f4..62cc18b253 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -14,6 +14,7 @@ module ActionController
autoload :ConditionalGet
autoload :Cookies
autoload :Flash
+ autoload :ForceSSL
autoload :Head
autoload :Helpers
autoload :HideActions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 81c0698fb8..e6523e56d2 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -198,6 +198,7 @@ module ActionController
Cookies,
Flash,
RequestForgeryProtection,
+ ForceSSL,
Streaming,
RecordIdentifier,
HttpAuthentication::Basic::ControllerMethods,
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 2c8a6e4d4d..5fc6956266 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -56,19 +56,18 @@ module ActionController #:nodoc:
#
# caches_page :public
#
- # caches_action :index, :if => proc do |c|
- # !c.request.format.json? # cache if is not a JSON request
+ # caches_action :index, :if => proc do
+ # !request.format.json? # cache if is not a JSON request
# end
#
# caches_action :show, :cache_path => { :project => 1 },
# :expires_in => 1.hour
#
- # caches_action :feed, :cache_path => proc do |c|
- # if c.params[:user_id]
- # c.send(:user_list_url,
- # c.params[:user_id], c.params[:id])
+ # caches_action :feed, :cache_path => proc do
+ # if params[:user_id]
+ # user_list_url(params[:user_id, params[:id])
# else
- # c.send(:list_url, c.params[:id])
+ # list_url(params[:id])
# end
# end
# end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
new file mode 100644
index 0000000000..eb8ed7dfbd
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -0,0 +1,35 @@
+module ActionController
+ # This module provides a method which will redirects browser to use HTTPS
+ # protocol. This will ensure that user's sensitive information will be
+ # transferred safely over the internet. You _should_ always force browser
+ # to use HTTPS when you're transferring sensitive information such as
+ # user authentication, account information, or credit card information.
+ #
+ # Note that if you really concern about your application safety, you might
+ # consider using +config.force_ssl+ in your configuration config file instead.
+ # That will ensure all the data transferred via HTTPS protocol and prevent
+ # user from getting session hijacked when accessing the site under unsecured
+ # HTTP protocol.
+ module ForceSSL
+ extend ActiveSupport::Concern
+ include AbstractController::Callbacks
+
+ module ClassMethods
+ # Force the request to this particular controller or specified actions to be
+ # under HTTPS protocol.
+ #
+ # Note that this method will not be effective on development environment.
+ #
+ # ==== Options
+ # * <tt>only</tt> - The callback should be run only for this action
+ # * <tt>except<tt> - The callback should be run for all actions except this action
+ def force_ssl(options = {})
+ before_filter(options) do
+ if !request.ssl? && !Rails.env.development?
+ redirect_to :protocol => 'https://', :status => :moved_permanently
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 39c804d707..b98429792d 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -8,9 +8,7 @@ module ActionController
# === Simple \Basic example
#
# class PostsController < ApplicationController
- # USER_NAME, PASSWORD = "dhh", "secret"
- #
- # before_filter :authenticate, :except => [ :index ]
+ # http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
#
# def index
# render :text => "Everyone can see me!"
@@ -19,15 +17,7 @@ module ActionController
# def edit
# render :text => "I'm only accessible if you know the password"
# end
- #
- # private
- # def authenticate
- # authenticate_or_request_with_http_basic do |user_name, password|
- # user_name == USER_NAME && password == PASSWORD
- # end
- # end
- # end
- #
+ # end
#
# === Advanced \Basic example
#
@@ -115,6 +105,18 @@ module ActionController
extend self
module ControllerMethods
+ extend ActiveSupport::Concern
+
+ module ClassMethods
+ def http_basic_authenticate_with(options = {})
+ before_filter(options.except(:name, :password, :realm)) do
+ authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
+ name == options[:name] && password == options[:password]
+ end
+ end
+ end
+ end
+
def authenticate_or_request_with_http_basic(realm = "Application", &login_procedure)
authenticate_with_http_basic(&login_procedure) || request_http_basic_authentication(realm)
end
@@ -378,7 +380,6 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
-
extend self
module ControllerMethods
@@ -458,6 +459,5 @@ module ActionController
controller.__send__ :render, :text => "HTTP Token: Access denied.\n", :status => :unauthorized
end
end
-
end
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index cfa7004048..678f4ca763 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,21 +1,21 @@
module ActionController
module ImplicitRender
- def send_action(*)
- ret = super
- default_render unless response_body
- ret
+ def send_action(method, *args)
+ if respond_to?(method, true)
+ ret = super
+ default_render unless response_body
+ ret
+ else
+ default_render
+ end
end
def default_render
render
end
- def method_for_action(action_name)
- super || begin
- if template_exists?(action_name.to_s, _prefixes)
- "default_render"
- end
- end
+ def action_method?(action_name)
+ super || template_exists?(action_name.to_s, _prefixes)
end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
index 50d8ca9484..2099fd069a 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
@@ -1,7 +1,7 @@
<h1>
<%=h @exception.class.to_s %>
<% if @request.parameters['controller'] %>
- in <%=h @request.parameters['controller'].humanize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
+ in <%=h @request.parameters['controller'].classify.pluralize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %>
<% end %>
</h1>
<pre><%=h @exception.message %></pre>
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 14c424f24b..35be0b3a27 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -104,10 +104,16 @@ module ActionDispatch
@options.reverse_merge!(:controller => /.+?/)
end
+ # Add a constraint for wildcard route to make it non-greedy and match the
+ # optional format part of the route by default
+ if path.match(/\*([^\/]+)$/) && @options[:format] != false
+ @options.reverse_merge!(:"#{$1}" => /.+?/)
+ end
+
if @options[:format] == false
@options.delete(:format)
path
- elsif path.include?(":format") || path.end_with?('/') || path.match(/^\/?\*/)
+ elsif path.include?(":format") || path.end_with?('/')
path
else
"#{path}(.:format)"
diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
index 11e8c63fa0..b760db42e2 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb
@@ -46,7 +46,7 @@ module ActionDispatch
expected_options.stringify_keys!
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
- assert_block(msg) { request.path_parameters == expected_options }
+ assert_equal(expected_options, request.path_parameters, msg)
end
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
@@ -84,11 +84,11 @@ module ActionDispatch
found_extras = options.reject {|k, v| ! extra_keys.include? k}
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
- assert_block(msg) { found_extras == extras }
+ assert_equal(extras, found_extras, msg)
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
expected_path)
- assert_block(msg) { expected_path == generated_path }
+ assert_equal(expected_path, generated_path, msg)
end
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 48abf119f1..9025d9e24c 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -185,7 +185,7 @@ module ActionView
#
# is equivalent to something like:
#
- # <%= form_for @post, :as => :post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ # <%= form_for @post, :as => :post, :url => post_path(@post), :method => :put, :html => { :class => "edit_post", :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
@@ -236,6 +236,16 @@ module ActionView
# Where <tt>@document = Document.find(params[:id])</tt> and
# <tt>@comment = Comment.new</tt>.
#
+ # === Setting the method
+ #
+ # You can force the form to use the full array of HTTP verbs by setting
+ #
+ # :method => (:get|:post|:put|:delete)
+ #
+ # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the
+ # form will be set to POST and a hidden input called _method will carry the intended verb for the server
+ # to interpret.
+ #
# === Unobtrusive JavaScript
#
# Specifying:
@@ -298,7 +308,7 @@ module ActionView
#
# In this case, if you use this:
#
- # <%= render :partial => f %>
+ # <%= render f %>
#
# The rendered template is <tt>people/_labelling_form</tt> and the local
# variable referencing the form builder is called
@@ -350,6 +360,7 @@ module ActionView
end
options[:html][:remote] = options.delete(:remote)
+ options[:html][:method] = options.delete(:method) if options.has_key?(:method)
options[:html][:authenticity_token] = options.delete(:authenticity_token)
builder = options[:parent_builder] = instantiate_builder(object_name, object, options, &proc)
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index cd3a3eac80..a19ba7a968 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -47,6 +47,9 @@ module ActionView
"'" => "\\'" }
# Escape carrier returns and single and double quotes for JavaScript segments.
+ # Also available through the alias j(). This is particularly helpful in JavaScript responses, like:
+ #
+ # $('some_element').replaceWith('<%=j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
javascript.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { JS_ESCAPE_MAP[$1] }
@@ -55,6 +58,8 @@ module ActionView
end
end
+ alias_method :j, :escape_javascript
+
# Returns a JavaScript tag with the +content+ inside. Example:
# javascript_tag "alert('All is good')"
#
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index 6c1063592f..41c6310ae2 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -157,8 +157,8 @@ module ActionView
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
}
- query.gsub!(/\.{html,/, ".{html,text.html,")
- query.gsub!(/\.{text,/, ".{text,text.plain,")
+ query.gsub!('.{html,', '.{html,text.html,')
+ query.gsub!('.{text,', '.{text,text.plain,')
File.expand_path(query, @path)
end
diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb
new file mode 100644
index 0000000000..3e723e20d9
--- /dev/null
+++ b/actionpack/test/controller/force_ssl_test.rb
@@ -0,0 +1,83 @@
+require 'abstract_unit'
+
+class ForceSSLController < ActionController::Base
+ def banana
+ render :text => "monkey"
+ end
+
+ def cheeseburger
+ render :text => "sikachu"
+ end
+end
+
+class ForceSSLControllerLevel < ForceSSLController
+ force_ssl
+end
+
+class ForceSSLOnlyAction < ForceSSLController
+ force_ssl :only => :cheeseburger
+end
+
+class ForceSSLExceptAction < ForceSSLController
+ force_ssl :except => :banana
+end
+
+class ForceSSLControllerLevelTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def test_banana_redirects_to_https
+ get :banana
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_controller_level/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLOnlyActionTest < ActionController::TestCase
+ tests ForceSSLOnlyAction
+
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_only_action/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLExceptActionTest < ActionController::TestCase
+ tests ForceSSLExceptAction
+
+ def test_banana_not_redirects_to_https
+ get :banana
+ assert_response 200
+ end
+
+ def test_cheeseburger_redirects_to_https
+ get :cheeseburger
+ assert_response 301
+ assert_equal "https://test.host/force_ssl_except_action/cheeseburger", redirect_to_url
+ end
+end
+
+class ForceSSLExcludeDevelopmentTest < ActionController::TestCase
+ tests ForceSSLControllerLevel
+
+ def setup
+ Rails.env.stubs(:development?).returns(false)
+ end
+
+ def test_development_environment_not_redirects_to_https
+ Rails.env.stubs(:development?).returns(true)
+ get :banana
+ assert_response 200
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb
index 01c650a494..bd3e13e6fa 100644
--- a/actionpack/test/controller/http_basic_authentication_test.rb
+++ b/actionpack/test/controller/http_basic_authentication_test.rb
@@ -6,6 +6,8 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
before_filter :authenticate_with_request, :only => :display
before_filter :authenticate_long_credentials, :only => :show
+ http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search
+
def index
render :text => "Hello Secret"
end
@@ -17,6 +19,10 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
def show
render :text => 'Only for loooooong credentials'
end
+
+ def search
+ render :text => 'All inline'
+ end
private
@@ -104,6 +110,16 @@ class HttpBasicAuthenticationTest < ActionController::TestCase
assert assigns(:logged_in)
assert_equal 'Definitely Maybe', @response.body
end
+
+ test "authenticate with class method" do
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'Goliath')
+ get :search
+ assert_response :success
+
+ @request.env['HTTP_AUTHORIZATION'] = encode_credentials('David', 'WRONG!')
+ get :search
+ assert_response :unauthorized
+ end
private
diff --git a/actionpack/test/controller/new_base/render_implicit_action_test.rb b/actionpack/test/controller/new_base/render_implicit_action_test.rb
index 9f69d20329..667a9021be 100644
--- a/actionpack/test/controller/new_base/render_implicit_action_test.rb
+++ b/actionpack/test/controller/new_base/render_implicit_action_test.rb
@@ -24,5 +24,10 @@ module RenderImplicitAction
assert_body "Hello hyphen-ated!"
assert_status 200
end
+
+ test "action_method? returns true for implicit actions" do
+ assert SimpleController.new.action_method?(:hello_world)
+ assert SimpleController.new.action_method?(:"hyphen-ated")
+ end
end
end
diff --git a/actionpack/test/action_dispatch/routing/mapper_test.rb b/actionpack/test/dispatch/mapper_test.rb
index e21b271907..b6c08ffc33 100644
--- a/actionpack/test/action_dispatch/routing/mapper_test.rb
+++ b/actionpack/test/dispatch/mapper_test.rb
@@ -25,6 +25,10 @@ module ActionDispatch
def conditions
routes.map { |x| x[1] }
end
+
+ def requirements
+ routes.map { |x| x[2] }
+ end
end
def test_initialize
@@ -50,8 +54,34 @@ module ActionDispatch
def test_map_wildcard
fakeset = FakeSet.new
mapper = Mapper.new fakeset
- mapper.match '/*path', :to => 'pages#show', :as => :page
+ mapper.match '/*path', :to => 'pages#show'
+ assert_equal '/*path(.:format)', fakeset.conditions.first[:path_info]
+ assert_equal(/.+?/, fakeset.requirements.first[:path])
+ end
+
+ def test_map_wildcard_with_other_element
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path/foo/:bar', :to => 'pages#show'
+ assert_equal '/*path/foo/:bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:path]
+ end
+
+ def test_map_wildcard_with_multiple_wildcard
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*foo/*bar', :to => 'pages#show'
+ assert_equal '/*foo/*bar(.:format)', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:foo]
+ assert_equal(/.+?/, fakeset.requirements.first[:bar])
+ end
+
+ def test_map_wildcard_with_format_false
+ fakeset = FakeSet.new
+ mapper = Mapper.new fakeset
+ mapper.match '/*path', :to => 'pages#show', :format => false
assert_equal '/*path', fakeset.conditions.first[:path_info]
+ assert_nil fakeset.requirements.first[:path]
end
end
end
diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb
index 2a478c214f..e453dd11ce 100644
--- a/actionpack/test/dispatch/show_exceptions_test.rb
+++ b/actionpack/test/dispatch/show_exceptions_test.rb
@@ -7,6 +7,8 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
case req.path
when "/not_found"
raise ActionController::UnknownAction
+ when "/runtime_error"
+ raise RuntimeError
when "/method_not_allowed"
raise ActionController::MethodNotAllowed
when "/not_implemented"
@@ -121,4 +123,18 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest
assert_response 404
assert_match(/AbstractController::ActionNotFound/, body)
end
+
+ test "show the controller name in the diagnostics template when controller name is present" do
+ @app = ProductionApp
+ get("/runtime_error", {}, {
+ 'action_dispatch.show_exceptions' => true,
+ 'action_dispatch.request.parameters' => {
+ 'action' => 'show',
+ 'id' => 'unknown',
+ 'controller' => 'featured_tiles'
+ }
+ })
+ assert_response 500
+ assert_match(/RuntimeError\n in FeaturedTilesController/, body)
+ end
end
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index aca2fef170..fd1f824a39 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -2720,11 +2720,11 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_pubdate_option
- assert_match /<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true)
+ assert_match(/<time.*pubdate="pubdate">.*<\/time>/, time_tag(Time.now, :pubdate => true))
end
def test_time_tag_with_given_text
- assert_match /<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now')
+ assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now'))
end
def test_time_tag_with_different_format
diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb
index 359b078466..ff183d097d 100644
--- a/actionpack/test/template/form_helper_test.rb
+++ b/actionpack/test/template/form_helper_test.rb
@@ -715,14 +715,31 @@ class FormHelperTest < ActionView::TestCase
assert_dom_equal expected, output_buffer
end
+ def test_form_for_with_method_as_part_of_html_options
+ form_for(@post, :url => '/', :html => { :id => 'create-post', :method => :delete }) do |f|
+ concat f.text_field(:title)
+ concat f.text_area(:body)
+ concat f.check_box(:secret)
+ end
+
+ expected = whole_form("/", "create-post", "edit_post", "delete") do
+ "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
+ "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
+ "<input name='post[secret]' type='hidden' value='0' />" +
+ "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"
+ end
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_form_for_with_method
- form_for(@post, :url => '/', :html => { :id => 'create-post', :method => :put }) do |f|
+ form_for(@post, :url => '/', :method => :delete, :html => { :id => 'create-post' }) do |f|
concat f.text_field(:title)
concat f.text_area(:body)
concat f.check_box(:secret)
end
- expected = whole_form("/", "create-post", "edit_post", "put") do
+ expected = whole_form("/", "create-post", "edit_post", "delete") do
"<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" +
"<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" +
"<input name='post[secret]' type='hidden' value='0' />" +
diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb
index 2e7484afaf..8aa2730da1 100644
--- a/actionpack/test/template/javascript_helper_test.rb
+++ b/actionpack/test/template/javascript_helper_test.rb
@@ -27,6 +27,7 @@ class JavaScriptHelperTest < ActionView::TestCase
assert_equal %(This \\"thing\\" is really\\n netos\\'), escape_javascript(%(This "thing" is really\n netos'))
assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) )
assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags))
+ assert_equal %(dont <\\/close> tags), j(%(dont </close> tags))
end
def test_button_to_function
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index c2f0228785..5e3cf510b0 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -295,8 +295,8 @@ module ActiveModel
type = options.delete(:message) if options[:message].is_a?(Symbol)
defaults = @base.class.lookup_ancestors.map do |klass|
- [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.attributes.#{attribute}.#{type}",
- :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.underscore}.#{type}" ]
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
end
defaults << options.delete(:message)
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index eb9b847509..315ccd40e9 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/module/introspection'
module ActiveModel
class Name < String
- attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key
+ attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
alias_method :cache_key, :collection
def initialize(klass, namespace = nil)
@@ -20,6 +20,7 @@ module ActiveModel
@partial_path = "#{@collection}/#{@element}".freeze
@param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
+ @i18n_key = self.underscore.to_sym
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -33,7 +34,7 @@ module ActiveModel
@klass.respond_to?(:i18n_scope)
defaults = @klass.lookup_ancestors.map do |klass|
- klass.model_name.underscore.to_sym
+ klass.model_name.i18n_key
end
defaults << options[:default] if options[:default]
@@ -44,9 +45,10 @@ module ActiveModel
end
private
- def _singularize(str)
- ActiveSupport::Inflector.underscore(str).tr('/', '_')
- end
+
+ def _singularize(string, replacement='_')
+ ActiveSupport::Inflector.underscore(string).tr('/', replacement)
+ end
end
# == Active Model Naming
@@ -62,6 +64,9 @@ module ActiveModel
# BookCover.model_name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
+ # BookCover.model_name.i18n_key # => "book_cover"
+ # BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover"
+ #
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the Active Model Lint test. So either extending the provided
# method below, or rolling your own is required.
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index dbb76244e4..920a133159 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -44,7 +44,7 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
defaults = lookup_ancestors.map do |klass|
- :"#{self.i18n_scope}.attributes.#{klass.model_name.underscore}.#{attribute}"
+ :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
end
defaults << :"attributes.#{attribute}"
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index 6be46349fb..e536d2b408 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,17 @@
*Rails 3.1.0 (unreleased)*
+* ConnectionManagement middleware is changed to clean up the connection pool
+ after the rack body has been flushed.
+
+* Added an update_column method on ActiveRecord. This new method updates a given attribute on an object, skipping validations and callbacks.
+ It is recommended to use #update_attribute unless you are sure you do not want to execute any callback, including the modification of
+ the updated_at column. It should not be called on new records.
+ Example:
+
+ User.first.update_column(:name, "sebastian") # => true
+
+ [Sebastian Martinez]
+
* Associations with a :through option can now use *any* association as the
through or source association, including other associations which have a
:through option and has_and_belongs_to_many associations
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_part.rb b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
index 3279e56e7d..2b1d888a9a 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_part.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_part.rb
@@ -22,7 +22,7 @@ module ActiveRecord
end
def aliased_table
- Arel::Nodes::TableAlias.new aliased_table_name, table
+ Arel::Nodes::TableAlias.new table, aliased_table_name
end
def ==(other)
diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb
index fcdd31ddea..5f06452247 100644
--- a/activerecord/lib/active_record/attribute_methods/primary_key.rb
+++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb
@@ -17,6 +17,11 @@ module ActiveRecord
@primary_key ||= reset_primary_key
end
+ # Returns a quoted version of the primary key name, used to construct SQL statements.
+ def quoted_primary_key
+ @quoted_primary_key ||= connection.quote_column_name(primary_key)
+ end
+
def reset_primary_key #:nodoc:
key = self == base_class ? get_primary_key(base_class.name) :
base_class.primary_key
@@ -43,7 +48,12 @@ module ActiveRecord
end
attr_accessor :original_primary_key
- attr_writer :primary_key
+
+ # Attribute writer for the primary key column
+ def primary_key=(value)
+ @quoted_primary_key = nil
+ @primary_key = value
+ end
# Sets the name of the primary key column to use to the given value,
# or (if the value is nil or false) to the value returned by the given
@@ -53,6 +63,7 @@ module ActiveRecord
# set_primary_key "sysid"
# end
def set_primary_key(value = nil, &block)
+ @quoted_primary_key = nil
@primary_key ||= ''
self.original_primary_key = @primary_key
value &&= value.to_s
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 3c4dab304e..7661676f8c 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -32,6 +32,7 @@ module ActiveRecord
@attributes[attr_name] = value
end
end
+ alias_method :raw_write_attribute, :write_attribute
private
# Handle *= for method_missing.
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index b778b0c0f0..fe81c7dc2f 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -437,9 +437,10 @@ module ActiveRecord #:nodoc:
self._attr_readonly = []
class << self # Class methods
- delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
+ delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped
+ delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :find_each, :find_in_batches, :to => :scoped
- delegate :select, :group, :order, :except, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
+ delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 4297c26413..b4db1eed18 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -151,6 +151,12 @@ module ActiveRecord
@reserved_connections[current_connection_id] ||= checkout
end
+ # Check to see if there is an active connection in this connection
+ # pool.
+ def active_connection?
+ @reserved_connections.key? current_connection_id
+ end
+
# Signal that the thread is finished with the current connection.
# #release_connection releases the connection-thread association
# and returns the connection to the pool.
@@ -346,6 +352,12 @@ module ActiveRecord
@connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
end
+ # Returns true if there are any active connections among the connection
+ # pools that the ConnectionHandler is managing.
+ def active_connections?
+ connection_pools.values.any? { |pool| pool.active_connection? }
+ end
+
# Returns any connections in use by the current thread back to the pool,
# and also returns connections to the pool cached by threads that are no
# longer alive.
@@ -405,18 +417,40 @@ module ActiveRecord
end
class ConnectionManagement
+ class Proxy # :nodoc:
+ attr_reader :body, :testing
+
+ def initialize(body, testing = false)
+ @body = body
+ @testing = testing
+ end
+
+ def each(&block)
+ body.each(&block)
+ end
+
+ def close
+ body.close if body.respond_to?(:close)
+
+ # Don't return connection (and perform implicit rollback) if
+ # this request is a part of integration test
+ ActiveRecord::Base.clear_active_connections! unless testing
+ end
+ end
+
def initialize(app)
@app = app
end
def call(env)
- @app.call(env)
- ensure
- # Don't return connection (and perform implicit rollback) if
- # this request is a part of integration test
- unless env.key?("rack.test")
- ActiveRecord::Base.clear_active_connections!
- end
+ testing = env.key?('rack.test')
+
+ status, headers, body = @app.call(env)
+
+ [status, headers, Proxy.new(body, testing)]
+ rescue
+ ActiveRecord::Base.clear_active_connections! unless testing
+ raise
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
index d88720c8bf..bcd3abc08d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -116,7 +116,11 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
- delegate :clear_active_connections!, :clear_reloadable_connections!,
+ def clear_active_connections!
+ connection_handler.clear_active_connections!
+ end
+
+ delegate :clear_reloadable_connections!,
:clear_all_connections!,:verify_active_connections!, :to => :connection_handler
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index ae61d6ce94..32229a8410 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -222,7 +222,7 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables(name = nil) #:nodoc:
+ def tables(name = 'SCHEMA') #:nodoc:
sql = <<-SQL
SELECT name
FROM sqlite_master
@@ -350,7 +350,7 @@ module ActiveRecord
end
def table_structure(table_name)
- structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})").to_hash
+ structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", 'SCHEMA').to_hash
raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty?
structure
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index afadbf03ef..d31e321440 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -23,6 +23,9 @@ module ActiveRecord
return unless logger.debug?
payload = event.payload
+
+ return if 'SCHEMA' == payload[:name]
+
name = '%s (%.1fms)' % [payload[:name], event.duration]
sql = payload[:sql].squeeze(' ')
binds = nil
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 17a64b6e86..a916c88348 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -119,6 +119,20 @@ module ActiveRecord
save(:validate => false)
end
+ # Updates a single attribute of an object, without calling save.
+ #
+ # * Validation is skipped.
+ # * Callbacks are skipped.
+ # * updated_at/updated_on column is not updated if that column is available.
+ #
+ def update_column(name, value)
+ name = name.to_s
+ raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
+ raise ActiveRecordError, "can not update on a new record object" unless persisted?
+ raw_write_attribute(name, value)
+ self.class.update_all({ name => value }, self.class.primary_key => id) == 1
+ end
+
# Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned.
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 8e545f9cad..896daf516e 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -12,7 +12,7 @@ module ActiveRecord
# These are explicitly delegated to improve performance (avoids method_missing)
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
- delegate :table_name, :primary_key, :to => :klass
+ delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
attr_reader :table, :klass, :loaded
attr_accessor :extensions
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index bf5a60f458..d52b84179f 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -83,7 +83,7 @@ module ActiveRecord
private
def batch_order
- "#{table_name}.#{primary_key} ASC"
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
end
end
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index c1842b1a96..869eebfa34 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -196,24 +196,22 @@ module ActiveRecord
end
def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
- column = aggregate_column(column_name)
-
# Postgresql doesn't like ORDER BY when there are no GROUP BY
relation = except(:order)
- select_value = operation_over_aggregate_column(column, operation, distinct)
- relation.select_values = [select_value]
+ if operation == "count" && (relation.limit_value || relation.offset_value)
+ # Shortcut when limit is zero.
+ return 0 if relation.limit_value == 0
- query_builder = relation.arel
+ query_builder = build_count_subquery(relation, column_name, distinct)
+ else
+ column = aggregate_column(column_name)
- if operation == "count"
- limit = relation.limit_value
- offset = relation.offset_value
+ select_value = operation_over_aggregate_column(column, operation, distinct)
- unless limit && offset
- query_builder.limit = nil
- query_builder.offset = nil
- end
+ relation.select_values = [select_value]
+
+ query_builder = relation.arel
end
type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation)
@@ -312,5 +310,18 @@ module ActiveRecord
select if select !~ /(,|\*)/
end
end
+
+ def build_count_subquery(relation, column_name, distinct)
+ column_alias = Arel.sql('count_column')
+ subquery_alias = Arel.sql('subquery_for_count')
+
+ aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
+ relation.select_values = [aliased_column]
+ subquery = relation.arel.as(subquery_alias)
+
+ sm = Arel::SelectManager.new relation.engine
+ select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
+ sm.project(select_value).from(subquery)
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 426000fde1..8fa315bdf3 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -123,6 +123,12 @@ module ActiveRecord
end
end
+ # Same as +first+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>first!</tt> accepts no arguments.
+ def first!
+ first or raise RecordNotFound
+ end
+
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:last)</tt>.
def last(*args)
@@ -137,6 +143,12 @@ module ActiveRecord
end
end
+ # Same as +last+ but raises <tt>ActiveRecord::RecordNotFound</tt> if no record
+ # is found. Note that <tt>last!</tt> accepts no arguments.
+ def last!
+ last or raise RecordNotFound
+ end
+
# A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
# same arguments to this method as you can to <tt>find(:all)</tt>.
def all(*args)
@@ -171,7 +183,9 @@ module ActiveRecord
def exists?(id = nil)
id = id.id if ActiveRecord::Base === id
- relation = select("1").limit(1)
+ join_dependency = construct_join_dependency_for_association_find
+ relation = construct_relation_for_association_find(join_dependency)
+ relation = relation.except(:select).select("1").limit(1)
case id
when Array, Hash
@@ -180,14 +194,13 @@ module ActiveRecord
relation = relation.where(table[primary_key].eq(id)) if id
end
- relation.first ? true : false
+ connection.select_value(relation.to_sql) ? true : false
end
protected
def find_with_associations
- including = (@eager_load_values + @includes_values).uniq
- join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ join_dependency = construct_join_dependency_for_association_find
relation = construct_relation_for_association_find(join_dependency)
rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values)
join_dependency.instantiate(rows)
@@ -195,6 +208,11 @@ module ActiveRecord
[]
end
+ def construct_join_dependency_for_association_find
+ including = (@eager_load_values + @includes_values).uniq
+ ActiveRecord::Associations::JoinDependency.new(@klass, including, [])
+ end
+
def construct_relation_for_association_calculations
including = (@eager_load_values + @includes_values).uniq
join_dependency = ActiveRecord::Associations::JoinDependency.new(@klass, including, arel.froms.first)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 9470e7c6c5..02b7056989 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -62,6 +62,10 @@ module ActiveRecord
relation
end
+ def reorder(*args)
+ except(:order).order(args)
+ end
+
def joins(*args)
return self if args.compact.blank?
diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
index 73d02c9676..f4d14853d3 100644
--- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -604,7 +604,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
project = SpecialProject.create("name" => "Special Project")
assert developer.save
developer.projects << project
- developer.update_attribute("name", "Bruza")
+ developer.update_column("name", "Bruza")
assert_equal 1, Developer.connection.select_value(<<-end_sql).to_i
SELECT count(*) FROM developers_projects
WHERE project_id = #{project.id}
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index ad774eb9ce..16d4877fe8 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -639,7 +639,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_delete_all
post = posts(:welcome)
- post.update_attribute(:taggings_with_delete_all_count, post.taggings_count)
+ post.update_column(:taggings_with_delete_all_count, post.taggings_count)
assert_difference "post.reload.taggings_with_delete_all_count", -1 do
post.taggings_with_delete_all.delete(post.taggings_with_delete_all.first)
@@ -648,7 +648,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
def test_deleting_updates_counter_cache_with_dependent_destroy
post = posts(:welcome)
- post.update_attribute(:taggings_with_destroy_count, post.taggings_count)
+ post.update_column(:taggings_with_destroy_count, post.taggings_count)
assert_difference "post.reload.taggings_with_destroy_count", -1 do
post.taggings_with_destroy.delete(post.taggings_with_destroy.first)
@@ -787,7 +787,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
firm = Firm.find(:first)
# break the vanilla firm_id foreign key
assert_equal 2, firm.clients.count
- firm.clients.first.update_attribute(:firm_id, nil)
+ firm.clients.first.update_column(:firm_id, nil)
assert_equal 1, firm.clients(true).count
assert_equal 1, firm.clients_using_primary_key_with_delete_all.count
old_record = firm.clients_using_primary_key_with_delete_all.first
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 9adaebe924..1efe3420a0 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -286,7 +286,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_update_counter_caches_on_delete_with_dependent_destroy
post = posts(:welcome)
tag = post.tags.create!(:name => 'doomed')
- post.update_attribute(:tags_with_destroy_count, post.tags.count)
+ post.update_column(:tags_with_destroy_count, post.tags.count)
assert_difference ['post.reload.taggings_count', 'post.reload.tags_with_destroy_count'], -1 do
posts(:welcome).tags_with_destroy.delete(tag)
@@ -296,7 +296,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_update_counter_caches_on_delete_with_dependent_nullify
post = posts(:welcome)
tag = post.tags.create!(:name => 'doomed')
- post.update_attribute(:tags_with_nullify_count, post.tags.count)
+ post.update_column(:tags_with_nullify_count, post.tags.count)
assert_no_difference 'post.reload.taggings_count' do
assert_difference 'post.reload.tags_with_nullify_count', -1 do
diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb
index 9ba5549277..968025ade8 100644
--- a/activerecord/test/cases/associations/has_one_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -90,12 +90,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase
def test_has_one_through_with_conditions_eager_loading
# conditions on the through table
assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :favourite_club).favourite_club
- memberships(:membership_of_favourite_club).update_attribute(:favourite, false)
+ memberships(:membership_of_favourite_club).update_column(:favourite, false)
assert_equal nil, Member.find(@member.id, :include => :favourite_club).reload.favourite_club
# conditions on the source table
assert_equal clubs(:moustache_club), Member.find(@member.id, :include => :hairy_club).hairy_club
- clubs(:moustache_club).update_attribute(:name, "Association of Clean-Shaven Persons")
+ clubs(:moustache_club).update_column(:name, "Association of Clean-Shaven Persons")
assert_equal nil, Member.find(@member.id, :include => :hairy_club).reload.hairy_club
end
diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb
index 1f95b31497..5a7b139030 100644
--- a/activerecord/test/cases/associations/join_model_test.rb
+++ b/activerecord/test/cases/associations/join_model_test.rb
@@ -161,7 +161,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_delete_all
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDeleteAll'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyDeleteAll'
post = find_post_with_dependency(1, :has_many, :taggings, :delete_all)
old_count = Tagging.count
@@ -172,7 +172,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_destroy
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyDestroy'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyDestroy'
post = find_post_with_dependency(1, :has_many, :taggings, :destroy)
old_count = Tagging.count
@@ -183,7 +183,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_many_with_nullify
assert_equal 1, posts(:welcome).taggings.count
- posts(:welcome).taggings.first.update_attribute :taggable_type, 'PostWithHasManyNullify'
+ posts(:welcome).taggings.first.update_column :taggable_type, 'PostWithHasManyNullify'
post = find_post_with_dependency(1, :has_many, :taggings, :nullify)
old_count = Tagging.count
@@ -194,7 +194,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_one_with_destroy
assert posts(:welcome).tagging
- posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneDestroy'
+ posts(:welcome).tagging.update_column :taggable_type, 'PostWithHasOneDestroy'
post = find_post_with_dependency(1, :has_one, :tagging, :destroy)
old_count = Tagging.count
@@ -205,7 +205,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
def test_delete_polymorphic_has_one_with_nullify
assert posts(:welcome).tagging
- posts(:welcome).tagging.update_attribute :taggable_type, 'PostWithHasOneNullify'
+ posts(:welcome).tagging.update_column :taggable_type, 'PostWithHasOneNullify'
post = find_post_with_dependency(1, :has_one, :tagging, :nullify)
old_count = Tagging.count
@@ -707,7 +707,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase
# create dynamic Post models to allow different dependency options
def find_post_with_dependency(post_id, association, association_name, dependency)
class_name = "PostWith#{association.to_s.classify}#{dependency.to_s.classify}"
- Post.find(post_id).update_attribute :type, class_name
+ Post.find(post_id).update_column :type, class_name
klass = Object.const_set(class_name, Class.new(ActiveRecord::Base))
klass.set_table_name 'posts'
klass.send(association, association_name, :as => :taggable, :dependent => dependency)
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index 47b8e48582..04f628a398 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -66,7 +66,7 @@ class AssociationsTest < ActiveRecord::TestCase
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
part.mark_for_destruction
- ShipPart.find(part.id).update_attribute(:name, 'Deck')
+ ShipPart.find(part.id).update_column(:name, 'Deck')
ship.parts.send(:load_target)
assert_equal 'Deck', ship.parts[0].name
end
@@ -170,7 +170,7 @@ class AssociationProxyTest < ActiveRecord::TestCase
david = developers(:david)
assert !david.projects.loaded?
- david.update_attribute(:created_at, Time.now)
+ david.update_column(:created_at, Time.now)
assert !david.projects.loaded?
end
diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb
index d8638ee776..84f75cc803 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -120,11 +120,11 @@ class AttributeMethodsTest < ActiveRecord::TestCase
def test_read_attributes_before_type_cast_on_datetime
in_time_zone "Pacific Time (US & Canada)" do
record = @target.new
-
+
record.written_on = "345643456"
assert_equal "345643456", record.written_on_before_type_cast
assert_equal nil, record.written_on
-
+
record.written_on = "2009-10-11 12:13:14"
assert_equal "2009-10-11 12:13:14", record.written_on_before_type_cast
assert_equal Time.zone.parse("2009-10-11 12:13:14"), record.written_on
@@ -132,6 +132,27 @@ class AttributeMethodsTest < ActiveRecord::TestCase
end
end
+ def test_read_attributes_after_type_cast_on_datetime
+ tz = "Pacific Time (US & Canada)"
+
+ in_time_zone tz do
+ record = @target.new
+
+ date_string = "2011-03-24"
+ time = Time.zone.parse date_string
+
+ record.written_on = date_string
+ assert_equal date_string, record.written_on_before_type_cast
+ assert_equal time, record.written_on
+ assert_equal ActiveSupport::TimeZone[tz], record.written_on.time_zone
+
+ record.save
+ record.reload
+
+ assert_equal time, record.written_on
+ end
+ end
+
def test_hash_content
topic = Topic.new
topic.content = { "one" => 1, "two" => 2 }
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index fba7af741d..aeb0b28bab 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -484,7 +484,7 @@ class BasicsTest < ActiveRecord::TestCase
weird.reload
assert_equal 'value', weird.send('a$b')
- weird.update_attribute('a$b', 'value2')
+ weird.update_column('a$b', 'value2')
weird.reload
assert_equal 'value2', weird.send('a$b')
end
diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb
index dc0e0da4c5..6620464d6a 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -83,4 +83,14 @@ class EachTest < ActiveRecord::TestCase
Post.find_in_batches(:batch_size => post_count + 1) {|batch| assert_kind_of Array, batch }
end
end
+
+ def test_find_in_batches_should_quote_batch_order
+ c = Post.connection
+ assert_sql(/ORDER BY #{c.quote_table_name('posts')}.#{c.quote_column_name('id')}/) do
+ Post.find_in_batches(:batch_size => 1) do |batch|
+ assert_kind_of Array, batch
+ assert_kind_of Post, batch.first
+ end
+ end
+ end
end
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index caf07a7357..654c4c9010 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -65,7 +65,7 @@ class CalculationsTest < ActiveRecord::TestCase
c = Account.sum(:credit_limit, :group => :firm_id)
[1,6,2].each { |firm_id| assert c.keys.include?(firm_id) }
end
-
+
def test_should_group_by_multiple_fields
c = Account.count(:all, :group => ['firm_id', :credit_limit])
[ [nil, 50], [1, 50], [6, 50], [6, 55], [9, 53], [2, 60] ].each { |firm_and_limit| assert c.keys.include?(firm_and_limit) }
@@ -109,27 +109,42 @@ class CalculationsTest < ActiveRecord::TestCase
assert_equal [2, 6], c.keys.compact
end
- def test_limit_with_offset_is_kept
+ def test_limit_should_apply_before_count
+ accounts = Account.limit(3).where('firm_id IS NOT NULL')
+
+ assert_equal 3, accounts.count(:firm_id)
+ assert_equal 3, accounts.select(:firm_id).count
+ end
+
+ def test_count_should_shortcut_with_limit_zero
+ accounts = Account.limit(0)
+
+ assert_no_queries { assert_equal 0, accounts.count }
+ end
+
+ def test_limit_is_kept
return if current_adapter?(:OracleAdapter)
- queries = assert_sql { Account.limit(1).offset(1).count }
+ queries = assert_sql { Account.limit(1).count }
assert_equal 1, queries.length
assert_match(/LIMIT/, queries.first)
- assert_match(/OFFSET/, queries.first)
end
- def test_offset_without_limit_removes_offset
+ def test_offset_is_kept
+ return if current_adapter?(:OracleAdapter)
+
queries = assert_sql { Account.offset(1).count }
assert_equal 1, queries.length
- assert_no_match(/LIMIT/, queries.first)
- assert_no_match(/OFFSET/, queries.first)
+ assert_match(/OFFSET/, queries.first)
end
- def test_limit_without_offset_removes_limit
- queries = assert_sql { Account.limit(1).count }
+ def test_limit_with_offset_is_kept
+ return if current_adapter?(:OracleAdapter)
+
+ queries = assert_sql { Account.limit(1).offset(1).count }
assert_equal 1, queries.length
- assert_no_match(/LIMIT/, queries.first)
- assert_no_match(/OFFSET/, queries.first)
+ assert_match(/LIMIT/, queries.first)
+ assert_match(/OFFSET/, queries.first)
end
def test_no_limit_no_offset
@@ -311,8 +326,8 @@ class CalculationsTest < ActiveRecord::TestCase
def test_should_count_scoped_select_with_options
Account.update_all("credit_limit = NULL")
- Account.last.update_attribute('credit_limit', 49)
- Account.first.update_attribute('credit_limit', 51)
+ Account.last.update_column('credit_limit', 49)
+ Account.first.update_column('credit_limit', 51)
assert_equal 1, Account.scoped(:select => "credit_limit").count(:conditions => ['credit_limit >= 50'])
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
new file mode 100644
index 0000000000..abf317768f
--- /dev/null
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -0,0 +1,33 @@
+require "cases/helper"
+
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionHandlerTest < ActiveRecord::TestCase
+ def setup
+ @handler = ConnectionHandler.new
+ @handler.establish_connection 'america', Base.connection_pool.spec
+ @klass = Struct.new(:name).new('america')
+ end
+
+ def test_retrieve_connection
+ assert @handler.retrieve_connection(@klass)
+ end
+
+ def test_active_connections?
+ assert !@handler.active_connections?
+ assert @handler.retrieve_connection(@klass)
+ assert @handler.active_connections?
+ @handler.clear_active_connections!
+ assert !@handler.active_connections?
+ end
+
+ def test_retrieve_connection_pool_with_ar_base
+ assert_nil @handler.retrieve_connection_pool(ActiveRecord::Base)
+ end
+
+ def test_retrieve_connection_pool
+ assert_not_nil @handler.retrieve_connection_pool(@klass)
+ end
+ end
+ end
+end
diff --git a/activerecord/test/cases/connection_management_test.rb b/activerecord/test/cases/connection_management_test.rb
index c535119972..85871aebdf 100644
--- a/activerecord/test/cases/connection_management_test.rb
+++ b/activerecord/test/cases/connection_management_test.rb
@@ -1,25 +1,82 @@
require "cases/helper"
-class ConnectionManagementTest < ActiveRecord::TestCase
- def setup
- @env = {}
- @app = stub('App')
- @management = ActiveRecord::ConnectionAdapters::ConnectionManagement.new(@app)
-
- @connections_cleared = false
- ActiveRecord::Base.stubs(:clear_active_connections!).with { @connections_cleared = true }
- end
+module ActiveRecord
+ module ConnectionAdapters
+ class ConnectionManagementTest < ActiveRecord::TestCase
+ class App
+ attr_reader :calls
+ def initialize
+ @calls = []
+ end
- test "clears active connections after each call" do
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert @connections_cleared
- end
+ def call(env)
+ @calls << env
+ [200, {}, ['hi mom']]
+ end
+ end
+
+ def setup
+ @env = {}
+ @app = App.new
+ @management = ConnectionManagement.new(@app)
+
+ # make sure we have an active connection
+ assert ActiveRecord::Base.connection
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_app_delegation
+ manager = ConnectionManagement.new(@app)
+
+ manager.call @env
+ assert_equal [@env], @app.calls
+ end
+
+ def test_connections_are_active_after_call
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_body_responds_to_each
+ _, _, body = @management.call(@env)
+ bits = []
+ body.each { |bit| bits << bit }
+ assert_equal ['hi mom'], bits
+ end
+
+ def test_connections_are_cleared_after_body_close
+ _, _, body = @management.call(@env)
+ body.close
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_active_connections_are_not_cleared_on_body_close_during_test
+ @env['rack.test'] = true
+ _, _, body = @management.call(@env)
+ body.close
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_connections_closed_if_exception
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert !ActiveRecord::Base.connection_handler.active_connections?
+ end
+
+ def test_connections_not_closed_if_exception_and_test
+ @env['rack.test'] = true
+ app = Class.new(App) { def call(env); raise; end }.new
+ explosive = ConnectionManagement.new(app)
+ assert_raises(RuntimeError) { explosive.call(@env) }
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
- test "doesn't clear active connections when running in a test case" do
- @env['rack.test'] = true
- @app.expects(:call).with(@env)
- @management.call(@env)
- assert !@connections_cleared
+ test "doesn't clear active connections when running in a test case" do
+ @env['rack.test'] = true
+ @management.call(@env)
+ assert ActiveRecord::Base.connection_handler.active_connections?
+ end
+ end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 7ac14fa8d6..f92f4e62c5 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -18,6 +18,14 @@ module ActiveRecord
end
end
+ def test_active_connection?
+ assert !@pool.active_connection?
+ assert @pool.connection
+ assert @pool.active_connection?
+ @pool.release_connection
+ assert !@pool.active_connection?
+ end
+
def test_pool_caches_columns
columns = @pool.columns['posts']
assert_equal columns, @pool.columns['posts']
diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a6738fb654..b1ce846218 100644
--- a/activerecord/test/cases/dirty_test.rb
+++ b/activerecord/test/cases/dirty_test.rb
@@ -413,7 +413,7 @@ class DirtyTest < ActiveRecord::TestCase
with_partial_updates(Topic) do
Topic.create!(:author_name => 'Bill', :content => {:a => "a"})
topic = Topic.select('id, author_name').first
- topic.update_attribute :author_name, 'John'
+ topic.update_column :author_name, 'John'
topic = Topic.first
assert_not_nil topic.content
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 2e620d8b03..3c242667eb 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -74,6 +74,11 @@ class FinderTest < ActiveRecord::TestCase
end
end
+ def test_exists_does_not_instantiate_records
+ Developer.expects(:instantiate).never
+ Developer.exists?
+ end
+
def test_find_by_array_of_one_id
assert_kind_of(Array, Topic.find([ 1 ]))
assert_equal(1, Topic.find([ 1 ]).length)
@@ -191,6 +196,46 @@ class FinderTest < ActiveRecord::TestCase
assert_nil Topic.where("title = 'The Second Topic of the day!'").first
end
+ def test_first_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").first!
+ end
+ end
+
+ def test_first_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").first!
+ end
+ end
+
+ def test_model_class_responds_to_first_bang
+ assert Topic.first!
+ Topic.delete_all
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.first!
+ end
+ end
+
+ def test_last_bang_present
+ assert_nothing_raised do
+ assert_equal topics(:second), Topic.where("title = 'The Second Topic of the day'").last!
+ end
+ end
+
+ def test_last_bang_missing
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.where("title = 'This title does not exist'").last!
+ end
+ end
+
+ def test_model_class_responds_to_last_bang
+ assert_equal topics(:fourth), Topic.last!
+ assert_raises ActiveRecord::RecordNotFound do
+ Topic.delete_all
+ Topic.last!
+ end
+ end
+
def test_unexisting_record_exception_handling
assert_raise(ActiveRecord::RecordNotFound) {
Topic.find(1).parent
diff --git a/activerecord/test/cases/log_subscriber_test.rb b/activerecord/test/cases/log_subscriber_test.rb
index cbaaca764b..8ebde933b4 100644
--- a/activerecord/test/cases/log_subscriber_test.rb
+++ b/activerecord/test/cases/log_subscriber_test.rb
@@ -22,6 +22,33 @@ class LogSubscriberTest < ActiveRecord::TestCase
ActiveRecord::Base.logger = logger
end
+ def test_schema_statements_are_ignored
+ event = Struct.new(:duration, :payload)
+
+ logger = Class.new(ActiveRecord::LogSubscriber) {
+ attr_accessor :debugs
+
+ def initialize
+ @debugs = []
+ super
+ end
+
+ def debug message
+ @debugs << message
+ end
+ }.new
+ assert_equal 0, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!' }))
+ assert_equal 1, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'foo' }))
+ assert_equal 2, logger.debugs.length
+
+ logger.sql(event.new(0, { :sql => 'hi mom!', :name => 'SCHEMA' }))
+ assert_equal 2, logger.debugs.length
+ end
+
def test_basic_query_logging
Developer.all
wait
diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb
index fb050c3e52..9b20ea08de 100644
--- a/activerecord/test/cases/named_scope_test.rb
+++ b/activerecord/test/cases/named_scope_test.rb
@@ -64,7 +64,7 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Reply.scopes.include?(:base)
assert_equal Reply.find(:all), Reply.base
end
-
+
def test_classes_dont_inherit_subclasses_scopes
assert !ActiveRecord::Base.scopes.include?(:base)
end
@@ -246,6 +246,12 @@ class NamedScopeTest < ActiveRecord::TestCase
assert_no_queries { assert topics.any? }
end
+ def test_model_class_should_respond_to_any
+ assert Topic.any?
+ Topic.delete_all
+ assert !Topic.any?
+ end
+
def test_many_should_not_load_results
topics = Topic.base
assert_queries(2) do
@@ -280,6 +286,15 @@ class NamedScopeTest < ActiveRecord::TestCase
assert Topic.base.many?
end
+ def test_model_class_should_respond_to_many
+ Topic.delete_all
+ assert !Topic.many?
+ Topic.create!
+ assert !Topic.many?
+ Topic.create!
+ assert Topic.many?
+ end
+
def test_should_build_on_top_of_scope
topic = Topic.approved.build({})
assert topic.approved
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8ca9d626d1..9aa13f04cd 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -389,6 +389,92 @@ class PersistencesTest < ActiveRecord::TestCase
assert_not_equal prev_month, developer.updated_at
end
+ def test_update_column
+ topic = Topic.find(1)
+ topic.update_column("approved", true)
+ assert topic.approved?
+ topic.reload
+ assert topic.approved?
+
+ topic.update_column(:approved, false)
+ assert !topic.approved?
+ topic.reload
+ assert !topic.approved?
+ end
+
+ def test_update_column_should_not_use_setter_method
+ dev = Developer.find(1)
+ dev.instance_eval { def salary=(value); write_attribute(:salary, value * 2); end }
+
+ dev.update_column(:salary, 80000)
+ assert_equal 80000, dev.salary
+
+ dev.reload
+ assert_equal 80000, dev.salary
+ end
+
+ def test_update_column_should_raise_exception_if_new_record
+ topic = Topic.new
+ assert_raises(ActiveRecord::ActiveRecordError) { topic.update_column("approved", false) }
+ end
+
+ def test_update_column_should_not_leave_the_object_dirty
+ topic = Topic.find(1)
+ topic.update_attribute("content", "Have a nice day")
+
+ topic.reload
+ topic.update_column(:content, "You too")
+ assert_equal [], topic.changed
+
+ topic.reload
+ topic.update_column("content", "Have a nice day")
+ assert_equal [], topic.changed
+ end
+
+ def test_update_column_with_model_having_primary_key_other_than_id
+ minivan = Minivan.find('m1')
+ new_name = 'sebavan'
+
+ minivan.update_column(:name, new_name)
+ assert_equal new_name, minivan.name
+ end
+
+ def test_update_column_for_readonly_attribute
+ minivan = Minivan.find('m1')
+ prev_color = minivan.color
+ assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_column(:color, 'black') }
+ assert_equal prev_color, minivan.color
+ end
+
+ def test_update_column_should_not_modify_updated_at
+ developer = Developer.find(1)
+ prev_month = Time.now.prev_month
+
+ developer.update_column(:updated_at, prev_month)
+ assert_equal prev_month, developer.updated_at
+
+ developer.update_column(:salary, 80001)
+ assert_equal prev_month, developer.updated_at
+
+ developer.reload
+ assert_equal prev_month.to_i, developer.updated_at.to_i
+ end
+
+ def test_update_column_with_one_changed_and_one_updated
+ t = Topic.order('id').limit(1).first
+ title, author_name = t.title, t.author_name
+ t.author_name = 'John'
+ t.update_column(:title, 'super_title')
+ assert_equal 'John', t.author_name
+ assert_equal 'super_title', t.title
+ assert t.changed?, "topic should have changed"
+ assert t.author_name_changed?, "author_name should have changed"
+
+ t.reload
+ assert_equal author_name, t.author_name
+ assert_equal 'super_title', t.title
+ end
+
def test_update_attributes
topic = Topic.find(1)
assert !topic.approved?
diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb
index 63d8c7d1c1..05a41d8a0a 100644
--- a/activerecord/test/cases/primary_keys_test.rb
+++ b/activerecord/test/cases/primary_keys_test.rb
@@ -136,4 +136,13 @@ class PrimaryKeysTest < ActiveRecord::TestCase
assert_nil ActiveRecord::Base.connection.primary_key('developers_projects')
end
end
+
+ def test_quoted_primary_key_after_set_primary_key
+ k = Class.new( ActiveRecord::Base )
+ assert_equal k.connection.quote_column_name("id"), k.quoted_primary_key
+ k.primary_key = "foo"
+ assert_equal k.connection.quote_column_name("foo"), k.quoted_primary_key
+ k.set_primary_key "bar"
+ assert_equal k.connection.quote_column_name("bar"), k.quoted_primary_key
+ end
end
diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb
index 7369aaea1d..30a783d5a2 100644
--- a/activerecord/test/cases/relation_scoping_test.rb
+++ b/activerecord/test/cases/relation_scoping_test.rb
@@ -429,9 +429,9 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
- def test_except_and_order_overrides_default_scope_order
+ def test_reorder_overrides_default_scope_order
expected = Developer.order('name DESC').collect { |dev| dev.name }
- received = DeveloperOrderedBySalary.except(:order).order('name DESC').collect { |dev| dev.name }
+ received = DeveloperOrderedBySalary.reorder('name DESC').collect { |dev| dev.name }
assert_equal expected, received
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 4c5c871251..fc9df8c7a3 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -151,6 +151,12 @@ class RelationTest < ActiveRecord::TestCase
assert_equal topics(:fourth).title, topics.first.title
end
+ def test_finding_with_reorder
+ topics = Topic.order('author_name').order('title').reorder('id').all
+ topics_titles = topics.map{ |t| t.title }
+ assert_equal ['The First Topic', 'The Second Topic of the day', 'The Third Topic of the day', 'The Fourth Topic of the day'], topics_titles
+ end
+
def test_finding_with_order_and_take
entrants = Entrant.order("id ASC").limit(2).to_a
@@ -666,6 +672,34 @@ class RelationTest < ActiveRecord::TestCase
assert_no_queries { assert_equal 9, best_posts.size }
end
+ def test_size_with_limit
+ posts = Post.limit(10)
+
+ assert_queries(1) { assert_equal 10, posts.size }
+ assert ! posts.loaded?
+
+ best_posts = posts.where(:comments_count => 0)
+ best_posts.to_a # force load
+ assert_no_queries { assert_equal 9, best_posts.size }
+ end
+
+ def test_size_with_zero_limit
+ posts = Post.limit(0)
+
+ assert_no_queries { assert_equal 0, posts.size }
+ assert ! posts.loaded?
+
+ posts.to_a # force load
+ assert_no_queries { assert_equal 0, posts.size }
+ end
+
+ def test_empty_with_zero_limit
+ posts = Post.limit(0)
+
+ assert_no_queries { assert_equal true, posts.empty? }
+ assert ! posts.loaded?
+ end
+
def test_count_complex_chained_relations
posts = Post.select('comments_count').where('id is not null').group("author_id").where("comments_count > 0")
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 1c21f0f3b6..ceb1452afd 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -131,8 +131,9 @@ class TimestampTest < ActiveRecord::TestCase
toy = Toy.first
pet = toy.pet
owner = pet.owner
+ time = 3.days.ago
- owner.update_attribute(:updated_at, (time = 3.days.ago))
+ owner.update_column(:updated_at, time)
toy.touch
owner.reload
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 75f1df44e7..c1e0a214d2 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.1.0 (unreleased)*
+* Added `config.force_ssl` configuration which loads Rack::SSL middleware and force all requests to be under HTTPS protocol [DHH, Prem Sichanugrist, and Josh Peek]
+
* Added `rails plugin new` command which generates rails plugin with gemspec, tests and dummy application for testing [Piotr Sarnacki]
* Added -j parameter with jquery/prototype as options. Now you can create your apps with jQuery using `rails new myapp -j jquery`. The default is still Prototype. [siong1987]
diff --git a/railties/guides/source/action_controller_overview.textile b/railties/guides/source/action_controller_overview.textile
index ecb03a48e4..178d98c2d6 100644
--- a/railties/guides/source/action_controller_overview.textile
+++ b/railties/guides/source/action_controller_overview.textile
@@ -816,6 +816,28 @@ end
NOTE: Certain exceptions are only rescuable from the +ApplicationController+ class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's "article":http://m.onkey.org/2008/7/20/rescue-from-dispatching on the subject for more information.
+h3. Force HTTPS protocol
+
+Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reason. Since Rails 3.1 you can now use +force_ssl+ method in your controller to enforce that:
+
+<ruby>
+class DinnerController
+ force_ssl
+end
+</ruby>
+
+Just like the filter, you could also passing +:only+ and +:except+ to enforce the secure connection only to specific actions
+
+<ruby>
+class DinnerController
+ force_ssl :only => :cheeseburger
+ # or
+ force_ssl :except => :cheeseburger
+end
+</ruby>
+
+Please note that if you found yourself adding +force_ssl+ to many controllers, you may found yourself wanting to force the whole application to use HTTPS instead. In that case, you can set the +config.force_ssl+ in your environment file.
+
h3. Changelog
* February 17, 2009: Yet another proofread by Xavier Noria.
diff --git a/railties/guides/source/active_record_querying.textile b/railties/guides/source/active_record_querying.textile
index 009d541106..f3a10b8b92 100644
--- a/railties/guides/source/active_record_querying.textile
+++ b/railties/guides/source/active_record_querying.textile
@@ -76,7 +76,7 @@ Primary operation of <tt>Model.find(options)</tt> can be summarized as:
h4. Retrieving a Single Object
-Active Record lets you retrieve a single object using three different ways.
+Active Record lets you retrieve a single object using five different ways.
h5. Using a Primary Key
@@ -130,6 +130,40 @@ SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
<tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
+h5. +first!+
+
+<tt>Model.first!</tt> finds the first record. For example:
+
+<ruby>
+client = Client.first!
+=> #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
+
+h5. +last!+
+
+<tt>Model.last!</tt> finds the last record. For example:
+
+<ruby>
+client = Client.last!
+=> #<Client id: 221, first_name: "Russel">
+</ruby>
+
+SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
+</sql>
+
+<tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found.
+
h4. Retrieving Multiple Objects
h5. Using Multiple Primary Keys
@@ -928,6 +962,26 @@ Client.exists?
The above returns +false+ if the +clients+ table is empty and +true+ otherwise.
+You can also use +any?+ and +many?+ to check for existence on a model or relation.
+
+<ruby>
+# via a model
+Post.any?
+Post.many?
+
+# via a named scope
+Post.recent.any?
+Post.recent.many?
+
+# via a relation
+Post.where(:published => true).any?
+Post.where(:published => true).many?
+
+# via an association
+Post.first.categories.any?
+Post.first.categories.many?
+</ruby>
+
h3. Calculations
This section uses count as an example method in this preamble, but the options described apply to all sub-sections.
diff --git a/railties/guides/source/active_record_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index e5349d546c..514d0322b9 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -84,6 +84,7 @@ The following methods skip validations, and will save the object to the database
* +toggle!+
* +update_all+
* +update_attribute+
+* +update_column+
* +update_counters+
Note that +save+ also has the ability to skip validations if passed +:validate => false+ as argument. This technique should be used with caution.
@@ -993,6 +994,7 @@ Just as with validations, it's also possible to skip callbacks. These methods sh
* +increment+
* +increment_counter+
* +toggle+
+* +update_column+
* +update_all+
* +update_counters+
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 62b846e871..298335d484 100644
--- a/railties/guides/source/configuring.textile
+++ b/railties/guides/source/configuring.textile
@@ -81,6 +81,8 @@ end
* +config.filter_parameters+ used for filtering out the parameters that you don't want shown in the logs, such as passwords or credit card numbers.
+* +config.force_ssl+ forcing all requests to be under HTTPS protocol by using +Rack::SSL+ middleware. This will secure your application from a session hijack attempt.
+
* +config.helper_paths+ configures where Rails can find helpers for this application.
* +config.log_level+ defines the verbosity of the Rails logger. In production mode, this defaults to +:info+. In development mode, it defaults to +:debug+.
@@ -147,6 +149,7 @@ h4. Configuring Middleware
Every Rails application comes with a standard set of middleware which it uses in this order in the development environment:
+* +Rack::SSL+ Will force every requests to be under HTTPS protocol. Will be available if +config.force_ssl+ is set to _true_.
* +ActionDispatch::Static+ is used to serve static assets. Disabled if +config.serve_static_assets+ is _true_.
* +Rack::Lock+ Will wrap the app in mutex so it can only be called by a single thread at a time. Only enabled if +config.action_controller.allow_concurrency+ is set to _false_, which it is by default.
* +ActiveSupport::Cache::Strategy::LocalCache+ Serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread.
diff --git a/railties/guides/source/routing.textile b/railties/guides/source/routing.textile
index c447fd911a..58b75b9a1d 100644
--- a/railties/guides/source/routing.textile
+++ b/railties/guides/source/routing.textile
@@ -557,6 +557,18 @@ match '*a/foo/*b' => 'test#index'
would match +zoo/woo/foo/bar/baz+ with +params[:a]+ equals +"zoo/woo"+, and +params[:b]+ equals +"bar/baz"+.
+NOTE: Starting from Rails 3.1, wildcard route will always matching the optional format segment by default. For example if you have this route:
+
+<ruby>
+map '*pages' => 'pages#show'
+</ruby>
+
+NOTE: By requesting +"/foo/bar.json"+, your +params[:pages]+ will be equals to +"foo/bar"+ with the request format of JSON. If you want the old 3.0.x behavior back, you could supply +:format => false+ like this:
+
+<ruby>
+map '*pages' => 'pages#show', :format => false
+</ruby>
+
h4. Redirection
You can redirect any path to another path using the +redirect+ helper in your router:
diff --git a/railties/guides/source/security.textile b/railties/guides/source/security.textile
index 182f3631ef..893f65856c 100644
--- a/railties/guides/source/security.textile
+++ b/railties/guides/source/security.textile
@@ -57,7 +57,11 @@ Many web applications have an authentication system: a user provides a user name
Hence, the cookie serves as temporary authentication for the web application. Everyone who seizes a cookie from someone else, may use the web application as this user – with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures:
-* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _(highlight)provide a secure connection over SSL_.
+* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. This is one more reason not to work from a coffee shop. For the web application builder this means to _(highlight)provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file:
+
+<ruby>
+config.force_ssl = true
+</ruby>
* Most people don't clear out the cookies after working at a public terminal. So if the last user didn't log out of a web application, you would be able to use it as this user. Provide the user with a _(highlight)log-out button_ in the web application, and _(highlight)make it prominent_.
diff --git a/railties/guides/source/testing.textile b/railties/guides/source/testing.textile
index 4ebdb3edf6..d3f72509c6 100644
--- a/railties/guides/source/testing.textile
+++ b/railties/guides/source/testing.textile
@@ -81,7 +81,7 @@ Each fixture is given a name followed by an indented list of colon-separated key
h5. ERb'in It Up
-ERb allows you embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERb when you load fixtures. This allows you to use Ruby to help you generate some sample data.
+ERb allows you to embed ruby code within templates. Both the YAML and CSV fixture formats are pre-processed with ERb when you load fixtures. This allows you to use Ruby to help you generate some sample data.
<erb>
<% earth_size = 20 %>
@@ -227,7 +227,7 @@ $ rake db:migrate
$ rake db:test:load
</shell>
-Above +rake db:migrate+ runs any pending migrations on the _development_ environment and updates +db/schema.rb+. +rake db:test:load+ recreates the test database from the current +db/schema.rb+. On subsequent attempts, it is a good idea to first run +db:test:prepare+, as it first checks for pending migrations and warns you appropriately.
+The +rake db:migrate+ above runs any pending migrations on the _development_ environment and updates +db/schema.rb+. The +rake db:test:load+ recreates the test database from the current +db/schema.rb+. On subsequent attempts, it is a good idea to first run +db:test:prepare+, as it first checks for pending migrations and warns you appropriately.
NOTE: +db:test:prepare+ will fail with an error if +db/schema.rb+ doesn't exist.
diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb
index 754b03258a..5b8841fd97 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -174,15 +174,21 @@ module Rails
def default_middleware_stack
ActionDispatch::MiddlewareStack.new.tap do |middleware|
- rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
+ if rack_cache = config.action_controller.perform_caching && config.action_dispatch.rack_cache
+ require "action_dispatch/http/rack_cache"
+ middleware.use ::Rack::Cache, rack_cache
+ end
- require "action_dispatch/http/rack_cache" if rack_cache
- middleware.use ::Rack::Cache, rack_cache if rack_cache
+ if config.force_ssl
+ require "rack/ssl"
+ middleware.use ::Rack::SSL
+ end
if config.serve_static_assets
asset_paths = ActiveSupport::OrderedHash[config.static_asset_paths.to_a.reverse]
middleware.use ::ActionDispatch::Static, asset_paths
end
+
middleware.use ::Rack::Lock unless config.allow_concurrency
middleware.use ::Rack::Runtime
middleware.use ::Rails::Rack::Logger
@@ -203,7 +209,10 @@ module Rails
middleware.use ::ActionDispatch::Head
middleware.use ::Rack::ConditionalGet
middleware.use ::Rack::ETag, "no-cache"
- middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support if config.action_dispatch.best_standards_support
+
+ if config.action_dispatch.best_standards_support
+ middleware.use ::ActionDispatch::BestStandardsSupport, config.action_dispatch.best_standards_support
+ end
end
end
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 9e87714e4f..4ec5336f6c 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -9,7 +9,7 @@ module Rails
:filter_parameters, :helpers_paths, :logger,
:preload_frameworks, :reload_plugins,
:secret_token, :serve_static_assets, :session_options,
- :time_zone, :whiny_nils,
+ :time_zone, :whiny_nils, :force_ssl,
:asset_pipeline, :precompile_assets
attr_writer :log_level
@@ -23,6 +23,7 @@ module Rails
@helpers_paths = []
@dependency_loading = true
@serve_static_assets = true
+ @force_ssl = false
@session_store = :cookie_store
@session_options = {}
@time_zone = "UTC"
diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb
index 4fc23fe277..ee265366ff 100644
--- a/railties/lib/rails/engine.rb
+++ b/railties/lib/rails/engine.rb
@@ -360,11 +360,11 @@ module Rails
def isolate_namespace(mod)
engine_name(generate_railtie_name(mod))
- name = engine_name
- self.routes.default_scope = {:module => name}
+ self.routes.default_scope = { :module => ActiveSupport::Inflector.underscore(mod.name) }
self.isolated = true
unless mod.respond_to?(:_railtie)
+ name = engine_name
_railtie = self
mod.singleton_class.instance_eval do
define_method(:_railtie) do
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index ab7ed4eb9e..a2eaf7a6fb 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -1,5 +1,6 @@
require 'digest/md5'
require 'active_support/secure_random'
+require 'active_support/core_ext/string/strip'
require 'rails/version' unless defined?(Rails::VERSION)
require 'rbconfig'
require 'open-uri'
@@ -112,30 +113,38 @@ module Rails
end
def database_gemfile_entry
- options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
+ entry = options[:skip_active_record] ? "" : "gem '#{gem_for_database}'"
+ if options[:database] == 'mysql'
+ if options.dev? || options.edge?
+ entry += ", :git => 'git://github.com/brianmario/mysql2.git'"
+ else
+ entry += "\n# gem 'mysql2', :git => 'git://github.com/brianmario/mysql2.git'"
+ end
+ end
+ entry
end
def rails_gemfile_entry
if options.dev?
- <<-GEMFILE
-gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
-gem 'arel', :git => 'git://github.com/rails/arel.git'
-gem "rack", :git => "git://github.com/rack/rack.git"
+ <<-GEMFILE.strip_heredoc
+ gem 'rails', :path => '#{Rails::Generators::RAILS_DEV_PATH}'
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
+ gem 'rack', :git => 'git://github.com/rack/rack.git'
GEMFILE
elsif options.edge?
- <<-GEMFILE
-gem 'rails', :git => 'git://github.com/rails/rails.git'
-gem 'arel', :git => 'git://github.com/rails/arel.git'
-gem "rack", :git => "git://github.com/rack/rack.git"
+ <<-GEMFILE.strip_heredoc
+ gem 'rails', :git => 'git://github.com/rails/rails.git'
+ gem 'arel', :git => 'git://github.com/rails/arel.git'
+ gem 'rack', :git => 'git://github.com/rack/rack.git'
GEMFILE
else
- <<-GEMFILE
-gem 'rails', '#{Rails::VERSION::STRING}'
+ <<-GEMFILE.strip_heredoc
+ gem 'rails', '#{Rails::VERSION::STRING}'
-# Bundle edge Rails instead:
-# gem 'rails', :git => 'git://github.com/rails/rails.git'
-# gem 'arel', :git => 'git://github.com/rails/arel.git'
-# gem "rack", :git => "git://github.com/rack/rack.git"
+ # Bundle edge Rails instead:
+ # gem 'rails', :git => 'git://github.com/rails/rails.git'
+ # gem 'arel', :git => 'git://github.com/rails/arel.git'
+ # gem 'rack', :git => 'git://github.com/rack/rack.git'
GEMFILE
end
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
index 91d3133ea4..bdb897ad33 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
@@ -3,7 +3,7 @@
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
- # since you don't have to restart the webserver when you make code changes.
+ # since you don't have to restart the web server when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
index 89bb891ddd..874cc403ba 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -1,7 +1,6 @@
<%= app_const %>.configure do
# Settings specified here will take precedence over those in config/application.rb
- # The production environment is meant for finished, "live" apps.
# Code is not reloaded between requests
config.cache_classes = true
@@ -9,14 +8,15 @@
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
- # Specifies the header that your server uses for sending files
- config.action_dispatch.x_sendfile_header = "X-Sendfile"
+ # Disable Rails's static asset server (Apache or nginx will already do this)
+ config.serve_static_assets = false
- # For nginx:
- # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
+ # Specifies the header that your server uses for sending files
+ # (comment out if your front-end server doesn't support this)
+ config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx
- # If you have no front-end server that supports something like X-Sendfile,
- # just comment this out and Rails will serve the files
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
# See everything in the log (default is :info)
# config.log_level = :debug
@@ -27,10 +27,6 @@
# Use a different cache store in production
# config.cache_store = :mem_cache_store
- # Disable Rails's static asset server
- # In production, Apache or nginx will already do this
- config.serve_static_assets = false
-
# Enable serving of images, stylesheets, and javascripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
diff --git a/railties/railties.gemspec b/railties/railties.gemspec
index c3793d3ac3..c51fe856be 100644
--- a/railties/railties.gemspec
+++ b/railties/railties.gemspec
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency('rake', '>= 0.8.7')
s.add_dependency('thor', '~> 0.14.4')
+ s.add_dependency('rack-ssl', '~> 1.3.2')
s.add_dependency('activesupport', version)
s.add_dependency('actionpack', version)
end
diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb
index b314832685..01e6c49d9c 100644
--- a/railties/test/application/middleware_test.rb
+++ b/railties/test/application/middleware_test.rb
@@ -52,6 +52,12 @@ module ApplicationTests
assert_equal "Rack::Cache", middleware.first
end
+ test "Rack::SSL is present when force_ssl is set" do
+ add_to_config "config.force_ssl = true"
+ boot!
+ assert middleware.include?("Rack::SSL")
+ end
+
test "removing Active Record omits its middleware" do
use_frameworks []
boot!
diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb
index 0ce00db3c4..20797a2b0c 100644
--- a/railties/test/railties/engine_test.rb
+++ b/railties/test/railties/engine_test.rb
@@ -559,6 +559,45 @@ module RailtiesTest
assert_match /name="post\[title\]"/, last_response.body
end
+ test "isolated engine should set correct route module prefix for nested namespace" do
+ @plugin.write "lib/bukkits.rb", <<-RUBY
+ module Bukkits
+ module Awesome
+ class Engine < ::Rails::Engine
+ isolate_namespace Bukkits::Awesome
+ end
+ end
+ end
+ RUBY
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ mount Bukkits::Awesome::Engine => "/bukkits", :as => "bukkits"
+ end
+ RUBY
+
+ @plugin.write "config/routes.rb", <<-RUBY
+ Bukkits::Awesome::Engine.routes.draw do
+ match "/foo" => "foo#index"
+ end
+ RUBY
+
+ @plugin.write "app/controllers/bukkits/awesome/foo_controller.rb", <<-RUBY
+ class Bukkits::Awesome::FooController < ActionController::Base
+ def index
+ render :text => "ok"
+ end
+ end
+ RUBY
+
+ add_to_config("config.action_dispatch.show_exceptions = false")
+
+ boot_rails
+
+ get("/bukkits/foo")
+ assert_equal "ok", last_response.body
+ end
+
test "loading seed data" do
@plugin.write "db/seeds.rb", <<-RUBY
Bukkits::Engine.config.bukkits_seeds_loaded = true