Skip to content
Browse files

Merge branch 'master' of https://github.com/rails/rails into performa…

…nce_test
  • Loading branch information...
2 parents 726b7ed + 245542e commit 6f0caa1a534a065753d51430b649114bc8bf54ac @goncalossilva goncalossilva committed Mar 28, 2011
Showing with 479 additions and 79 deletions.
  1. +8 −0 actionpack/CHANGELOG
  2. +1 −0 actionpack/lib/action_controller.rb
  3. +1 −0 actionpack/lib/action_controller/base.rb
  4. +6 −7 actionpack/lib/action_controller/caching/actions.rb
  5. +35 −0 actionpack/lib/action_controller/metal/force_ssl.rb
  6. +1 −1 actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb
  7. +13 −2 actionpack/lib/action_view/helpers/form_helper.rb
  8. +5 −0 actionpack/lib/action_view/helpers/javascript_helper.rb
  9. +2 −2 actionpack/lib/action_view/template/resolver.rb
  10. +83 −0 actionpack/test/controller/force_ssl_test.rb
  11. +16 −0 actionpack/test/dispatch/show_exceptions_test.rb
  12. +19 −2 actionpack/test/template/form_helper_test.rb
  13. +1 −0 actionpack/test/template/javascript_helper_test.rb
  14. +9 −0 activerecord/CHANGELOG
  15. +1 −0 activerecord/lib/active_record/attribute_methods/write.rb
  16. +14 −0 activerecord/lib/active_record/persistence.rb
  17. +8 −6 activerecord/lib/active_record/relation/finder_methods.rb
  18. +1 −1 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
  19. +3 −3 activerecord/test/cases/associations/has_many_associations_test.rb
  20. +2 −2 activerecord/test/cases/associations/has_many_through_associations_test.rb
  21. +2 −2 activerecord/test/cases/associations/has_one_through_associations_test.rb
  22. +6 −6 activerecord/test/cases/associations/join_model_test.rb
  23. +2 −2 activerecord/test/cases/associations_test.rb
  24. +23 −2 activerecord/test/cases/attribute_methods_test.rb
  25. +1 −1 activerecord/test/cases/base_test.rb
  26. +2 −2 activerecord/test/cases/calculations_test.rb
  27. +1 −1 activerecord/test/cases/dirty_test.rb
  28. +86 −0 activerecord/test/cases/persistence_test.rb
  29. +2 −1 activerecord/test/cases/timestamp_test.rb
  30. +2 −0 railties/CHANGELOG
  31. +22 −0 railties/guides/source/action_controller_overview.textile
  32. +35 −1 railties/guides/source/active_record_querying.textile
  33. +2 −0 railties/guides/source/active_record_validations_callbacks.textile
  34. +3 −0 railties/guides/source/configuring.textile
  35. +5 −1 railties/guides/source/security.textile
  36. +2 −2 railties/guides/source/testing.textile
  37. +13 −4 railties/lib/rails/application.rb
  38. +2 −1 railties/lib/rails/application/configuration.rb
  39. +24 −15 railties/lib/rails/generators/app_base.rb
  40. +1 −1 railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt
  41. +7 −11 railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
  42. +1 −0 railties/railties.gemspec
  43. +6 −0 railties/test/application/middleware_test.rb
View
8 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]
View
1 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
View
1 actionpack/lib/action_controller/base.rb
@@ -198,6 +198,7 @@ def self.without_modules(*modules)
Cookies,
Flash,
RequestForgeryProtection,
+ ForceSSL,
Streaming,
RecordIdentifier,
HttpAuthentication::Basic::ControllerMethods,
View
13 actionpack/lib/action_controller/caching/actions.rb
@@ -56,19 +56,18 @@ module Caching
#
# 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
View
35 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
View
2 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>
View
15 actionpack/lib/action_view/helpers/form_helper.rb
@@ -185,7 +185,7 @@ module FormHelper
#
# 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 FormHelper
# 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 FormHelper
#
# 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 @@ def form_for(record, options = {}, &proc)
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)
View
5 actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -47,6 +47,9 @@ module JavaScriptHelper
"'" => "\\'" }
# 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 @@ def escape_javascript(javascript)
end
end
+ alias_method :j, :escape_javascript
+
# Returns a JavaScript tag with the +content+ inside. Example:
# javascript_tag "alert('All is good')"
#
View
4 actionpack/lib/action_view/template/resolver.rb
@@ -157,8 +157,8 @@ def build_query(path, exts)
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
View
83 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
View
16 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
View
21 actionpack/test/template/form_helper_test.rb
@@ -715,14 +715,31 @@ def test_form_for_with_symbol_object_name
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' />" +
View
1 actionpack/test/template/javascript_helper_test.rb
@@ -27,6 +27,7 @@ def test_escape_javascript
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
View
9 activerecord/CHANGELOG
@@ -1,5 +1,14 @@
*Rails 3.1.0 (unreleased)*
+* 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
View
1 activerecord/lib/active_record/attribute_methods/write.rb
@@ -32,6 +32,7 @@ def write_attribute(attr_name, value)
@attributes[attr_name] = value
end
end
+ alias_method :raw_write_attribute, :write_attribute
private
# Handle *= for method_missing.
View
14 activerecord/lib/active_record/persistence.rb
@@ -119,6 +119,20 @@ def update_attribute(name, value)
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.
View
14 activerecord/lib/active_record/relation/finder_methods.rb
@@ -123,9 +123,10 @@ def first(*args)
end
end
- # Same as #first! but raises RecordNotFound if no record is returned
- def first!(*args)
- self.first(*args) or raise RecordNotFound
+ # 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
@@ -142,9 +143,10 @@ def last(*args)
end
end
- # Same as #last! but raises RecordNotFound if no record is returned
- def last!(*args)
- self.last(*args) or raise RecordNotFound
+ # 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
View
2 activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb
@@ -604,7 +604,7 @@ def test_update_attributes_after_push_without_duplicate_join_table_rows
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}
View
6 activerecord/test/cases/associations/has_many_associations_test.rb
@@ -639,7 +639,7 @@ def test_deleting_updates_counter_cache_without_dependent_option
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 @@ def test_deleting_updates_counter_cache_with_dependent_delete_all
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 @@ def test_delete_all_association_with_primary_key_deletes_correct_records
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
View
4 activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -286,7 +286,7 @@ def test_update_counter_caches_on_delete
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 @@ def test_update_counter_caches_on_delete_with_dependent_destroy
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
View
4 activerecord/test/cases/associations/has_one_through_associations_test.rb
@@ -90,12 +90,12 @@ def test_has_one_through_eager_loading_through_polymorphic
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
View
12 activerecord/test/cases/associations/join_model_test.rb
@@ -161,7 +161,7 @@ def test_create_polymorphic_has_one_with_scope
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 @@ def test_delete_polymorphic_has_many_with_delete_all
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 @@ def test_delete_polymorphic_has_many_with_destroy
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 @@ def test_delete_polymorphic_has_many_with_nullify
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 @@ def test_delete_polymorphic_has_one_with_destroy
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 @@ def test_has_many_through_goes_through_all_sti_classes
# 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)
View
4 activerecord/test/cases/associations_test.rb
@@ -66,7 +66,7 @@ def test_loading_the_association_target_should_load_most_recent_attributes_for_c
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 @@ def test_save_on_parent_does_not_load_target
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
View
25 activerecord/test/cases/attribute_methods_test.rb
@@ -120,18 +120,39 @@ def test_read_attributes_before_type_cast_on_boolean
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
assert_equal ActiveSupport::TimeZone["Pacific Time (US & Canada)"], record.written_on.time_zone
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 }
View
2 activerecord/test/cases/base_test.rb
@@ -484,7 +484,7 @@ def test_non_valid_identifier_column_name
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
View
4 activerecord/test/cases/calculations_test.rb
@@ -311,8 +311,8 @@ def test_should_count_scoped_select
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
View
2 activerecord/test/cases/dirty_test.rb
@@ -413,7 +413,7 @@ def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_p
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
View
86 activerecord/test/cases/persistence_test.rb
@@ -389,6 +389,92 @@ def test_update_attribute_for_updated_at_on
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, 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"
+
+ 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?
View
3 activerecord/test/cases/timestamp_test.rb
@@ -131,8 +131,9 @@ def test_touching_a_record_touches_parent_record_and_grandparent_record
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
View
2 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]
View
22 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.
View
36 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
View
2 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+
View
3 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.
View
6 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_.
View
4 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.
View
17 railties/lib/rails/application.rb
@@ -145,15 +145,21 @@ def default_asset_path
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 @@ def default_middleware_stack
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
View
3 railties/lib/rails/application/configuration.rb
@@ -9,7 +9,7 @@ class Configuration < ::Rails::Engine::Configuration
: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 @@ def initialize(*)
@helpers_paths = []
@dependency_loading = true
@serve_static_assets = true
+ @force_ssl = false
@session_store = :cookie_store
@session_options = {}
@time_zone = "UTC"
View
39 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 @@ def set_default_accessors!
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
View
2 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.
View
18 railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt
@@ -1,22 +1,22 @@
<%= 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
# Full error reports are disabled and caching is turned on
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"
View
1 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
View
6 railties/test/application/middleware_test.rb
@@ -52,6 +52,12 @@ def app
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!

0 comments on commit 6f0caa1

Please sign in to comment.
Something went wrong with that request. Please try again.