aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb29
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/base.rb98
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb36
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb108
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb19
-rw-r--r--activerecord/lib/active_record/named_scope.rb46
-rw-r--r--activerecord/lib/active_record/railtie.rb3
-rw-r--r--activerecord/lib/active_record/railties/databases.rake8
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb16
-rw-r--r--activerecord/lib/active_record/relation.rb21
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb5
17 files changed, 329 insertions, 137 deletions
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index 43f736c89c..aef99e3129 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
if attr_name == primary_key && attr_name != "id"
- define_read_method(:id, attr_name, columns_hash[attr_name])
+ define_read_method('id', attr_name, columns_hash[attr_name])
end
end
@@ -59,7 +59,9 @@ module ActiveRecord
end
# Define an attribute reader method. Cope with nil column.
- def define_read_method(symbol, attr_name, column)
+ # method_name is the same as attr_name except when a non-standard primary key is used,
+ # we still define #id as an accessor for the key
+ def define_read_method(method_name, attr_name, column)
cast_code = column.type_cast_code('v')
access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}"
@@ -70,12 +72,25 @@ module ActiveRecord
if cache_attribute?(attr_name)
access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})"
end
- if symbol =~ /^[a-zA-Z_]\w*[!?=]?$/
- generated_attribute_methods.module_eval("def _#{symbol}; #{access_code}; end; alias #{symbol} _#{symbol}", __FILE__, __LINE__)
+
+ # Where possible, generate the method by evalling a string, as this will result in
+ # faster accesses because it avoids the block eval and then string eval incurred
+ # by the second branch.
+ #
+ # The second, slower, branch is necessary to support instances where the database
+ # returns columns with extra stuff in (like 'my_column(omg)').
+ if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
+ generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
+ def _#{method_name}
+ #{access_code}
+ end
+
+ alias #{method_name} _#{method_name}
+ STR
else
generated_attribute_methods.module_eval do
- define_method("_#{symbol}") { eval(access_code) }
- alias_method(symbol, "_#{symbol}")
+ define_method("_#{method_name}") { eval(access_code) }
+ alias_method(method_name, "_#{method_name}")
end
end
end
@@ -85,7 +100,7 @@ module ActiveRecord
# "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
def read_attribute(attr_name)
if respond_to? "_#{attr_name}"
- send "_#{attr_name}"
+ send "_#{attr_name}" if @attributes.has_key?(attr_name.to_s)
else
_read_attribute attr_name
end
diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb
index 7661676f8c..c77a3ac145 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -10,7 +10,7 @@ module ActiveRecord
module ClassMethods
protected
def define_method_attribute=(attr_name)
- if attr_name =~ /^[a-zA-Z_]\w*[!?=]?$/
+ if attr_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__)
else
generated_attribute_methods.send(:define_method, "#{attr_name}=") do |new_value|
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 08a41e2d8b..4512e8c8ad 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1177,13 +1177,11 @@ MSG
Thread.current[:"#{self}_current_scope"] = scope
end
- # Implement this method in your model to set a default scope for all operations on
+ # Use this macro in your model to set a default scope for all operations on
# the model.
#
# class Person < ActiveRecord::Base
- # def self.default_scope
- # order('last_name, first_name')
- # end
+ # default_scope order('last_name, first_name')
# end
#
# Person.all # => SELECT * FROM people ORDER BY last_name, first_name
@@ -1192,40 +1190,59 @@ MSG
# applied while updating a record.
#
# class Article < ActiveRecord::Base
- # def self.default_scope
- # where(:published => true)
- # end
+ # default_scope where(:published => true)
# end
#
# Article.new.published # => true
# Article.create.published # => true
#
- # === Deprecation warning
+ # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
#
- # There is an alternative syntax as follows:
- #
- # class Person < ActiveRecord::Base
- # default_scope order('last_name, first_name')
+ # class Article < ActiveRecord::Base
+ # default_scope { where(:published_at => Time.now - 1.week) }
# end
#
- # This is now deprecated and will be removed in Rails 3.2.
+ # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
+ # macro, and it will be called when building the default scope.)
+ #
+ # If you need to do more complex things with a default scope, you can alternatively
+ # define it as a class method:
+ #
+ # class Article < ActiveRecord::Base
+ # def self.default_scope
+ # # Should return a scope, you can call 'super' here etc.
+ # end
+ # end
def default_scope(scope = {})
- ActiveSupport::Deprecation.warn <<-WARN
-Passing a hash or scope to default_scope is deprecated and will be removed in Rails 3.2. You should create a class method for your scope instead. For example, change this:
+ if default_scopes.length != 0
+ ActiveSupport::Deprecation.warn <<-WARN
+Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together:
-class Post < ActiveRecord::Base
+class Post < ActiveRecord::Base # Rails 3.1
+ default_scope where(:published => true)
+ default_scope where(:hidden => false)
+ # The default scope is now: where(:published => true, :hidden => false)
+end
+
+In Rails 3.2, the behavior will be changed to overwrite previous scopes:
+
+class Post < ActiveRecord::Base # Rails 3.2
default_scope where(:published => true)
+ default_scope where(:hidden => false)
+ # The default scope is now: where(:hidden => false)
end
-To this:
+If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.):
class Post < ActiveRecord::Base
def self.default_scope
- where(:published => true)
+ where(:published => true).where(:hidden => false)
end
end
-WARN
+ WARN
+ end
+ scope = Proc.new if block_given?
self.default_scopes = default_scopes.dup << scope
end
@@ -1238,6 +1255,8 @@ WARN
default_scopes.inject(relation) do |default_scope, scope|
if scope.is_a?(Hash)
default_scope.apply_finder_options(scope)
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
+ default_scope.merge(scope.call)
else
default_scope.merge(scope)
end
@@ -1621,10 +1640,49 @@ WARN
# user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
return unless new_attributes.is_a?(Hash)
+ if guard_protected_attributes
+ assign_attributes(new_attributes)
+ else
+ assign_attributes(new_attributes, :without_protection => true)
+ end
+ end
+
+ # Allows you to set all the attributes for a particular mass-assignment
+ # security scope by passing in a hash of attributes with keys matching
+ # the attribute names (which again matches the column names) and the scope
+ # name using the :as option.
+ #
+ # To bypass mass-assignment security you can use the :without_protection => true
+ # option.
+ #
+ # class User < ActiveRecord::Base
+ # attr_accessible :name
+ # attr_accessible :name, :is_admin, :as => :admin
+ # end
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true })
+ # user.name # => "Josh"
+ # user.is_admin? # => false
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
+ # user.name # => "Josh"
+ # user.is_admin? # => true
+ #
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :without_protection => true)
+ # user.name # => "Josh"
+ # user.is_admin? # => true
+ def assign_attributes(new_attributes, options = {})
attributes = new_attributes.stringify_keys
+ scope = options[:as] || :default
multi_parameter_attributes = []
- attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
+
+ unless options[:without_protection]
+ attributes = sanitize_for_mass_assignment(attributes, scope)
+ end
attributes.each do |k, v|
if k.include?("(")
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index a3082b8f01..70da9d5f1e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -55,9 +55,25 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [])
end
+ # Executes insert +sql+ statement in the context of this connection using
+ # +binds+ as the bind substitutes. +name+ is the logged along with
+ # the executed +sql+ statement.
+ def exec_insert(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
# Returns the last auto-generated ID from the affected table.
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
- insert_sql(sql, name, pk, id_value, sequence_name)
+ #
+ # +id_value+ will be returned unless the value is nil, in
+ # which case the database will attempt to calculate the last inserted
+ # id and return that value.
+ #
+ # If the next id was calculated in advance (as in Oracle), it should be
+ # passed in as +id_value+.
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
+ sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ value = exec_insert(sql, name, binds)
+ id_value || last_inserted_id(value)
end
# Executes the update statement and returns the number of rows affected.
@@ -271,10 +287,6 @@ module ActiveRecord
execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert'
end
- def null_insert_value
- Arel.sql 'DEFAULT'
- end
-
def empty_insert_statement_value
"VALUES(DEFAULT)"
end
@@ -364,6 +376,15 @@ module ActiveRecord
end
end
end
+
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ [sql, binds]
+ end
+
+ def last_inserted_id(result)
+ row = result.rows.first
+ row && row.first
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 325665dd87..3de850ec9e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -39,6 +39,42 @@ module ActiveRecord
end
end
+ # Cast a +value+ to a type that the database understands. For example,
+ # SQLite does not understand dates, so this method will convert a Date
+ # to a String.
+ def type_cast(value, column)
+ return value.id if value.respond_to?(:quoted_id)
+
+ case value
+ when String, ActiveSupport::Multibyte::Chars
+ value = value.to_s
+ return value unless column
+
+ case column.type
+ when :binary then value
+ when :integer then value.to_i
+ when :float then value.to_f
+ else
+ value
+ end
+
+ when true, false
+ if column && column.type == :integer
+ value ? 1 : 0
+ else
+ value ? 't' : 'f'
+ end
+ # BigDecimals need to be put in a non-normalized form and quoted.
+ when nil then nil
+ when BigDecimal then value.to_s('F')
+ when Numeric then value
+ when Date, Time then quoted_date(value)
+ when Symbol then value.to_s
+ else
+ YAML.dump(value)
+ end
+ end
+
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d24cce0a3c..468a2b106b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -223,7 +223,9 @@ module ActiveRecord
rescue Exception => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.debug message if @logger
- raise translate_exception(e, message)
+ exception = translate_exception(e, message)
+ exception.set_backtrace e.backtrace
+ raise exception
end
def translate_exception(e, message)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 1f7e527eeb..7ac72acd58 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -282,6 +282,17 @@ module ActiveRecord
end
alias :create :insert_sql
+ def exec_insert(sql, name, binds)
+ binds = binds.dup
+
+ # Pretend to support bind parameters
+ execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ end
+
+ def last_inserted_id(result)
+ @connection.last_id
+ end
+
def update_sql(sql, name = nil)
super
@connection.affected_rows
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index d88075fdea..2c05ff21f9 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -208,16 +208,18 @@ module ActiveRecord
true
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
+ # Returns +true+, since this connection adapter supports prepared statement
+ # caching.
def supports_statement_cache?
true
end
+ # Returns true.
def supports_migrations? #:nodoc:
true
end
+ # Returns true.
def supports_primary_key? #:nodoc:
true
end
@@ -244,6 +246,12 @@ module ActiveRecord
end
end
+ def type_cast(value, column)
+ return super unless value == true || value == false
+
+ value ? 1 : 0
+ end
+
def quote_column_name(name) #:nodoc:
@quoted_column_names[name] ||= "`#{name}`"
end
@@ -302,6 +310,8 @@ module ActiveRecord
connect
end
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
@connection.close rescue nil
end
@@ -324,6 +334,7 @@ module ActiveRecord
rows
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.values.each do |cache|
cache[:stmt].close
@@ -403,7 +414,7 @@ module ActiveRecord
end
stmt.execute(*binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
})
if metadata = stmt.result_metadata
cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
@@ -421,8 +432,8 @@ module ActiveRecord
end
end
- def exec_insert(sql, name, binds)
- exec_query(sql, name, binds)
+ def last_inserted_id(result)
+ @connection.insert_id
end
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
@@ -548,6 +559,10 @@ module ActiveRecord
end
end
+ # Drops a MySQL database.
+ #
+ # Example:
+ # drop_database 'sebastian_development'
def drop_database(name) #:nodoc:
execute "DROP DATABASE IF EXISTS `#{name}`"
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 05f0e5ebe1..e2b9a5d0d9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -1,6 +1,9 @@
require 'active_record/connection_adapters/abstract_adapter'
require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/object/blank'
+
+# Make sure we're using pg high enough for PGResult#values
+gem 'pg', '~> 0.11'
require 'pg'
module ActiveRecord
@@ -211,8 +214,8 @@ module ActiveRecord
ADAPTER_NAME
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
+ # Returns +true+, since this connection adapter supports prepared statement
+ # caching.
def supports_statement_cache?
true
end
@@ -237,6 +240,7 @@ module ActiveRecord
@local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"]
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.each_value do |value|
@connection.query "DEALLOCATE #{value}"
@@ -275,7 +279,8 @@ module ActiveRecord
super
end
- # Close the connection.
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
clear_cache!
@connection.close rescue nil
@@ -360,6 +365,18 @@ module ActiveRecord
end
end
+ def type_cast(value, column)
+ return super unless column
+
+ case value
+ when String
+ return super unless 'bytea' == column.sql_type
+ { :value => value, :format => 1 }
+ else
+ super
+ end
+ end
+
# Quotes strings for use in SQL input.
def quote_string(s) #:nodoc:
@connection.escape(s)
@@ -448,42 +465,43 @@ module ActiveRecord
# create a 2D array representing the result set
def result_as_array(res) #:nodoc:
# check if we have any binary column and if they need escaping
- unescape_col = []
- res.nfields.times do |j|
- unescape_col << res.ftype(j)
+ ftypes = Array.new(res.nfields) do |i|
+ [i, res.ftype(i)]
end
- ary = []
- res.ntuples.times do |i|
- ary << []
- res.nfields.times do |j|
- data = res.getvalue(i,j)
- case unescape_col[j]
-
- # unescape string passed BYTEA field (OID == 17)
- when BYTEA_COLUMN_TYPE_OID
- data = unescape_bytea(data) if String === data
-
- # If this is a money type column and there are any currency symbols,
- # then strip them off. Indeed it would be prettier to do this in
- # PostgreSQLColumn.string_to_decimal but would break form input
- # fields that call value_before_type_cast.
- when MONEY_COLUMN_TYPE_OID
- # Because money output is formatted according to the locale, there are two
- # cases to consider (note the decimal separators):
- # (1) $12,345,678.12
- # (2) $12.345.678,12
- case data
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
- data.gsub!(/[^-\d.]/, '')
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
- data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
- end
+ rows = res.values
+ return rows unless ftypes.any? { |_, x|
+ x == BYTEA_COLUMN_TYPE_OID || x == MONEY_COLUMN_TYPE_OID
+ }
+
+ typehash = ftypes.group_by { |_, type| type }
+ binaries = typehash[BYTEA_COLUMN_TYPE_OID] || []
+ monies = typehash[MONEY_COLUMN_TYPE_OID] || []
+
+ rows.each do |row|
+ # unescape string passed BYTEA field (OID == 17)
+ binaries.each do |index, _|
+ row[index] = unescape_bytea(row[index])
+ end
+
+ # If this is a money type column and there are any currency symbols,
+ # then strip them off. Indeed it would be prettier to do this in
+ # PostgreSQLColumn.string_to_decimal but would break form input
+ # fields that call value_before_type_cast.
+ monies.each do |index, _|
+ data = row[index]
+ # Because money output is formatted according to the locale, there are two
+ # cases to consider (note the decimal separators):
+ # (1) $12,345,678.12
+ # (2) $12.345.678,12
+ case data
+ when /^-?\D+[\d,]+\.\d{2}$/ # (1)
+ data.gsub!(/[^-\d.]/, '')
+ when /^-?\D+[\d.]+,\d{2}$/ # (2)
+ data.gsub!(/[^-\d,]/, '').sub!(/,/, '.')
end
- ary[i] << data
end
end
- return ary
end
@@ -530,7 +548,7 @@ module ActiveRecord
# Clear the queue
@connection.get_last_result
@connection.send_query_prepared(key, binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
})
@connection.block
result = @connection.get_last_result
@@ -540,8 +558,16 @@ module ActiveRecord
end
end
- def exec_insert(sql, name, binds)
- exec_query(sql, name, binds)
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ unless pk
+ _, table = extract_schema_and_table(sql.split(" ", 4)[2])
+
+ pk = primary_key(table)
+ end
+
+ sql = "#{sql} RETURNING #{quote_column_name(pk)}" if pk
+
+ [sql, binds]
end
# Executes an UPDATE query and returns the number of affected tuples.
@@ -617,7 +643,7 @@ module ActiveRecord
execute "CREATE DATABASE #{quote_table_name(name)}#{option_string}"
end
- # Drops a PostgreSQL database
+ # Drops a PostgreSQL database.
#
# Example:
# drop_database 'matt_development'
@@ -909,10 +935,7 @@ module ActiveRecord
order_columns.delete_if { |c| c.blank? }
order_columns = order_columns.zip((0...order_columns.size).to_a).map { |s,i| "#{s} AS alias_#{i}" }
- # Return a DISTINCT ON() clause that's distinct on the columns we want but includes
- # all the required columns for the ORDER BY to work properly.
- sql = "DISTINCT ON (#{columns}) #{columns}, "
- sql << order_columns * ', '
+ "DISTINCT #{columns}, #{order_columns * ', '}"
end
protected
@@ -1069,4 +1092,3 @@ module ActiveRecord
end
end
end
-
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index 8ef286b473..ed5006dcec 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -66,16 +66,18 @@ module ActiveRecord
sqlite_version >= '3.6.8'
end
- # Returns +true+ when the connection adapter supports prepared statement
- # caching, otherwise returns +false+
+ # Returns true, since this connection adapter supports prepared statement
+ # caching.
def supports_statement_cache?
true
end
+ # Returns true.
def supports_migrations? #:nodoc:
true
end
+ # Returns true.
def supports_primary_key? #:nodoc:
true
end
@@ -88,12 +90,15 @@ module ActiveRecord
sqlite_version >= '3.1.6'
end
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
super
clear_cache!
@connection.close rescue nil
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.clear
end
@@ -165,7 +170,7 @@ module ActiveRecord
cols = cache[:cols] ||= stmt.columns
stmt.reset!
stmt.bind_params binds.map { |col, val|
- col ? col.type_cast(val) : val
+ type_cast(val, col)
}
end
@@ -173,8 +178,8 @@ module ActiveRecord
end
end
- def exec_insert(sql, name, binds)
- exec_query(sql, name, binds)
+ def last_inserted_id(result)
+ @connection.last_insert_row_id
end
def execute(sql, name = nil) #:nodoc:
@@ -341,10 +346,6 @@ module ActiveRecord
alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
end
- def null_insert_value
- Arel.sql 'NULL'
- end
-
def empty_insert_statement_value
"VALUES(NULL)"
end
diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb
index f1df04950b..588f52be44 100644
--- a/activerecord/lib/active_record/named_scope.rb
+++ b/activerecord/lib/active_record/named_scope.rb
@@ -82,16 +82,18 @@ module ActiveRecord
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
# only shirts.
#
- # If you need to pass parameters to a scope, define it as a normal method:
+ # Named \scopes can also be procedural:
#
# class Shirt < ActiveRecord::Base
- # def self.colored(color)
- # where(:color => color)
- # end
+ # scope :colored, lambda { |color| where(:color => color) }
# end
#
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
#
+ # On Ruby 1.9 you can use the 'stabby lambda' syntax:
+ #
+ # scope :colored, ->(color) { where(:color => color) }
+ #
# Note that scopes defined with \scope will be evaluated when they are defined, rather than
# when they are used. For example, the following would be incorrect:
#
@@ -101,13 +103,11 @@ module ActiveRecord
#
# The example above would be 'frozen' to the <tt>Time.now</tt> value when the <tt>Post</tt>
# class was defined, and so the resultant SQL query would always be the same. The correct
- # way to do this would be via a class method, which will re-evaluate the scope each time
+ # way to do this would be via a lambda, which will re-evaluate the scope each time
# it is called:
#
# class Post < ActiveRecord::Base
- # def self.recent
- # where('published_at >= ?', Time.now - 1.week)
- # end
+ # scope :recent, lambda { where('published_at >= ?', Time.now - 1.week) }
# end
#
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
@@ -120,18 +120,6 @@ module ActiveRecord
# end
# end
#
- # The above could also be written as a class method like so:
- #
- # class Shirt < ActiveRecord::Base
- # def self.red
- # where(:color => 'red').extending do
- # def dom_id
- # 'red_shirts'
- # end
- # end
- # end
- # end
- #
# Scopes can also be used while creating/building a record.
#
# class Article < ActiveRecord::Base
@@ -168,24 +156,6 @@ module ActiveRecord
valid_scope_name?(name)
extension = Module.new(&Proc.new) if block_given?
- if !scope_options.is_a?(Relation) && scope_options.respond_to?(:call)
- ActiveSupport::Deprecation.warn <<-WARN
-Passing a proc (or other object that responds to #call) to scope is deprecated. If you need your scope to be lazily evaluated, or takes parameters, please define it as a normal class method instead. For example, change this:
-
-class Post < ActiveRecord::Base
- scope :unpublished, lambda { where('published_at > ?', Time.now) }
-end
-
-To this:
-
-class Post < ActiveRecord::Base
- def self.unpublished
- where('published_at > ?', Time.now)
- end
-end
- WARN
- end
-
scope_proc = lambda do |*args|
options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index cace6f0cc0..d38588519b 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -50,6 +50,9 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
+ if app.config.active_record.delete(:whitelist_attributes)
+ attr_accessible(nil)
+ end
app.config.active_record.each do |k,v|
send "#{k}=", v
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 6b3c38cb58..a49f940e5b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -70,7 +70,13 @@ db_namespace = namespace :db do
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
creation_options = {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
- error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
+ if config['adapter'] =~ /jdbc/
+ #FIXME After Jdbcmysql gives this class
+ require 'active_record/railties/jdbcmysql_error'
+ error_class = ArJdbcMySQL::Error
+ else
+ error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
+ end
access_denied_error = 1045
begin
ActiveRecord::Base.establish_connection(config.merge('database' => nil))
diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
new file mode 100644
index 0000000000..6b9af2a0cb
--- /dev/null
+++ b/activerecord/lib/active_record/railties/jdbcmysql_error.rb
@@ -0,0 +1,16 @@
+#FIXME Remove if ArJdbcMysql will give.
+module ArJdbcMySQL
+ class Error < StandardError
+ attr_accessor :error_number, :sql_state
+
+ def initialize msg
+ super
+ @error_number = nil
+ @sql_state = nil
+ end
+
+ # Mysql gem compatibility
+ alias_method :errno, :error_number
+ alias_method :error, :message
+ end
+end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 490360ccb5..8e5f66ec1d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -48,17 +48,30 @@ module ActiveRecord
im = arel.create_insert
im.into @table
+ conn = @klass.connection
+
+ substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
+ binds = substitutes.map do |arel_attr, value|
+ [@klass.columns_hash[arel_attr.name], value]
+ end
+
+ substitutes.each_with_index do |tuple, i|
+ tuple[1] = conn.substitute_at(binds[i][0], i)
+ end
+
if values.empty? # empty insert
- im.values = im.create_values [connection.null_insert_value], []
+ im.values = Arel.sql(connection.empty_insert_statement_value)
else
- im.insert values
+ im.insert substitutes
end
- @klass.connection.insert(
+ conn.insert(
im.to_sql,
'SQL',
primary_key,
- primary_key_value)
+ primary_key_value,
+ nil,
+ binds)
end
def new(*args, &block)
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index aae257a0e7..a3d4b7f45a 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -246,6 +246,8 @@ module ActiveRecord
orders = relation.order_values
values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
+ relation = relation.dup
+
ids_array = relation.select(values).collect {|row| row[primary_key]}
ids_array.empty? ? raise(ThrowResult) : table[primary_key].in(ids_array)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index d1225a9ed9..4db4105389 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -56,8 +56,9 @@ module ActiveRecord
column = klass.columns_hash[attribute.to_s]
value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text?
- if !options[:case_sensitive] && column.text?
- relation = table[attribute].matches(value)
+ if !options[:case_sensitive] && value && column.text?
+ # will use SQL LOWER function before comparison
+ relation = table[attribute].lower.eq(table.lower(value))
else
value = klass.connection.case_sensitive_modifier(value)
relation = table[attribute].eq(value)