aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVijay Dev <vijaydev.cse@gmail.com>2011-03-29 00:41:11 +0530
committerVijay Dev <vijaydev.cse@gmail.com>2011-03-29 00:41:11 +0530
commit910a8d22460a8b8e67a16441dadb87cc12ab7a4a (patch)
treea0561863efa0e49b6f1982b6a39182e7750c5083
parent7b9bdd9253c7f8f3b89664ac616ff9bd5ea3ac87 (diff)
parent62dd3458e326b1f2927d43401e7b10004410fdf0 (diff)
downloadrails-910a8d22460a8b8e67a16441dadb87cc12ab7a4a.tar.gz
rails-910a8d22460a8b8e67a16441dadb87cc12ab7a4a.tar.bz2
rails-910a8d22460a8b8e67a16441dadb87cc12ab7a4a.zip
Merge branch 'master' of github.com:lifo/docrails
-rw-r--r--actionpack/CHANGELOG8
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb35
-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/template/form_helper_test.rb21
-rw-r--r--actionpack/test/template/javascript_helper_test.rb1
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb3
-rw-r--r--activerecord/lib/active_record/persistence.rb18
-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.rb21
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/calculations_test.rb4
-rw-r--r--activerecord/test/cases/dirty_test.rb5
-rw-r--r--activerecord/test/cases/persistence_test.rb88
-rw-r--r--activerecord/test/cases/timestamp_test.rb6
-rw-r--r--railties/CHANGELOG2
-rw-r--r--railties/guides/source/action_controller_overview.textile22
-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/security.textile6
-rw-r--r--railties/lib/rails/application.rb17
-rw-r--r--railties/lib/rails/application/configuration.rb3
-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
35 files changed, 331 insertions, 104 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5ab92c8cfc..a90a7b37f7 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,13 @@
*Rails 3.1.0 (unreleased)*
+* 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/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/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_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/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/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 1d2e8667e4..dfcb116392 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -34,7 +34,8 @@ module ActiveRecord
when :destroy
target.destroy
when :nullify
- target.update_attribute(reflection.foreign_key, nil)
+ target.send("#{reflection.foreign_key}=", nil)
+ target.save(:validations => false)
end
end
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 17a64b6e86..3377a5934b 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -104,19 +104,17 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record.
- # This is especially useful for boolean flags on existing records. Also note that
+ # Updates a single attribute of an object, without calling save.
#
# * Validation is skipped.
- # * Callbacks are invoked.
- # * updated_at/updated_on column is updated if that column is available.
- # * Updates all the attributes that are dirty in this object.
+ # * Callbacks are skipped.
+ # * updated_at/updated_on column is not updated in any case.
#
- def update_attribute(name, value)
+ def update_column(name, value)
name = name.to_s
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
send("#{name}=", value)
- save(:validate => false)
+ self.class.update_all({ name => value }, self.class.primary_key => id)
end
# Updates the attributes of the model from the passed-in hash and saves the
@@ -156,7 +154,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def increment!(attribute, by = 1)
- increment(attribute, by).update_attribute(attribute, self[attribute])
+ increment(attribute, by).update_column(attribute, self[attribute])
end
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
@@ -173,7 +171,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def decrement!(attribute, by = 1)
- decrement(attribute, by).update_attribute(attribute, self[attribute])
+ decrement(attribute, by).update_column(attribute, self[attribute])
end
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
@@ -190,7 +188,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def toggle!(attribute)
- toggle(attribute).update_attribute(attribute, self[attribute])
+ toggle(attribute).update_column(attribute, self[attribute])
end
# Reloads the attributes of this object from the database.
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 d03c3ee1a1..84f75cc803 100644
--- a/activerecord/test/cases/attribute_methods_test.rb
+++ b/activerecord/test/cases/attribute_methods_test.rb
@@ -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/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index caf07a7357..c97f1ae634 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -311,8 +311,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/dirty_test.rb b/activerecord/test/cases/dirty_test.rb
index a6738fb654..b75482a956 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
@@ -487,7 +487,8 @@ class DirtyTest < ActiveRecord::TestCase
assert !pirate.previous_changes.key?('created_on')
pirate = Pirate.find_by_catchphrase("Ahoy!")
- pirate.update_attribute(:catchphrase, "Ninjas suck!")
+ pirate.catchphrase = "Ninjas suck!"
+ pirate.save(:validations => false)
assert_equal 2, pirate.previous_changes.size
assert_equal ["Ahoy!", "Ninjas suck!"], pirate.previous_changes['catchphrase']
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 8ca9d626d1..68d861c9c2 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -327,66 +327,62 @@ class PersistencesTest < ActiveRecord::TestCase
assert_raise(ActiveSupport::FrozenObjectError) { client.name = "something else" }
end
- def test_update_attribute
- assert !Topic.find(1).approved?
- Topic.find(1).update_attribute("approved", true)
- assert Topic.find(1).approved?
+ def test_update_column
+ topic = Topic.find(1)
+ topic.update_column("approved", true)
+ assert topic.approved?
+ topic.reload
+ assert topic.approved?
- Topic.find(1).update_attribute(:approved, false)
- assert !Topic.find(1).approved?
+ topic.update_column(:approved, false)
+ assert !topic.approved?
+ topic.reload
+ assert !topic.approved?
end
- def test_update_attribute_for_readonly_attribute
+ def test_update_column_with_model_having_primary_key_other_than_id
minivan = Minivan.find('m1')
- assert_raises(ActiveRecord::ActiveRecordError) { minivan.update_attribute(:color, 'black') }
- end
-
- # This test is correct, but it is hard to fix it since
- # update_attribute trigger simply call save! that triggers
- # all callbacks.
- # def test_update_attribute_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_attribute(: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"
- # assert !t.title_changed?, "title should not have changed"
- # assert_nil t.title_change, 'title change should be nil'
- # assert_equal ['author_name'], t.changed
- #
- # t.reload
- # assert_equal 'David', t.author_name
- # assert_equal 'super_title', t.title
- # end
-
- def test_update_attribute_with_one_updated
- t = Topic.first
- title = t.title
- t.update_attribute(:title, 'super_title')
- assert_equal 'super_title', t.title
- assert !t.changed?, "topic should not have changed"
- assert !t.title_changed?, "title should not have changed"
- assert_nil t.title_change, 'title change should be nil'
+ new_name = 'sebavan'
- t.reload
- assert_equal 'super_title', t.title
+ minivan.update_column(:name, new_name)
+ assert_equal new_name, minivan.name
end
- def test_update_attribute_for_updated_at_on
+ 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_attribute(:updated_at, prev_month)
+ developer.update_column(:updated_at, prev_month)
assert_equal prev_month, developer.updated_at
- developer.update_attribute(:salary, 80001)
- assert_not_equal prev_month, developer.updated_at
+ developer.update_column(:salary, 80001)
+ assert_equal prev_month, developer.updated_at
developer.reload
- assert_not_equal prev_month, developer.updated_at
+ assert_equal prev_month, developer.updated_at
+ 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"
+ assert t.title_changed?, "title should have changed"
+
+ t.reload
+ assert_equal author_name, t.author_name
+ assert_equal 'super_title', t.title
end
def test_update_attributes
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index 1c21f0f3b6..08bbd14d38 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -113,7 +113,8 @@ class TimestampTest < ActiveRecord::TestCase
pet = Pet.first
owner = pet.owner
- owner.update_attribute(:happy_at, 3.days.ago)
+ owner.happy_at = 3.days.ago
+ owner.save
previously_owner_updated_at = owner.updated_at
pet.name = "I'm a parrot"
@@ -131,8 +132,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_validations_callbacks.textile b/railties/guides/source/active_record_validations_callbacks.textile
index 6c80ec39f2..514d0322b9 100644
--- a/railties/guides/source/active_record_validations_callbacks.textile
+++ b/railties/guides/source/active_record_validations_callbacks.textile
@@ -83,6 +83,7 @@ The following methods skip validations, and will save the object to the database
* +increment_counter+
* +toggle!+
* +update_all+
+* +update_attribute+
* +update_column+
* +update_counters+
@@ -964,6 +965,7 @@ The following methods trigger callbacks:
* +save(false)+
* +toggle!+
* +update+
+* +update_attribute+
* +update_attributes+
* +update_attributes!+
* +valid?+
diff --git a/railties/guides/source/configuring.textile b/railties/guides/source/configuring.textile
index 62b846e871..9ca567129b 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 request 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/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/lib/rails/application.rb b/railties/lib/rails/application.rb
index 94819820bc..1b834275a7 100644
--- a/railties/lib/rails/application.rb
+++ b/railties/lib/rails/application.rb
@@ -145,15 +145,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
@@ -174,7 +180,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 c74bcbedf2..23b0e765ae 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
attr_writer :log_level
@@ -22,6 +22,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/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!