aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actionpack/lib/action_view/helpers/tags/collection_helpers.rb2
-rw-r--r--actionpack/test/template/form_collections_helper_test.rb9
-rw-r--r--activerecord/CHANGELOG.md6
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb8
-rw-r--r--activerecord/test/cases/adapters/mysql/connection_test.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql2/connection_test.rb16
-rw-r--r--activerecord/test/cases/column_test.rb28
-rw-r--r--activerecord/test/schema/schema.rb4
-rw-r--r--guides/rails_guides/textile_extensions.rb6
-rw-r--r--guides/source/active_record_querying.textile64
12 files changed, 151 insertions, 19 deletions
diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
index 6a1479069f..4e33e79a36 100644
--- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb
@@ -49,7 +49,7 @@ module ActionView
accept = if current_value.respond_to?(:call)
current_value.call(item)
else
- Array(current_value).include?(value)
+ Array(current_value).map(&:to_s).include?(value.to_s)
end
if accept
diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb
index 4d878635ef..c73e80ed88 100644
--- a/actionpack/test/template/form_collections_helper_test.rb
+++ b/actionpack/test/template/form_collections_helper_test.rb
@@ -195,6 +195,15 @@ class FormCollectionsHelperTest < ActionView::TestCase
assert_no_select 'input[type=checkbox][value=2][checked=checked]'
end
+ test 'collection check boxes accepts selected string values as :checked option' do
+ collection = (1..3).map{|i| [i, "Category #{i}"] }
+ with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => ['1', '3']
+
+ assert_select 'input[type=checkbox][value=1][checked=checked]'
+ assert_select 'input[type=checkbox][value=3][checked=checked]'
+ assert_no_select 'input[type=checkbox][value=2][checked=checked]'
+ end
+
test 'collection check boxes accepts a single checked value' do
collection = (1..3).map{|i| [i, "Category #{i}"] }
with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a661a44f1f..31886c8212 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,11 @@
## Rails 4.0.0 (unreleased) ##
+* `mysql` and `mysql2` connections will set `SQL_MODE=STRICT_ALL_TABLES` by
+ default to avoid silent data loss. This can be disabled by specifying
+ `strict: false` in your `database.yml`.
+
+ *Michael Pearson*
+
* Added default order to `first` to assure consistent results among
diferent database engines. Introduced `take` as a replacement to
the old behavior of `first`.
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 9af8e46120..1933ce2b46 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -158,7 +158,7 @@ module ActiveRecord
def value_to_date(value)
if value.is_a?(String)
- return nil if value.empty?
+ return nil if value.blank?
fast_string_to_date(value) || fallback_string_to_date(value)
elsif value.respond_to?(:to_date)
value.to_date
@@ -169,14 +169,14 @@ module ActiveRecord
def string_to_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
fast_string_to_time(string) || fallback_string_to_time(string)
end
def string_to_dummy_time(string)
return string unless string.is_a?(String)
- return nil if string.empty?
+ return nil if string.blank?
string_to_time "2000-01-01 #{string}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 92908d9599..350ccce03d 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -253,6 +253,14 @@ module ActiveRecord
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
variable_assignments = ['SQL_AUTO_IS_NULL=0']
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ variable_assignments << "SQL_MODE='STRICT_ALL_TABLES'"
+ end
+
encoding = @config[:encoding]
# make sure we set the encoding
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index 724dbff1f0..0b6734b010 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -53,6 +53,7 @@ module ActiveRecord
# * <tt>:database</tt> - The name of the database. No default, must be provided.
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
+ # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html)
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
@@ -404,6 +405,13 @@ module ActiveRecord
# By default, MySQL 'where id is null' selects the last inserted id.
# Turn this off. http://dev.rubyonrails.org/ticket/6778
execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+
+ # Make MySQL reject illegal values rather than truncating or
+ # blanking them. See
+ # http://dev.mysql.com/doc/refman/5.5/en/server-sql-mode.html#sqlmode_strict_all_tables
+ if @config.fetch(:strict, true)
+ execute("SET SQL_MODE='STRICT_ALL_TABLES'", :skip_logging)
+ end
end
def select(sql, name = nil, binds = [])
diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb
index fa2ba8d592..5e1c52c9ba 100644
--- a/activerecord/test/cases/adapters/mysql/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql/connection_test.rb
@@ -120,6 +120,19 @@ class MysqlConnectionTest < ActiveRecord::TestCase
end
end
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb
index 8e2b9ca9a5..684c7f5929 100644
--- a/activerecord/test/cases/adapters/mysql2/connection_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb
@@ -29,6 +29,22 @@ class MysqlConnectionTest < ActiveRecord::TestCase
assert @connection.active?
end
+ # TODO: Below is a straight up copy/paste from mysql/connection_test.rb
+ # I'm not sure what the correct way is to share these tests between
+ # adapters in minitest.
+ def test_mysql_default_in_strict_mode
+ result = @connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [["STRICT_ALL_TABLES"]], result.rows
+ end
+
+ def test_mysql_strict_mode_disabled
+ run_without_connection do |orig_connection|
+ ActiveRecord::Model.establish_connection(orig_connection.merge({:strict => false}))
+ result = ActiveRecord::Model.connection.exec_query "SELECT @@SESSION.sql_mode"
+ assert_equal [['']], result.rows
+ end
+ end
+
private
def run_without_connection
diff --git a/activerecord/test/cases/column_test.rb b/activerecord/test/cases/column_test.rb
index 4fcf8a33a4..4111a5f808 100644
--- a/activerecord/test/cases/column_test.rb
+++ b/activerecord/test/cases/column_test.rb
@@ -48,6 +48,34 @@ module ActiveRecord
column.type_cast(false)
end
end
+
+ def test_type_cast_time
+ column = Column.new("field", nil, "time")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ time_string = Time.now.utc.strftime("%T")
+ assert_equal time_string, column.type_cast(time_string).strftime("%T")
+ end
+
+ def test_type_cast_datetime_and_timestamp
+ [Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column|
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ datetime_string = Time.now.utc.strftime("%FT%T")
+ assert_equal datetime_string, column.type_cast(datetime_string).strftime("%FT%T")
+ end
+ end
+
+ def test_type_cast_date
+ column = Column.new("field", nil, "date")
+ assert_equal nil, column.type_cast('')
+ assert_equal nil, column.type_cast(' ')
+
+ date_string = Time.now.utc.strftime("%F")
+ assert_equal date_string, column.type_cast(date_string).strftime("%F")
+ end
end
end
end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 7082d5dc86..cef08cd99c 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -38,7 +38,9 @@ ActiveRecord::Schema.define do
create_table :admin_users, :force => true do |t|
t.string :name
t.text :settings, :null => true
- t.text :preferences, :null => false, :default => ""
+ # MySQL does not allow default values for blobs. Fake it out with a
+ # big varchar below.
+ t.string :preferences, :null => false, :default => '', :limit => 1024
t.references :account
end
diff --git a/guides/rails_guides/textile_extensions.rb b/guides/rails_guides/textile_extensions.rb
index 4677fae504..0a002a785f 100644
--- a/guides/rails_guides/textile_extensions.rb
+++ b/guides/rails_guides/textile_extensions.rb
@@ -1,5 +1,11 @@
require 'active_support/core_ext/object/inclusion'
+module RedCloth::Formatters::HTML
+ def emdash(opts)
+ "--"
+ end
+end
+
module RailsGuides
module TextileExtensions
def notestuff(body)
diff --git a/guides/source/active_record_querying.textile b/guides/source/active_record_querying.textile
index f9dbaa1125..a9cb424eaa 100644
--- a/guides/source/active_record_querying.textile
+++ b/guides/source/active_record_querying.textile
@@ -99,9 +99,28 @@ SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
<tt>Model.find(primary_key)</tt> will raise an +ActiveRecord::RecordNotFound+ exception if no matching record is found.
+h5. +take+
+
+<tt>Model.take</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take</tt> returns +nil+ if no record is found and no exception will be raised.
+
+TIP: The retrieved record may vary depending on the database engine.
+
h5. +first+
-<tt>Model.first</tt> finds the first record matched by the supplied options, if any. For example:
+<tt>Model.first</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first
@@ -111,14 +130,14 @@ client = Client.first
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.first</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +last+
-<tt>Model.last</tt> finds the last record matched by the supplied options. For example:
+<tt>Model.last</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last
@@ -131,7 +150,7 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last</tt> returns +nil+ if no matching record is found. No exception will be raised.
+<tt>Model.last</tt> returns +nil+ if no matching record is found and no exception will be raised.
h5. +find_by+
@@ -148,12 +167,29 @@ Client.find_by first_name: 'Jon'
It is equivalent to writing:
<ruby>
-Client.where(first_name: 'Lifo').first
+Client.where(first_name: 'Lifo').take
</ruby>
+h5(#take_1). +take!+
+
+<tt>Model.take!</tt> retrieves a record without any implicit ordering. For example:
+
+<ruby>
+client = Client.take!
+# => #<Client id: 1, first_name: "Lifo">
+</ruby>
+
+The SQL equivalent of the above is:
+
+<sql>
+SELECT * FROM clients LIMIT 1
+</sql>
+
+<tt>Model.take!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
+
h5(#first_1). +first!+
-<tt>Model.first!</tt> finds the first record. For example:
+<tt>Model.first!</tt> finds the first record ordered by the primary key. For example:
<ruby>
client = Client.first!
@@ -163,14 +199,14 @@ client = Client.first!
The SQL equivalent of the above is:
<sql>
-SELECT * FROM clients LIMIT 1
+SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1
</sql>
-<tt>Model.first!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.first!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#last_1). +last!+
-<tt>Model.last!</tt> finds the last record. For example:
+<tt>Model.last!</tt> finds the last record ordered by the primary key. For example:
<ruby>
client = Client.last!
@@ -183,24 +219,24 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1
</sql>
-<tt>Model.last!</tt> raises +RecordNotFound+ if no matching record is found.
+<tt>Model.last!</tt> raises +ActiveRecord::RecordNotFound+ if no matching record is found.
h5(#find_by_1). +find_by!+
-<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +RecordNotFound+ if no matching record is found. For example:
+<tt>Model.find_by!</tt> finds the first record matching some conditions. It raises +ActiveRecord::RecordNotFound+ if no matching record is found. For example:
<ruby>
Client.find_by! first_name: 'Lifo'
# => #<Client id: 1, first_name: "Lifo">
Client.find_by! first_name: 'Jon'
-# => RecordNotFound
+# => ActiveRecord::RecordNotFound
</ruby>
It is equivalent to writing:
<ruby>
-Client.where(first_name: 'Lifo').first!
+Client.where(first_name: 'Lifo').take!
</ruby>
h4. Retrieving Multiple Objects