aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
authorGonçalo Silva <goncalossilva@gmail.com>2011-05-08 03:54:55 +0100
committerGonçalo Silva <goncalossilva@gmail.com>2011-05-08 03:54:55 +0100
commitaec7456f81985b88d6d604679d754636183b5b3a (patch)
tree798fcab8ae0c6e2c8c5a63e1f39308ff4bb5a20c /activerecord/lib/active_record
parent1c2b2233c3a7ec76c0a0eddf5b8be45c489be133 (diff)
parent70f9558d0e4b8e605576693cbb489caa5cf6a2bc (diff)
downloadrails-aec7456f81985b88d6d604679d754636183b5b3a.tar.gz
rails-aec7456f81985b88d6d604679d754636183b5b3a.tar.bz2
rails-aec7456f81985b88d6d604679d754636183b5b3a.zip
Merge branch 'master' of https://github.com/rails/rails into performance_test
Conflicts: activesupport/lib/active_support/testing/performance.rb
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb20
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb9
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb16
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb2
-rw-r--r--activerecord/lib/active_record/base.rb284
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb58
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb122
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb236
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb28
-rw-r--r--activerecord/lib/active_record/fixtures.rb573
-rw-r--r--activerecord/lib/active_record/identity_map.rb48
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb9
-rw-r--r--activerecord/lib/active_record/named_scope.rb46
-rw-r--r--activerecord/lib/active_record/observer.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb23
-rw-r--r--activerecord/lib/active_record/query_cache.rb28
-rw-r--r--activerecord/lib/active_record/railtie.rb13
-rw-r--r--activerecord/lib/active_record/railties/console_sandbox.rb6
-rw-r--r--activerecord/lib/active_record/railties/databases.rake186
-rw-r--r--activerecord/lib/active_record/railties/jdbcmysql_error.rb16
-rw-r--r--activerecord/lib/active_record/relation.rb10
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb13
-rw-r--r--activerecord/lib/active_record/validations.rb6
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb5
-rw-r--r--activerecord/lib/active_record/version.rb2
36 files changed, 1091 insertions, 800 deletions
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 33a184d48d..6cdec8c487 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -93,20 +93,20 @@ module ActiveRecord
first_or_last(:last, *args)
end
- def build(attributes = {}, &block)
- build_or_create(attributes, :build, &block)
+ def build(attributes = {}, options = {}, &block)
+ build_or_create(:build, attributes, options, &block)
end
- def create(attributes = {}, &block)
+ def create(attributes = {}, options = {}, &block)
unless owner.persisted?
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
end
- build_or_create(attributes, :create, &block)
+ build_or_create(:create, attributes, options, &block)
end
- def create!(attrs = {}, &block)
- record = create(attrs, &block)
+ def create!(attrs = {}, options = {}, &block)
+ record = create(attrs, options, &block)
Array.wrap(record).each(&:save!)
record
end
@@ -403,9 +403,9 @@ module ActiveRecord
end + existing
end
- def build_or_create(attributes, method)
+ def build_or_create(method, attributes, options)
records = Array.wrap(attributes).map do |attrs|
- record = build_record(attrs)
+ record = build_record(attrs, options)
add_to_target(record) do
yield(record) if block_given?
@@ -421,8 +421,8 @@ module ActiveRecord
raise NotImplementedError
end
- def build_record(attributes)
- reflection.build_association(scoped.scope_for_create.merge(attributes))
+ def build_record(attributes, options)
+ reflection.build_association(scoped.scope_for_create.merge(attributes), options)
end
def delete_or_destroy(records, method)
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 388173c1fb..adfc71d435 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -64,9 +64,12 @@ module ActiveRecord
def method_missing(method, *args, &block)
match = DynamicFinderMatch.match(method)
- if match && match.creator?
- attributes = match.attribute_names
- return send(:"find_by_#{attributes.join('_and_')}", *args) || create(Hash[attributes.zip(args)])
+ if match && match.instantiator?
+ record = send(:find_or_instantiator_by_attributes, match, match.attribute_names, *args) do |r|
+ @association.send :set_owner_attributes, r
+ @association.send :add_to_target, r
+ yield(r) if block_given?
+ end
end
if target.respond_to?(method) || (!@association.klass.respond_to?(method) && Class.respond_to?(method))
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 9d2b29685b..7708228d23 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -60,10 +60,10 @@ module ActiveRecord
through_record
end
- def build_record(attributes)
+ def build_record(attributes, options = {})
ensure_not_nested
- record = super(attributes)
+ record = super(attributes, options)
inverse = source_reflection.inverse_of
if inverse
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 0a666598ed..c32753782f 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -91,12 +91,12 @@ module ActiveRecord
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
- relation.from(join(table, constraint))
-
unless conditions[i].empty?
- relation.where(sanitize(conditions[i], table))
+ constraint = constraint.and(sanitize(conditions[i], table))
end
+ relation.from(join(table, constraint))
+
# The current table in this iteration becomes the foreign table in the next
foreign_table = table
end
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 4edbe216be..ea4d73d414 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -17,16 +17,16 @@ module ActiveRecord
replace(record)
end
- def create(attributes = {})
- new_record(:create, attributes)
+ def create(attributes = {}, options = {})
+ new_record(:create, attributes, options)
end
- def create!(attributes = {})
- build(attributes).tap { |record| record.save! }
+ def create!(attributes = {}, options = {})
+ build(attributes, options).tap { |record| record.save! }
end
- def build(attributes = {})
- new_record(:build, attributes)
+ def build(attributes = {}, options = {})
+ new_record(:build, attributes, options)
end
private
@@ -44,9 +44,9 @@ module ActiveRecord
replace(record)
end
- def new_record(method, attributes)
+ def new_record(method, attributes, options)
attributes = scoped.scope_for_create.merge(attributes || {})
- record = reflection.send("#{method}_association", attributes)
+ record = reflection.send("#{method}_association", attributes, options)
set_new_record(record)
record
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index a248eb3a7b..aef99e3129 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -79,7 +79,7 @@ module ActiveRecord
#
# 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 =~ /^[a-zA-Z_]\w*[!?=]?$/
+ if method_name =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__
def _#{method_name}
#{access_code}
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..78318b1be0 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -83,7 +83,7 @@ module ActiveRecord #:nodoc:
#
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
- # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
# before inserting them in the query, which will ensure that an attacker can't escape the
# query and fake the login (or worse).
@@ -406,10 +406,10 @@ module ActiveRecord #:nodoc:
##
# :singleton-method:
# Specifies the format to use when dumping the database schema with Rails'
- # Rakefile. If :sql, the schema is dumped as (potentially database-
- # specific) SQL statements. If :ruby, the schema is dumped as an
+ # Rakefile. If :sql, the schema is dumped as (potentially database-
+ # specific) SQL statements. If :ruby, the schema is dumped as an
# ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
+ # supports migrations. Use :ruby if you want to have different database
# adapters for, e.g., your development and test environments.
cattr_accessor :schema_format , :instance_writer => false
@@schema_format = :ruby
@@ -443,17 +443,17 @@ module ActiveRecord #:nodoc:
delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
- # Executes a custom SQL query against your database and returns all the results. The results will
+ # Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
- # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
+ # this method from. If you call <tt>Product.find_by_sql</tt> then the results will be returned in
# a Product object with the attributes you specified in the SQL query.
#
# If you call a complicated SQL query which spans multiple tables the columns specified by the
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
# table.
#
- # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
- # no database agnostic conversions performed. This should be a last resort because using, for example,
+ # The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
+ # no database agnostic conversions performed. This should be a last resort because using, for example,
# MySQL specific terms will lock you to using that particular database engine or require you to
# change your call if you switch engines.
#
@@ -472,13 +472,22 @@ module ActiveRecord #:nodoc:
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
- # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
+ # The +attributes+ parameter can be either be a Hash or an Array of Hashes. These Hashes describe the
# attributes on the objects that are to be created.
#
+ # +create+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
# ==== Examples
# # Create a single new object
# User.create(:first_name => 'Jamie')
#
+ # # Create a single new object using the :admin mass-assignment security scope
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Create a single new object bypassing mass-assignment security
+ # User.create({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ #
# # Create an Array of new objects
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
#
@@ -491,11 +500,11 @@ module ActiveRecord #:nodoc:
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
# u.is_admin = false
# end
- def create(attributes = nil, &block)
+ def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
- attributes.collect { |attr| create(attr, &block) }
+ attributes.collect { |attr| create(attr, options, &block) }
else
- object = new(attributes)
+ object = new(attributes, options)
yield(object) if block_given?
object.save
object
@@ -504,7 +513,7 @@ module ActiveRecord #:nodoc:
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# The use of this method should be restricted to complicated SQL queries that can't be executed
- # using the ActiveRecord::Calculations class methods. Look into those before using this.
+ # using the ActiveRecord::Calculations class methods. Look into those before using this.
#
# ==== Parameters
#
@@ -581,7 +590,7 @@ module ActiveRecord #:nodoc:
# invoice/lineitem.rb Invoice::Lineitem lineitems
#
# Additionally, the class-level +table_name_prefix+ is prepended and the
- # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
+ # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
# the table name guess for an Invoice class becomes "myapp_invoices".
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
#
@@ -615,7 +624,7 @@ module ActiveRecord #:nodoc:
@inheritance_column ||= "type"
end
- # Lazy-set the sequence name to the connection's default. This method
+ # Lazy-set the sequence name to the connection's default. This method
# is only ever called once since set_sequence_name overrides it.
def sequence_name #:nodoc:
reset_sequence_name
@@ -627,7 +636,7 @@ module ActiveRecord #:nodoc:
default
end
- # Sets the table name. If the value is nil or false then the value returned by the given
+ # Sets the table name. If the value is nil or false then the value returned by the given
# block is used.
#
# class Project < ActiveRecord::Base
@@ -821,6 +830,10 @@ module ActiveRecord #:nodoc:
@symbolized_base_class ||= base_class.to_s.to_sym
end
+ def symbolized_sti_name
+ @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class
+ end
+
# Returns the base AR subclass that this class descends from. If A
# extends AR::Base, A.base_class will return A. If B descends from A
# through some arbitrarily deep hierarchy, B.base_class will return A.
@@ -1077,7 +1090,7 @@ module ActiveRecord #:nodoc:
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
#
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
- # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
+ # problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
# array of strings format for your joins.
#
# class Article < ActiveRecord::Base
@@ -1177,56 +1190,54 @@ 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
+ # class Article < ActiveRecord::Base
+ # default_scope where(:published => true)
# end
#
- # Person.all # => SELECT * FROM people ORDER BY last_name, first_name
+ # Article.all # => SELECT * FROM articles WHERE published = true
#
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
# applied while updating a record.
#
- # class Article < ActiveRecord::Base
- # def self.default_scope
- # where(:published => true)
- # end
- # 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:
+ #
+ # class Article < ActiveRecord::Base
+ # default_scope { where(:published_at => Time.now - 1.week) }
+ # end
+ #
+ # (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.)
#
- # There is an alternative syntax as follows:
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
+ # be merged together:
#
- # class Person < ActiveRecord::Base
- # default_scope order('last_name, first_name')
+ # class Article < ActiveRecord::Base
+ # default_scope where(:published => true)
+ # default_scope where(:rating => 'G')
# end
#
- # This is now deprecated and will be removed in Rails 3.2.
+ # Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
+ #
+ # This is also the case with inheritance and module includes where the parent or module
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
+ #
+ # 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:
-
-class Post < ActiveRecord::Base
- default_scope where(:published => true)
-end
-
-To this:
-
-class Post < ActiveRecord::Base
- def self.default_scope
- where(:published => true)
- end
-end
-WARN
-
- self.default_scopes = default_scopes.dup << scope
+ scope = Proc.new if block_given?
+ self.default_scopes = default_scopes + [scope]
end
def build_default_scope #:nodoc:
@@ -1238,6 +1249,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
@@ -1381,7 +1394,7 @@ WARN
end.join(', ')
end
- # Accepts an array of conditions. The array has each value
+ # Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
def sanitize_sql_array(ary)
@@ -1465,7 +1478,20 @@ WARN
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
# In both instances, valid attribute keys are determined by the column names of the associated table --
# hence you can't have attributes that aren't part of the table columns.
- def initialize(attributes = nil)
+ #
+ # +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
+ # in the +options+ parameter.
+ #
+ # ==== Examples
+ # # Instantiates a single new object
+ # User.new(:first_name => 'Jamie')
+ #
+ # # Instantiates a single new object using the :admin mass-assignment security scope
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
+ #
+ # # Instantiates a single new object bypassing mass-assignment security
+ # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
+ def initialize(attributes = nil, options = {})
@attributes = attributes_from_column_definition
@association_cache = {}
@aggregation_cache = {}
@@ -1481,7 +1507,8 @@ WARN
set_serialized_attributes
populate_with_current_scope_attributes
- self.attributes = attributes unless attributes.nil?
+
+ assign_attributes(attributes, options) if attributes
result = yield self if block_given?
run_callbacks :initialize
@@ -1489,7 +1516,7 @@ WARN
end
# Populate +coder+ with attributes about this record that should be
- # serialized. The structure of +coder+ defined in this method is
+ # serialized. The structure of +coder+ defined in this method is
# guaranteed to match the structure of +coder+ passed to the +init_with+
# method.
#
@@ -1504,8 +1531,8 @@ WARN
coder['attributes'] = attributes
end
- # Initialize an empty model object from +coder+. +coder+ must contain
- # the attributes necessary for initializing an empty model object. For
+ # Initialize an empty model object from +coder+. +coder+ must contain
+ # the attributes necessary for initializing an empty model object. For
# example:
#
# class Post < ActiveRecord::Base
@@ -1602,11 +1629,11 @@ WARN
# Allows you to set all the attributes at once by passing in a hash with keys
# matching the attribute names (which again matches the column names).
#
- # If +guard_protected_attributes+ is true (the default), then sensitive
- # attributes can be protected from this form of mass-assignment by using
- # the +attr_protected+ macro. Or you can alternatively specify which
- # attributes *can* be accessed with the +attr_accessible+ macro. Then all the
- # attributes not included in that won't be allowed to be mass-assigned.
+ # If any attributes are protected by either +attr_protected+ or
+ # +attr_accessible+ then only settable attributes will be assigned.
+ #
+ # The +guard_protected_attributes+ argument is now deprecated, use
+ # the +assign_attributes+ method if you want to bypass mass-assignment security.
#
# class User < ActiveRecord::Base
# attr_protected :is_admin
@@ -1616,15 +1643,59 @@ WARN
# user.attributes = { :username => 'Phusion', :is_admin => true }
# user.username # => "Phusion"
# user.is_admin? # => false
+ def attributes=(new_attributes, guard_protected_attributes = nil)
+ unless guard_protected_attributes.nil?
+ message = "the use of 'guard_protected_attributes' will be removed from the next major release of rails, " +
+ "if you want to bypass mass-assignment security then look into using assign_attributes"
+ ActiveSupport::Deprecation.warn(message)
+ end
+
+ return unless new_attributes.is_a?(Hash)
+
+ guard_protected_attributes ||= true
+ 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.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
+ # user = User.new
+ # user.assign_attributes({ :name => 'Josh', :is_admin => true }, :as => :admin)
+ # user.name # => "Josh"
# user.is_admin? # => true
- def attributes=(new_attributes, guard_protected_attributes = true)
- return unless new_attributes.is_a?(Hash)
+ #
+ # 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?("(")
@@ -1876,32 +1947,9 @@ WARN
errors = []
callstack.each do |name, values_with_empty_parameters|
begin
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
- # in order to allow a date to be set without a year, we must keep the empty values.
- # Otherwise, we wouldn't be able to distinguish it from a date with an empty day.
- values = values_with_empty_parameters.reject { |v| v.nil? }
-
- if values.empty?
- send(name + "=", nil)
- else
-
- value = if Time == klass
- instantiate_time_object(name, values)
- elsif Date == klass
- begin
- values = values_with_empty_parameters.collect do |v| v.nil? ? 1 : v end
- Date.new(*values)
- rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
- instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
- end
- else
- klass.new(*values)
- end
-
- send(name + "=", value)
- end
+ send(name + "=", read_value_from_parameter(name, values_with_empty_parameters))
rescue => ex
- errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name}", ex, name)
end
end
unless errors.empty?
@@ -1909,19 +1957,65 @@ WARN
end
end
+ def read_value_from_parameter(name, values_hash_from_param)
+ klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ if values_hash_from_param.values.all?{|v|v.nil?}
+ nil
+ elsif klass == Time
+ read_time_parameter_value(name, values_hash_from_param)
+ elsif klass == Date
+ read_date_parameter_value(name, values_hash_from_param)
+ else
+ read_other_parameter_value(klass, name, values_hash_from_param)
+ end
+ end
+
+ def read_time_parameter_value(name, values_hash_from_param)
+ # If Date bits were not provided, error
+ raise "Missing Parameter" if [1,2,3].any?{|position| !values_hash_from_param.has_key?(position)}
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param, 6)
+ set_values = (1..max_position).collect{|position| values_hash_from_param[position] }
+ # If Date bits were provided but blank, then default to 1
+ # If Time bits are not there, then default to 0
+ [1,1,1,0,0,0].each_with_index{|v,i| set_values[i] = set_values[i].blank? ? v : set_values[i]}
+ instantiate_time_object(name, set_values)
+ end
+
+ def read_date_parameter_value(name, values_hash_from_param)
+ set_values = (1..3).collect{|position| values_hash_from_param[position].blank? ? 1 : values_hash_from_param[position]}
+ begin
+ Date.new(*set_values)
+ rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
+ instantiate_time_object(name, set_values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
+ end
+ end
+
+ def read_other_parameter_value(klass, name, values_hash_from_param)
+ max_position = extract_max_param_for_multiparameter_attributes(values_hash_from_param)
+ values = (1..max_position).collect do |position|
+ raise "Missing Parameter" if !values_hash_from_param.has_key?(position)
+ values_hash_from_param[position]
+ end
+ klass.new(*values)
+ end
+
+ def extract_max_param_for_multiparameter_attributes(values_hash_from_param, upper_cap = 100)
+ [values_hash_from_param.keys.max,upper_cap].min
+ end
+
def extract_callstack_for_multiparameter_attributes(pairs)
attributes = { }
for pair in pairs
multiparameter_name, value = pair
attribute_name = multiparameter_name.split("(").first
- attributes[attribute_name] = [] unless attributes.include?(attribute_name)
+ attributes[attribute_name] = {} unless attributes.include?(attribute_name)
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
- attributes[attribute_name] << [ find_parameter_position(multiparameter_name), parameter_value ]
+ attributes[attribute_name][find_parameter_position(multiparameter_name)] ||= parameter_value
end
- attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
+ attributes
end
def type_cast_attribute_value(multiparameter_name, value)
@@ -1929,7 +2023,7 @@ WARN
end
def find_parameter_position(multiparameter_name)
- multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first.to_i
end
# Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index b4db1eed18..6f21cea288 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -113,7 +113,7 @@ module ActiveRecord
end
end
- # A cached lookup for table existence
+ # A cached lookup for table existence.
def table_exists?(name)
return true if @tables.key? name
@@ -135,7 +135,7 @@ module ActiveRecord
@tables.clear
end
- # Clear out internal caches for table with +table_name+
+ # Clear out internal caches for table with +table_name+.
def clear_table_cache!(table_name)
@columns.delete table_name
@columns_hash.delete table_name
@@ -193,7 +193,7 @@ module ActiveRecord
@connections = []
end
- # Clears the cache which maps classes
+ # Clears the cache which maps classes.
def clear_reloadable_connections!
@reserved_connections.each do |name, conn|
checkin conn
@@ -365,7 +365,7 @@ module ActiveRecord
@connection_pools.each_value {|pool| pool.release_connection }
end
- # Clears the cache which maps classes
+ # Clears the cache which maps classes.
def clear_reloadable_connections!
@connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
index 29ac9341ec..30ccb8f0a4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_limits.rb
@@ -2,52 +2,53 @@ module ActiveRecord
module ConnectionAdapters # :nodoc:
module DatabaseLimits
- # the maximum length of a table alias
+ # Returns the maximum length of a table alias.
def table_alias_length
255
end
- # the maximum length of a column name
+ # Returns the maximum length of a column name.
def column_name_length
64
end
- # the maximum length of a table name
+ # Returns the maximum length of a table name.
def table_name_length
64
end
- # the maximum length of an index name
+ # Returns the maximum length of an index name.
def index_name_length
64
end
- # the maximum number of columns per table
+ # Returns the maximum number of columns per table.
def columns_per_table
1024
end
- # the maximum number of indexes per table
+ # Returns the maximum number of indexes per table.
def indexes_per_table
16
end
- # the maximum number of columns in a multicolumn index
+ # Returns the maximum number of columns in a multicolumn index.
def columns_per_multicolumn_index
16
end
- # the maximum number of elements in an IN (x,y,z) clause. nil means no limit
+ # Returns the maximum number of elements in an IN (x,y,z) clause.
+ # nil means no limit.
def in_clause_length
nil
end
- # the maximum length of an SQL query
+ # Returns the maximum length of an SQL query.
def sql_query_length
1048575
end
- # maximum number of joins in a single query
+ # Returns maximum number of joins in a single query.
def joins_per_query
256
end
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 6d9b5c7b32..3045e30407 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -55,6 +55,27 @@ 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
+
+ # Executes delete +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_delete(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
+ # Executes update +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_update(sql, name, binds)
+ exec_query(sql, name, binds)
+ end
+
# Returns the last auto-generated ID from the affected table.
#
# +id_value+ will be returned unless the value is nil, in
@@ -70,13 +91,13 @@ module ActiveRecord
end
# Executes the update statement and returns the number of rows affected.
- def update(sql, name = nil)
- update_sql(sql, name)
+ def update(sql, name = nil, binds = [])
+ exec_update(sql, name, binds)
end
# Executes the delete statement and returns the number of rows affected.
- def delete(sql, name = nil)
- delete_sql(sql, name)
+ def delete(sql, name = nil, binds = [])
+ exec_delete(sql, name, binds)
end
# Checks whether there is currently no transaction active. This is done
@@ -280,10 +301,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
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 1db397f584..093c30aa42 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -29,6 +29,14 @@ module ActiveRecord
@query_cache_enabled = old
end
+ def enable_query_cache!
+ @query_cache_enabled = true
+ end
+
+ def disable_query_cache!
+ @query_cache_enabled = false
+ end
+
# Disable the query cache within the block.
def uncached
old, @query_cache_enabled = @query_cache_enabled, false
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index 7ac48c6646..70a8f6bb58 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -104,7 +104,7 @@ module ActiveRecord
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
# Requests a maximum column length. This is number of characters for <tt>:string</tt> and
- # <tt>:text</tt> columns and number of bytes for :binary and :integer columns.
+ # <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns.
# * <tt>:default</tt> -
# The column's default value. Use nil for NULL.
# * <tt>:null</tt> -
@@ -153,7 +153,7 @@ module ActiveRecord
# This method returns <tt>self</tt>.
#
# == Examples
- # # Assuming td is an instance of TableDefinition
+ # # Assuming +td+ is an instance of TableDefinition
# td.column(:granted, :boolean)
# # granted BOOLEAN
#
@@ -204,7 +204,7 @@ module ActiveRecord
# end
#
# There's a short-hand method for each of the type values declared at the top. And then there's
- # TableDefinition#timestamps that'll add created_at and +updated_at+ as datetimes.
+ # TableDefinition#timestamps that'll add +created_at+ and +updated_at+ as datetimes.
#
# TableDefinition#references will add an appropriately-named _id column, plus a corresponding _type
# column if the <tt>:polymorphic</tt> option is supplied. If <tt>:polymorphic</tt> is a hash of
@@ -351,7 +351,7 @@ module ActiveRecord
@base.index_exists?(@table_name, column_name, options)
end
- # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
+ # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps
# ===== Example
# t.timestamps
def timestamps
@@ -398,7 +398,7 @@ module ActiveRecord
@base.remove_index(@table_name, options)
end
- # Removes the timestamp columns (created_at and updated_at) from the table.
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
# ===== Example
# t.remove_timestamps
def remove_timestamps
@@ -412,7 +412,7 @@ module ActiveRecord
@base.rename_column(@table_name, column_name, new_column_name)
end
- # Adds a reference. Optionally adds a +type+ column.
+ # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
# <tt>references</tt> and <tt>belongs_to</tt> are acceptable.
# ===== Examples
# t.references(:goat)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 8bae50885f..9f9c2c42cb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -17,6 +17,10 @@ module ActiveRecord
# def tables(name = nil) end
+ # Checks to see if the table +table_name+ exists on the database.
+ #
+ # === Example
+ # table_exists?(:developers)
def table_exists?(table_name)
tables.include?(table_name.to_s)
end
@@ -24,7 +28,7 @@ module ActiveRecord
# Returns an array of indexes for the given table.
# def indexes(table_name, name = nil) end
- # Checks to see if an index exists on a table for a given index definition
+ # Checks to see if an index exists on a table for a given index definition.
#
# === Examples
# # Check an index exists
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d24cce0a3c..65024d76f8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -46,34 +46,34 @@ module ActiveRecord
@instrumenter = ActiveSupport::Notifications.instrumenter
end
- # Returns the human-readable name of the adapter. Use mixed case - one
+ # Returns the human-readable name of the adapter. Use mixed case - one
# can always use downcase if needed.
def adapter_name
'Abstract'
end
- # Does this adapter support migrations? Backend specific, as the
+ # Does this adapter support migrations? Backend specific, as the
# abstract adapter always returns +false+.
def supports_migrations?
false
end
# Can this adapter determine the primary key for tables not attached
- # to an Active Record class, such as join tables? Backend specific, as
+ # to an Active Record class, such as join tables? Backend specific, as
# the abstract adapter always returns +false+.
def supports_primary_key?
false
end
- # Does this adapter support using DISTINCT within COUNT? This is +true+
+ # Does this adapter support using DISTINCT within COUNT? This is +true+
# for all adapters except sqlite.
def supports_count_distinct?
true
end
- # Does this adapter support DDL rollbacks in transactions? That is, would
- # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
- # SQL Server, and others support this. MySQL and others do not.
+ # Does this adapter support DDL rollbacks in transactions? That is, would
+ # CREATE TABLE or ALTER TABLE get rolled back by a transaction? PostgreSQL,
+ # SQL Server, and others support this. MySQL and others do not.
def supports_ddl_transactions?
false
end
@@ -89,7 +89,7 @@ module ActiveRecord
end
# Should primary key values be selected from their corresponding
- # sequence before the insert statement? If true, next_sequence_value
+ # sequence before the insert statement? If true, next_sequence_value
# is called before each insert to set the record's primary key.
# This is false for all adapters but Firebird.
def prefetch_primary_key?(table_name = nil)
@@ -149,7 +149,7 @@ module ActiveRecord
###
# Clear any caching the database adapter may be doing, for example
- # clearing the prepared statement cache. This is database specific.
+ # clearing the prepared statement cache. This is database specific.
def clear_cache!
# this should be overridden by concrete adapters
end
@@ -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 7ac72acd58..8af22fe9f5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,9 +1,11 @@
# encoding: utf-8
+gem 'mysql2', '~> 0.3.0'
require 'mysql2'
module ActiveRecord
class Base
+ # Establishes a connection to the database that's used by all Active Record objects.
def self.mysql2_connection(config)
config[:username] = 'root' if config[:username].nil?
@@ -131,6 +133,7 @@ module ActiveRecord
ADAPTER_NAME
end
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
end
@@ -139,6 +142,7 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints?
true
end
@@ -210,6 +214,8 @@ module ActiveRecord
false
end
+ # Disconnects from the database if already connected.
+ # Otherwise, this method does nothing.
def disconnect!
unless @connection.nil?
@connection.close
@@ -289,6 +295,15 @@ module ActiveRecord
execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
end
+ def exec_delete(sql, name, binds)
+ binds = binds.dup
+
+ # Pretend to support bind parameters
+ execute sql.gsub('?') { quote(*binds.shift.reverse) }, name
+ @connection.affected_rows
+ end
+ alias :exec_update :exec_delete
+
def last_inserted_id(result)
@connection.last_id
end
@@ -356,6 +371,8 @@ module ActiveRecord
end
end
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
def recreate_database(name, options = {})
drop_database(name)
create_database(name, options)
@@ -376,6 +393,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
@@ -394,22 +415,36 @@ module ActiveRecord
show_variable 'collation_database'
end
- def tables(name = nil)
- tables = []
- execute("SHOW TABLES", name).each do |field|
- tables << field.first
+ def tables(name = nil, database = nil) #:nodoc:
+ sql = ["SHOW TABLES", database].compact.join(' IN ')
+ execute(sql, 'SCHEMA').collect do |field|
+ field.first
end
- tables
+ end
+
+ def table_exists?(name)
+ return true if super
+
+ name = name.to_s
+ schema, table = name.split('.', 2)
+
+ unless table # A table was provided without a schema
+ table = schema
+ schema = nil
+ end
+
+ tables(nil, schema).include? table
end
def drop_table(table_name, options = {})
super(table_name, options)
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
indexes = []
current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
+ result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) do |row|
if current_index != row[:Key_name]
next if row[:Key_name] == PRIMARY # skip the primary key
@@ -423,10 +458,11 @@ module ActiveRecord
indexes
end
+ # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
def columns(table_name, name = nil)
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
columns = []
- result = execute(sql)
+ result = execute(sql, 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) { |field|
columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
}
@@ -437,6 +473,10 @@ module ActiveRecord
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
end
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
end
@@ -516,14 +556,16 @@ module ActiveRecord
end
end
+ # SHOW VARIABLES LIKE 'name'.
def show_variable(name)
variables = select_all("SHOW VARIABLES LIKE '#{name}'")
variables.first['Value'] unless variables.empty?
end
+ # Returns a table's primary key and belonging sequence.
def pk_and_sequence_for(table)
keys = []
- result = execute("describe #{quote_table_name(table)}")
+ result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA')
result.each(:symbolize_keys => true, :as => :hash) do |row|
keys << row[:Field] if row[:Key] == "PRI"
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index c2e75acb9a..a9f4c08348 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -3,15 +3,8 @@ require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/object/blank'
require 'set'
-begin
- require 'mysql'
-rescue LoadError
- raise "!!! Missing the mysql gem. Add it to your Gemfile: gem 'mysql'"
-end
-
-unless defined?(Mysql::Result) && Mysql::Result.method_defined?(:each_hash)
- raise "!!! Outdated mysql gem. Upgrade to 2.8.1 or later. In your Gemfile: gem 'mysql', '2.8.1'. Or use gem 'mysql2'"
-end
+gem 'mysql', '~> 2.8.1'
+require 'mysql'
class Mysql
class Time
@@ -208,20 +201,23 @@ 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, since this connection adapter supports migrations.
def supports_migrations? #:nodoc:
true
end
+ # Returns true.
def supports_primary_key? #:nodoc:
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints? #:nodoc:
true
end
@@ -308,6 +304,8 @@ module ActiveRecord
connect
end
+ # Disconnects from the database if already connected. Otherwise, this
+ # method does nothing.
def disconnect!
@connection.close rescue nil
end
@@ -330,6 +328,7 @@ module ActiveRecord
rows
end
+ # Clears the prepared statements cache.
def clear_cache!
@statements.values.each do |cache|
cache[:stmt].close
@@ -396,41 +395,12 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [])
log(sql, name, binds) do
- result = nil
-
- cache = {}
- if binds.empty?
- stmt = @connection.prepare(sql)
- else
- cache = @statements[sql] ||= {
- :stmt => @connection.prepare(sql)
- }
- stmt = cache[:stmt]
+ exec_stmt(sql, name, binds) do |cols, stmt|
+ ActiveRecord::Result.new(cols, stmt.to_a) if cols
end
-
- stmt.execute(*binds.map { |col, val|
- type_cast(val, col)
- })
- if metadata = stmt.result_metadata
- cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
- field.name
- }
-
- metadata.free
- result = ActiveRecord::Result.new(cols, stmt.to_a)
- end
-
- stmt.free_result
- stmt.close if binds.empty?
-
- result
end
end
- def exec_insert(sql, name, binds)
- exec_query(sql, name, binds)
- end
-
def last_inserted_id(result)
@connection.insert_id
end
@@ -479,6 +449,15 @@ module ActiveRecord
@connection.affected_rows
end
+ def exec_delete(sql, name, binds)
+ log(sql, name, binds) do
+ exec_stmt(sql, name, binds) do |cols, stmt|
+ stmt.affected_rows
+ end
+ end
+ end
+ alias :exec_update :exec_delete
+
def begin_db_transaction #:nodoc:
exec_without_stmt "BEGIN"
rescue Mysql::Error
@@ -538,6 +517,8 @@ module ActiveRecord
end.join("")
end
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
def recreate_database(name, options = {}) #:nodoc:
drop_database(name)
create_database(name, options)
@@ -558,6 +539,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
@@ -577,9 +562,8 @@ module ActiveRecord
end
def tables(name = nil, database = nil) #:nodoc:
- tables = []
result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
- result.each { |field| tables << field[0] }
+ tables = result.collect { |field| field[0] }
result.free
tables
end
@@ -602,6 +586,7 @@ module ActiveRecord
super(table_name, options)
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)#:nodoc:
indexes = []
current_index = nil
@@ -620,11 +605,11 @@ module ActiveRecord
indexes
end
+ # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
def columns(table_name, name = nil)#:nodoc:
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- columns = []
result = execute(sql, 'SCHEMA')
- result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
+ columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
result.free
columns
end
@@ -633,6 +618,10 @@ module ActiveRecord
super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
end
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(table_name, new_name)
execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
end
@@ -828,6 +817,46 @@ module ActiveRecord
end
private
+ def exec_stmt(sql, name, binds)
+ cache = {}
+ if binds.empty?
+ stmt = @connection.prepare(sql)
+ else
+ cache = @statements[sql] ||= {
+ :stmt => @connection.prepare(sql)
+ }
+ stmt = cache[:stmt]
+ end
+
+
+ begin
+ stmt.execute(*binds.map { |col, val| type_cast(val, col) })
+ rescue Mysql::Error => e
+ # Older versions of MySQL leave the prepared statement in a bad
+ # place when an error occurs. To support older mysql versions, we
+ # need to close the statement and delete the statement from the
+ # cache.
+ stmt.close
+ @statements.delete sql
+ raise e
+ end
+
+ cols = nil
+ if metadata = stmt.result_metadata
+ cols = cache[:cols] ||= metadata.fetch_fields.map { |field|
+ field.name
+ }
+ end
+
+ result = yield [cols, stmt]
+
+ stmt.result_metadata.free if cols
+ stmt.free_result
+ stmt.close if binds.empty?
+
+ result
+ end
+
def connect
encoding = @config[:encoding]
if encoding
@@ -870,6 +899,7 @@ module ActiveRecord
version[0] >= 5
end
+ # Returns the version of the connected MySQL server.
def version
@version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e74ec84e81..37db2be7a9 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
@@ -95,6 +98,9 @@ module ActiveRecord
# XML type
when 'xml'
:xml
+ # tsvector type
+ when 'tsvector'
+ :tsvector
# Arrays
when /^\D+\[\]$/
:string
@@ -116,6 +122,14 @@ module ActiveRecord
# Extracts the value from a PostgreSQL column default definition.
def self.extract_value_from_default(default)
case default
+ # This is a performance optimization for Ruby 1.9.2 in development.
+ # If the value is nil, we return nil straight away without checking
+ # the regular expressions. If we check each regular expression,
+ # Regexp#=== will call NilClass#to_str, which will trigger
+ # method_missing (defined by whiny nil in ActiveSupport) which
+ # makes this method very very slow.
+ when NilClass
+ nil
# Numeric types
when /\A\(?(-?\d+(\.\d*)?\)?)\z/
$1
@@ -186,6 +200,11 @@ module ActiveRecord
options = args.extract_options!
column(args[0], 'xml', options)
end
+
+ def tsvector(*args)
+ options = args.extract_options!
+ column(args[0], 'tsvector', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -203,7 +222,8 @@ module ActiveRecord
:date => { :name => "date" },
:binary => { :name => "bytea" },
:boolean => { :name => "boolean" },
- :xml => { :name => "xml" }
+ :xml => { :name => "xml" },
+ :tsvector => { :name => "tsvector" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -211,8 +231,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
@@ -225,7 +245,6 @@ module ActiveRecord
# @local_tz is initialized as nil to avoid warnings when connect tries to use it
@local_tz = nil
@table_alias_length = nil
- @postgresql_version = nil
@statements = {}
connect
@@ -237,6 +256,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}"
@@ -246,28 +266,16 @@ module ActiveRecord
# Is this connection alive and ready for queries?
def active?
- if @connection.respond_to?(:status)
- @connection.status == PGconn::CONNECTION_OK
- else
- # We're asking the driver, not Active Record, so use @connection.query instead of #query
- @connection.query 'SELECT 1'
- true
- end
- # postgres-pr raises a NoMethodError when querying if no connection is available.
- rescue PGError, NoMethodError
+ @connection.status == PGconn::CONNECTION_OK
+ rescue PGError
false
end
# Close then reopen the connection.
def reconnect!
- if @connection.respond_to?(:reset)
- clear_cache!
- @connection.reset
- configure_connection
- else
- disconnect!
- connect
- end
+ clear_cache!
+ @connection.reset
+ configure_connection
end
def reset!
@@ -275,7 +283,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
@@ -285,7 +294,7 @@ module ActiveRecord
NATIVE_DATABASE_TYPES
end
- # Does PostgreSQL support migrations?
+ # Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
end
@@ -311,6 +320,7 @@ module ActiveRecord
true
end
+ # Returns true, since this connection adapter supports savepoints.
def supports_savepoints?
true
end
@@ -366,7 +376,7 @@ module ActiveRecord
case value
when String
return super unless 'bytea' == column.sql_type
- escape_bytea(value)
+ { :value => value, :format => 1 }
else
super
end
@@ -419,17 +429,17 @@ module ActiveRecord
# REFERENTIAL INTEGRITY ====================================
- def supports_disable_referential_integrity?() #:nodoc:
+ def supports_disable_referential_integrity? #:nodoc:
true
end
def disable_referential_integrity #:nodoc:
- if supports_disable_referential_integrity?() then
+ if supports_disable_referential_integrity? then
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";"))
end
yield
ensure
- if supports_disable_referential_integrity?() then
+ if supports_disable_referential_integrity? then
execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";"))
end
end
@@ -460,54 +470,50 @@ 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
# Queries the database and returns the results in an Array-like object
def query(sql, name = nil) #:nodoc:
log(sql, name) do
- if @async
- res = @connection.async_exec(sql)
- else
- res = @connection.exec(sql)
- end
- return result_as_array(res)
+ result_as_array @connection.async_exec(sql)
end
end
@@ -515,11 +521,7 @@ module ActiveRecord
# or raising a PGError exception otherwise.
def execute(sql, name = nil)
log(sql, name) do
- if @async
- @connection.async_exec(sql)
- else
- @connection.exec(sql)
- end
+ @connection.async_exec(sql)
end
end
@@ -528,33 +530,26 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- return exec_no_cache(sql, name) if binds.empty?
-
log(sql, name, binds) do
- unless @statements.key? sql
- nextkey = "a#{@statements.length + 1}"
- @connection.prepare nextkey, sql
- @statements[sql] = nextkey
- end
-
- key = @statements[sql]
+ result = binds.empty? ? exec_no_cache(sql, binds) :
+ exec_cache(sql, binds)
- # Clear the queue
- @connection.get_last_result
- @connection.send_query_prepared(key, binds.map { |col, val|
- type_cast(val, col)
- })
- @connection.block
- result = @connection.get_last_result
ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
result.clear
return ret
end
end
- def exec_insert(sql, name, binds)
- exec_query(sql, name, binds)
+ def exec_delete(sql, name = 'SQL', binds = [])
+ log(sql, name, binds) do
+ result = binds.empty? ? exec_no_cache(sql, binds) :
+ exec_cache(sql, binds)
+ affected = result.cmd_tuples
+ result.clear
+ affected
+ end
end
+ alias :exec_update :exec_delete
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
unless pk
@@ -641,7 +636,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'
@@ -688,7 +683,7 @@ module ActiveRecord
[schema, table]
end
- # Returns the list of all indexes for a table.
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil)
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
result = query(<<-SQL, name)
@@ -853,6 +848,9 @@ module ActiveRecord
end
# Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(name, new_name)
execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
@@ -933,31 +931,13 @@ 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
- # Returns the version of the connected PostgreSQL version.
+ # Returns the version of the connected PostgreSQL server.
def postgresql_version
- @postgresql_version ||=
- if @connection.respond_to?(:server_version)
- @connection.server_version
- else
- # Mimic PGconn.server_version behavior
- begin
- if query('SELECT version()')[0][0] =~ /PostgreSQL ([0-9.]+)/
- major, minor, tiny = $1.split(".")
- (major.to_i * 10000) + (minor.to_i * 100) + tiny.to_i
- else
- 0
- end
- rescue
- 0
- end
- end
+ @connection.server_version
end
def translate_exception(exception, message)
@@ -972,13 +952,26 @@ module ActiveRecord
end
private
- def exec_no_cache(sql, name)
- log(sql, name) do
- result = @connection.async_exec(sql)
- ret = ActiveRecord::Result.new(result.fields, result_as_array(result))
- result.clear
- ret
+ def exec_no_cache(sql, binds)
+ @connection.async_exec(sql)
+ end
+
+ def exec_cache(sql, binds)
+ unless @statements.key? sql
+ nextkey = "a#{@statements.length + 1}"
+ @connection.prepare nextkey, sql
+ @statements[sql] = nextkey
end
+
+ key = @statements[sql]
+
+ # Clear the queue
+ @connection.get_last_result
+ @connection.send_query_prepared(key, binds.map { |col, val|
+ type_cast(val, col)
+ })
+ @connection.block
+ @connection.get_last_result
end
# The internal PostgreSQL identifier of the money data type.
@@ -990,10 +983,6 @@ module ActiveRecord
# connected server's characteristics.
def connect
@connection = PGconn.connect(*@connection_parameters)
- PGconn.translate_results = false if PGconn.respond_to?(:translate_results=)
-
- # Ignore async_exec and async_query when using postgres-pr.
- @async = @connection.respond_to?(:async_exec)
# Money type has a fixed precision of 10 in PostgreSQL 8.2 and below, and as of
# PostgreSQL 8.3 it has a fixed precision of 19. PostgreSQLColumn.extract_precision
@@ -1007,11 +996,7 @@ module ActiveRecord
# This is called by #connect and should not be called manually.
def configure_connection
if @config[:encoding]
- if @connection.respond_to?(:set_client_encoding)
- @connection.set_client_encoding(@config[:encoding])
- else
- execute("SET client_encoding TO '#{@config[:encoding]}'")
- end
+ @connection.set_client_encoding(@config[:encoding])
end
self.client_min_messages = @config[:min_messages] if @config[:min_messages]
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
@@ -1093,4 +1078,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 9e7f874f4b..d2785b234a 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -58,24 +58,28 @@ module ActiveRecord
'SQLite'
end
+ # Returns true if SQLite version is '2.0.0' or greater, false otherwise.
def supports_ddl_transactions?
sqlite_version >= '2.0.0'
end
+ # Returns true if SQLite version is '3.6.8' or greater, false otherwise.
def supports_savepoints?
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, since this connection adapter supports migrations.
def supports_migrations? #:nodoc:
true
end
+ # Returns true.
def supports_primary_key? #:nodoc:
true
end
@@ -84,24 +88,30 @@ module ActiveRecord
true
end
+ # Returns true if SQLite version is '3.1.6' or greater, false otherwise.
def supports_add_column?
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
+ # Returns true if SQLite version is '3.2.6' or greater, false otherwise.
def supports_count_distinct? #:nodoc:
sqlite_version >= '3.2.6'
end
+ # Returns true if SQLite version is '3.1.0' or greater, false otherwise.
def supports_autoincrement? #:nodoc:
sqlite_version >= '3.1.0'
end
@@ -173,9 +183,11 @@ module ActiveRecord
end
end
- def exec_insert(sql, name, binds)
+ def exec_delete(sql, name = 'SQL', binds = [])
exec_query(sql, name, binds)
+ @connection.changes
end
+ alias :exec_update :exec_delete
def last_inserted_id(result)
@connection.last_insert_row_id
@@ -243,6 +255,7 @@ module ActiveRecord
end
end
+ # Returns an array of +SQLiteColumn+ objects for the table specified by +table_name+.
def columns(table_name, name = nil) #:nodoc:
table_structure(table_name).map do |field|
case field["dflt_value"]
@@ -258,6 +271,7 @@ module ActiveRecord
end
end
+ # Returns an array of indexes for the given table.
def indexes(table_name, name = nil) #:nodoc:
exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", name).map do |row|
IndexDefinition.new(
@@ -281,6 +295,10 @@ module ActiveRecord
exec_query "DROP INDEX #{quote_column_name(index_name)}"
end
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
def rename_table(name, new_name)
exec_query "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}"
end
@@ -345,10 +363,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/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 0939ec2626..0e3ed7aac7 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -423,8 +423,8 @@ class FixturesFileNotFound < StandardError; end
# to the rescue:
#
# george_reginald:
-# monkey_id: <%= Fixtures.identify(:reginald) %>
-# pirate_id: <%= Fixtures.identify(:george) %>
+# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %>
+# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %>
#
# == Support for YAML defaults
#
@@ -444,367 +444,374 @@ class FixturesFileNotFound < StandardError; end
#
# Any fixture labeled "DEFAULTS" is safely ignored.
-class Fixtures
- MAX_ID = 2 ** 30 - 1
+Fixture = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixture', 'ActiveRecord::Fixture')
+Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Fixtures', 'ActiveRecord::Fixtures')
- @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
+module ActiveRecord
+ class Fixtures
+ MAX_ID = 2 ** 30 - 1
- def self.find_table_name(table_name) # :nodoc:
- ActiveRecord::Base.pluralize_table_names ?
- table_name.to_s.singularize.camelize :
- table_name.to_s.camelize
- end
+ @@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
- def self.reset_cache
- @@all_cached_fixtures.clear
- end
+ def self.find_table_name(table_name) # :nodoc:
+ ActiveRecord::Base.pluralize_table_names ?
+ table_name.to_s.singularize.camelize :
+ table_name.to_s.camelize
+ end
- def self.cache_for_connection(connection)
- @@all_cached_fixtures[connection]
- end
+ def self.reset_cache
+ @@all_cached_fixtures.clear
+ end
- def self.fixture_is_cached?(connection, table_name)
- cache_for_connection(connection)[table_name]
- end
+ def self.cache_for_connection(connection)
+ @@all_cached_fixtures[connection]
+ end
- def self.cached_fixtures(connection, keys_to_fetch = nil)
- if keys_to_fetch
- cache_for_connection(connection).values_at(*keys_to_fetch)
- else
- cache_for_connection(connection).values
+ def self.fixture_is_cached?(connection, table_name)
+ cache_for_connection(connection)[table_name]
end
- end
- def self.cache_fixtures(connection, fixtures_map)
- cache_for_connection(connection).update(fixtures_map)
- end
+ def self.cached_fixtures(connection, keys_to_fetch = nil)
+ if keys_to_fetch
+ cache_for_connection(connection).values_at(*keys_to_fetch)
+ else
+ cache_for_connection(connection).values
+ end
+ end
+
+ def self.cache_fixtures(connection, fixtures_map)
+ cache_for_connection(connection).update(fixtures_map)
+ end
- def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
- if load_instances
- fixtures.each do |name, fixture|
- begin
- object.instance_variable_set "@#{name}", fixture.find
- rescue FixtureClassNotFound
- nil
+ def self.instantiate_fixtures(object, fixture_name, fixtures, load_instances = true)
+ if load_instances
+ fixtures.each do |name, fixture|
+ begin
+ object.instance_variable_set "@#{name}", fixture.find
+ rescue FixtureClassNotFound
+ nil
+ end
end
end
end
- end
- def self.instantiate_all_loaded_fixtures(object, load_instances = true)
- all_loaded_fixtures.each do |table_name, fixtures|
- Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ def self.instantiate_all_loaded_fixtures(object, load_instances = true)
+ all_loaded_fixtures.each do |table_name, fixtures|
+ ActiveRecord::Fixtures.instantiate_fixtures(object, table_name, fixtures, load_instances)
+ end
end
- end
- cattr_accessor :all_loaded_fixtures
- self.all_loaded_fixtures = {}
+ cattr_accessor :all_loaded_fixtures
+ self.all_loaded_fixtures = {}
- def self.create_fixtures(fixtures_directory, table_names, class_names = {})
- table_names = [table_names].flatten.map { |n| n.to_s }
- table_names.each { |n|
- class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
- }
+ def self.create_fixtures(fixtures_directory, table_names, class_names = {})
+ table_names = [table_names].flatten.map { |n| n.to_s }
+ table_names.each { |n|
+ class_names[n.tr('/', '_').to_sym] = n.classify if n.include?('/')
+ }
- # FIXME: Apparently JK uses this.
- connection = block_given? ? yield : ActiveRecord::Base.connection
+ # FIXME: Apparently JK uses this.
+ connection = block_given? ? yield : ActiveRecord::Base.connection
- files_to_read = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }
+ files_to_read = table_names.reject { |table_name|
+ fixture_is_cached?(connection, table_name)
+ }
- unless files_to_read.empty?
- connection.disable_referential_integrity do
- fixtures_map = {}
+ unless files_to_read.empty?
+ connection.disable_referential_integrity do
+ fixtures_map = {}
- fixture_files = files_to_read.map do |path|
- table_name = path.tr '/', '_'
+ fixture_files = files_to_read.map do |path|
+ table_name = path.tr '/', '_'
- fixtures_map[path] = Fixtures.new(
- connection,
- table_name,
- class_names[table_name.to_sym],
- File.join(fixtures_directory, path))
- end
+ fixtures_map[path] = ActiveRecord::Fixtures.new(
+ connection,
+ table_name,
+ class_names[table_name.to_sym] || table_name.classify,
+ File.join(fixtures_directory, path))
+ end
- all_loaded_fixtures.update(fixtures_map)
+ all_loaded_fixtures.update(fixtures_map)
- connection.transaction(:requires_new => true) do
- fixture_files.each do |ff|
- conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
- table_rows = ff.table_rows
+ connection.transaction(:requires_new => true) do
+ fixture_files.each do |ff|
+ conn = ff.model_class.respond_to?(:connection) ? ff.model_class.connection : connection
+ table_rows = ff.table_rows
- table_rows.keys.each do |table|
- conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
- end
+ table_rows.keys.each do |table|
+ conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
+ end
- table_rows.each do |table_name,rows|
- rows.each do |row|
- conn.insert_fixture(row, table_name)
+ table_rows.each do |table_name,rows|
+ rows.each do |row|
+ conn.insert_fixture(row, table_name)
+ end
end
end
- end
- # Cap primary key sequences to max(pk).
- if connection.respond_to?(:reset_pk_sequence!)
- table_names.each do |table_name|
- connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ table_names.each do |table_name|
+ connection.reset_pk_sequence!(table_name.tr('/', '_'))
+ end
end
end
- end
- cache_fixtures(connection, fixtures_map)
+ cache_fixtures(connection, fixtures_map)
+ end
end
+ cached_fixtures(connection, table_names)
end
- cached_fixtures(connection, table_names)
- end
-
- # Returns a consistent, platform-independent identifier for +label+.
- # Identifiers are positive integers less than 2^32.
- def self.identify(label)
- Zlib.crc32(label.to_s) % MAX_ID
- end
- attr_reader :table_name, :name, :fixtures, :model_class
-
- def initialize(connection, table_name, class_name, fixture_path)
- @connection = connection
- @table_name = table_name
- @fixture_path = fixture_path
- @name = table_name # preserve fixture base name
- @class_name = class_name
-
- @fixtures = ActiveSupport::OrderedHash.new
- @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
-
- # Should be an AR::Base type class
- if class_name.is_a?(Class)
- @table_name = class_name.table_name
- @connection = class_name.connection
- @model_class = class_name
- else
- @model_class = class_name.constantize rescue nil
+ # Returns a consistent, platform-independent identifier for +label+.
+ # Identifiers are positive integers less than 2^32.
+ def self.identify(label)
+ Zlib.crc32(label.to_s) % MAX_ID
end
- read_fixture_files
- end
+ attr_reader :table_name, :name, :fixtures, :model_class
- def [](x)
- fixtures[x]
- end
+ def initialize(connection, table_name, class_name, fixture_path)
+ @connection = connection
+ @table_name = table_name
+ @fixture_path = fixture_path
+ @name = table_name # preserve fixture base name
+ @class_name = class_name
- def []=(k,v)
- fixtures[k] = v
- end
+ @fixtures = ActiveSupport::OrderedHash.new
+ @table_name = "#{ActiveRecord::Base.table_name_prefix}#{@table_name}#{ActiveRecord::Base.table_name_suffix}"
- def each(&block)
- fixtures.each(&block)
- end
+ # Should be an AR::Base type class
+ if class_name.is_a?(Class)
+ @table_name = class_name.table_name
+ @connection = class_name.connection
+ @model_class = class_name
+ else
+ @model_class = class_name.constantize rescue nil
+ end
- def size
- fixtures.size
- end
+ read_fixture_files
+ end
- # Return a hash of rows to be inserted. The key is the table, the value is
- # a list of rows to insert to that table.
- def table_rows
- now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
- now = now.to_s(:db)
+ def [](x)
+ fixtures[x]
+ end
- # allow a standard key to be used for doing defaults in YAML
- fixtures.delete('DEFAULTS')
+ def []=(k,v)
+ fixtures[k] = v
+ end
- # track any join tables we need to insert later
- rows = Hash.new { |h,table| h[table] = [] }
+ def each(&block)
+ fixtures.each(&block)
+ end
- rows[table_name] = fixtures.map do |label, fixture|
- row = fixture.to_hash
+ def size
+ fixtures.size
+ end
- if model_class && model_class < ActiveRecord::Base
- # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
- if model_class.record_timestamps
- timestamp_column_names.each do |name|
- row[name] = now unless row.key?(name)
- end
- end
+ # Return a hash of rows to be inserted. The key is the table, the value is
+ # a list of rows to insert to that table.
+ def table_rows
+ now = ActiveRecord::Base.default_timezone == :utc ? Time.now.utc : Time.now
+ now = now.to_s(:db)
- # interpolate the fixture label
- row.each do |key, value|
- row[key] = label if value == "$LABEL"
- end
+ # allow a standard key to be used for doing defaults in YAML
+ fixtures.delete('DEFAULTS')
- # generate a primary key if necessary
- if has_primary_key_column? && !row.include?(primary_key_name)
- row[primary_key_name] = Fixtures.identify(label)
- end
+ # track any join tables we need to insert later
+ rows = Hash.new { |h,table| h[table] = [] }
+
+ rows[table_name] = fixtures.map do |label, fixture|
+ row = fixture.to_hash
- # If STI is used, find the correct subclass for association reflection
- reflection_class =
- if row.include?(inheritance_column_name)
- row[inheritance_column_name].constantize rescue model_class
- else
- model_class
+ if model_class && model_class < ActiveRecord::Base
+ # fill in timestamp columns if they aren't specified and the model is set to record_timestamps
+ if model_class.record_timestamps
+ timestamp_column_names.each do |name|
+ row[name] = now unless row.key?(name)
+ end
end
- reflection_class.reflect_on_all_associations.each do |association|
- case association.macro
- when :belongs_to
- # Do not replace association name with association foreign key if they are named the same
- fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+ # interpolate the fixture label
+ row.each do |key, value|
+ row[key] = label if value == "$LABEL"
+ end
- if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
- if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
- # support polymorphic belongs_to as "label (Type)"
- row[association.foreign_type] = $1
- end
+ # generate a primary key if necessary
+ if has_primary_key_column? && !row.include?(primary_key_name)
+ row[primary_key_name] = ActiveRecord::Fixtures.identify(label)
+ end
- row[fk_name] = Fixtures.identify(value)
+ # If STI is used, find the correct subclass for association reflection
+ reflection_class =
+ if row.include?(inheritance_column_name)
+ row[inheritance_column_name].constantize rescue model_class
+ else
+ model_class
end
- when :has_and_belongs_to_many
- if (targets = row.delete(association.name.to_s))
- targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
- rows[table_name].concat targets.map { |target|
- { association.foreign_key => row[primary_key_name],
- association.association_foreign_key => Fixtures.identify(target) }
- }
+
+ reflection_class.reflect_on_all_associations.each do |association|
+ case association.macro
+ when :belongs_to
+ # Do not replace association name with association foreign key if they are named the same
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
+
+ if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
+ # support polymorphic belongs_to as "label (Type)"
+ row[association.foreign_type] = $1
+ end
+
+ row[fk_name] = ActiveRecord::Fixtures.identify(value)
+ end
+ when :has_and_belongs_to_many
+ if (targets = row.delete(association.name.to_s))
+ targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
+ table_name = association.options[:join_table]
+ rows[table_name].concat targets.map { |target|
+ { association.foreign_key => row[primary_key_name],
+ association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
+ }
+ end
end
end
end
- end
-
- row
- end
- rows
- end
- private
- def primary_key_name
- @primary_key_name ||= model_class && model_class.primary_key
+ row
+ end
+ rows
end
- def has_primary_key_column?
- @has_primary_key_column ||= primary_key_name &&
- model_class.columns.any? { |c| c.name == primary_key_name }
- end
+ private
+ def primary_key_name
+ @primary_key_name ||= model_class && model_class.primary_key
+ end
- def timestamp_column_names
- @timestamp_column_names ||=
- %w(created_at created_on updated_at updated_on) & column_names
- end
+ def has_primary_key_column?
+ @has_primary_key_column ||= primary_key_name &&
+ model_class.columns.any? { |c| c.name == primary_key_name }
+ end
- def inheritance_column_name
- @inheritance_column_name ||= model_class && model_class.inheritance_column
- end
+ def timestamp_column_names
+ @timestamp_column_names ||=
+ %w(created_at created_on updated_at updated_on) & column_names
+ end
- def column_names
- @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
- end
+ def inheritance_column_name
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
+ end
- def read_fixture_files
- if File.file?(yaml_file_path)
- read_yaml_fixture_files
- elsif File.file?(csv_file_path)
- read_csv_fixture_files
- else
- raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ def column_names
+ @column_names ||= @connection.columns(@table_name).collect { |c| c.name }
end
- end
- def read_yaml_fixture_files
- yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
- File.file?(f)
- } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
-
- if yaml = parse_yaml_string(yaml_string)
- # If the file is an ordered map, extract its children.
- yaml_value =
- if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
- yaml.value
- else
- [yaml]
- end
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ else
+ raise FixturesFileNotFound, "Could not find #{yaml_file_path} or #{csv_file_path}"
+ end
+ end
- yaml_value.each do |fixture|
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
- fixture.each do |name, data|
- unless data
- raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ def read_yaml_fixture_files
+ yaml_string = (Dir["#{@fixture_path}/**/*.yml"].select { |f|
+ File.file?(f)
+ } + [yaml_file_path]).map { |file_path| IO.read(file_path) }.join
+
+ if yaml = parse_yaml_string(yaml_string)
+ # If the file is an ordered map, extract its children.
+ yaml_value =
+ if yaml.respond_to?(:type_id) && yaml.respond_to?(:value)
+ yaml.value
+ else
+ [yaml]
end
- fixtures[name] = Fixture.new(data, model_class)
+ yaml_value.each do |fixture|
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{fixture}" unless fixture.respond_to?(:each)
+ fixture.each do |name, data|
+ unless data
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
+ end
+
+ fixtures[name] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
end
end
- end
- def read_csv_fixture_files
- reader = CSV.parse(erb_render(IO.read(csv_file_path)))
- header = reader.shift
- i = 0
- reader.each do |row|
- data = {}
- row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
- fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = Fixture.new(data, model_class)
+ def read_csv_fixture_files
+ reader = CSV.parse(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ fixtures["#{@class_name.to_s.underscore}_#{i+=1}"] = ActiveRecord::Fixture.new(data, model_class)
+ end
end
- end
- def yaml_file_path
- "#{@fixture_path}.yml"
- end
+ def yaml_file_path
+ "#{@fixture_path}.yml"
+ end
- def csv_file_path
- @fixture_path + ".csv"
- end
+ def csv_file_path
+ @fixture_path + ".csv"
+ end
- def yaml_fixtures_key(path)
- File.basename(@fixture_path).split(".").first
- end
+ def yaml_fixtures_key(path)
+ File.basename(@fixture_path).split(".").first
+ end
- def parse_yaml_string(fixture_content)
- YAML::load(erb_render(fixture_content))
- rescue => error
- raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
- end
+ def parse_yaml_string(fixture_content)
+ YAML::load(erb_render(fixture_content))
+ rescue => error
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}"
+ end
- def erb_render(fixture_content)
- ERB.new(fixture_content).result
- end
-end
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+ end
-class Fixture #:nodoc:
- include Enumerable
+ class Fixture #:nodoc:
+ include Enumerable
- class FixtureError < StandardError #:nodoc:
- end
+ class FixtureError < StandardError #:nodoc:
+ end
- class FormatError < FixtureError #:nodoc:
- end
+ class FormatError < FixtureError #:nodoc:
+ end
- attr_reader :model_class, :fixture
+ attr_reader :model_class, :fixture
- def initialize(fixture, model_class)
- @fixture = fixture
- @model_class = model_class
- end
+ def initialize(fixture, model_class)
+ @fixture = fixture
+ @model_class = model_class
+ end
- def class_name
- model_class.name if model_class
- end
+ def class_name
+ model_class.name if model_class
+ end
- def each
- fixture.each { |item| yield item }
- end
+ def each
+ fixture.each { |item| yield item }
+ end
- def [](key)
- fixture[key]
- end
+ def [](key)
+ fixture[key]
+ end
- alias :to_hash :fixture
+ alias :to_hash :fixture
- def find
- if model_class
- model_class.find(fixture[model_class.primary_key])
- else
- raise FixtureClassNotFound, "No class attached to find."
+ def find
+ if model_class
+ model_class.find(fixture[model_class.primary_key])
+ else
+ raise FixtureClassNotFound, "No class attached to find."
+ end
end
end
end
@@ -830,7 +837,7 @@ module ActiveRecord
self.pre_loaded_fixtures = false
self.fixture_class_names = Hash.new do |h, table_name|
- h[table_name] = Fixtures.find_table_name(table_name)
+ h[table_name] = ActiveRecord::Fixtures.find_table_name(table_name)
end
end
@@ -942,7 +949,7 @@ module ActiveRecord
ActiveRecord::Base.connection.begin_db_transaction
# Load fixtures for every test.
else
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
@@already_loaded_fixtures[self.class] = nil
@loaded_fixtures = load_fixtures
end
@@ -955,7 +962,7 @@ module ActiveRecord
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
unless run_in_transaction?
- Fixtures.reset_cache
+ ActiveRecord::Fixtures.reset_cache
end
# Rollback changes if a transaction is active.
@@ -968,7 +975,7 @@ module ActiveRecord
private
def load_fixtures
- fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
+ fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
Hash[fixtures.map { |f| [f.name, f] }]
end
@@ -977,16 +984,16 @@ module ActiveRecord
def instantiate_fixtures
if pre_loaded_fixtures
- raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::Fixtures.all_loaded_fixtures.empty?
unless @@required_fixture_classes
- self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
+ self.class.require_fixture_classes ActiveRecord::Fixtures.all_loaded_fixtures.keys
@@required_fixture_classes = true
end
- Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ ActiveRecord::Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
else
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
@loaded_fixtures.each do |fixture_name, fixtures|
- Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
+ ActiveRecord::Fixtures.instantiate_fixtures(self, fixture_name, fixtures, load_instances?)
end
end
end
diff --git a/activerecord/lib/active_record/identity_map.rb b/activerecord/lib/active_record/identity_map.rb
index 95a8e5cff7..f88ead9ca0 100644
--- a/activerecord/lib/active_record/identity_map.rb
+++ b/activerecord/lib/active_record/identity_map.rb
@@ -49,27 +49,30 @@ module ActiveRecord
end
def get(klass, primary_key)
- obj = repository[klass.symbolized_base_class][primary_key]
- if obj.is_a?(klass)
- if ActiveRecord::Base.logger
- ActiveRecord::Base.logger.debug "#{klass} with ID = #{primary_key} loaded from Identity Map"
- end
- obj
+ record = repository[klass.symbolized_sti_name][primary_key]
+
+ if record.is_a?(klass)
+ ActiveSupport::Notifications.instrument("identity.active_record",
+ :line => "From Identity Map (id: #{primary_key})",
+ :name => "#{klass} Loaded",
+ :connection_id => object_id)
+
+ record
else
nil
end
end
def add(record)
- repository[record.class.symbolized_base_class][record.id] = record
+ repository[record.class.symbolized_sti_name][record.id] = record
end
def remove(record)
- repository[record.class.symbolized_base_class].delete(record.id)
+ repository[record.class.symbolized_sti_name].delete(record.id)
end
- def remove_by_id(symbolized_base_class, id)
- repository[symbolized_base_class].delete(id)
+ def remove_by_id(symbolized_sti_name, id)
+ repository[symbolized_sti_name].delete(id)
end
def clear
@@ -95,14 +98,33 @@ module ActiveRecord
end
class Middleware
+ class Body #:nodoc:
+ def initialize(target, original)
+ @target = target
+ @original = original
+ end
+
+ def each(&block)
+ @target.each(&block)
+ end
+
+ def close
+ @target.close if @target.respond_to?(:close)
+ ensure
+ IdentityMap.enabled = @original
+ IdentityMap.clear
+ end
+ end
+
def initialize(app)
@app = app
end
def call(env)
- ActiveRecord::IdentityMap.use do
- @app.call(env)
- end
+ enabled = IdentityMap.enabled
+ IdentityMap.enabled = true
+ status, headers, body = @app.call(env)
+ [status, headers, Body.new(body, enabled)]
end
end
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index d31e321440..3a015ee8c2 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -46,6 +46,15 @@ module ActiveRecord
debug " #{name} #{sql}#{binds}"
end
+ def identity(event)
+ return unless logger.debug?
+
+ name = color(event.payload[:name], odd? ? CYAN : MAGENTA, true)
+ line = odd? ? color(event.payload[:line], nil, true) : event.payload[:line]
+
+ debug " #{name} #{line}"
+ end
+
def odd?
@odd_or_even = !@odd_or_even
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/observer.rb b/activerecord/lib/active_record/observer.rb
index 0893d7e337..c723436330 100644
--- a/activerecord/lib/active_record/observer.rb
+++ b/activerecord/lib/active_record/observer.rb
@@ -110,8 +110,8 @@ module ActiveRecord
next unless respond_to?(callback)
callback_meth = :"_notify_#{observer_name}_for_#{callback}"
unless klass.respond_to?(callback_meth)
- klass.send(:define_method, callback_meth) do
- observer.send(callback, self)
+ klass.send(:define_method, callback_meth) do |&block|
+ observer.send(callback, self, &block)
end
klass.send(callback, callback_meth)
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a916c88348..b4531ed35f 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -77,7 +77,15 @@ module ActiveRecord
def destroy
if persisted?
IdentityMap.remove(self) if IdentityMap.enabled?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).delete_all
+ pk = self.class.primary_key
+ column = self.class.columns_hash[pk]
+ substitute = connection.substitute_at(column, 0)
+
+ relation = self.class.unscoped.where(
+ self.class.arel_table[pk].eq(substitute))
+
+ relation.bind_values = [[column, id]]
+ relation.delete_all
end
@destroyed = true
@@ -136,22 +144,27 @@ module ActiveRecord
# 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.
- def update_attributes(attributes)
+ #
+ # When updating model attributes, mass-assignment security protection is respected.
+ # If no +:as+ option is supplied then the +:default+ scope will be used.
+ # If you want to bypass the protection given by +attr_protected+ and
+ # +attr_accessible+ then you can do so using the +:without_protection+ option.
+ def update_attributes(attributes, options = {})
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
- self.attributes = attributes
+ self.assign_attributes(attributes, options)
save
end
end
# Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
# of +save+, so an exception is raised if the record is invalid.
- def update_attributes!(attributes)
+ def update_attributes!(attributes, options = {})
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
- self.attributes = attributes
+ self.assign_attributes(attributes, options)
save!
end
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index d9f85a4e5e..4e61671473 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -27,10 +27,32 @@ module ActiveRecord
@app = app
end
- def call(env)
- ActiveRecord::Base.cache do
- @app.call(env)
+ class BodyProxy # :nodoc:
+ def initialize(original_cache_value, target)
+ @original_cache_value = original_cache_value
+ @target = target
+ end
+
+ def each(&block)
+ @target.each(&block)
+ end
+
+ def close
+ @target.close if @target.respond_to?(:close)
+ ensure
+ ActiveRecord::Base.connection.clear_query_cache
+ unless @original_cache_value
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
end
end
+
+ def call(env)
+ old = ActiveRecord::Base.connection.query_cache_enabled
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ status, headers, body = @app.call(env)
+ [status, headers, BodyProxy.new(old, body)]
+ end
end
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index cace6f0cc0..bae2ded244 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -26,10 +26,12 @@ module ActiveRecord
load "active_record/railties/databases.rake"
end
- # When loading console, force ActiveRecord to be loaded to avoid cross
- # references when loading a constant for the first time.
- console do
- ActiveRecord::Base
+ # When loading console, force ActiveRecord::Base to be loaded
+ # to avoid cross references when loading a constant for the
+ # first time. Also, make it output to STDERR.
+ console do |sandbox|
+ require "active_record/railties/console_sandbox" if sandbox
+ ActiveRecord::Base.logger = Logger.new(STDERR)
end
initializer "active_record.initialize_timezone" do
@@ -50,6 +52,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/console_sandbox.rb b/activerecord/lib/active_record/railties/console_sandbox.rb
new file mode 100644
index 0000000000..65a3d68619
--- /dev/null
+++ b/activerecord/lib/active_record/railties/console_sandbox.rb
@@ -0,0 +1,6 @@
+ActiveRecord::Base.connection.increment_open_transactions
+ActiveRecord::Base.connection.begin_db_transaction
+at_exit do
+ ActiveRecord::Base.connection.rollback_db_transaction
+ ActiveRecord::Base.connection.decrement_open_transactions
+end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 6b3c38cb58..c6bc040f9f 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -4,11 +4,11 @@ db_namespace = namespace :db do
task :load_config => :rails_env do
require 'active_record'
ActiveRecord::Base.configurations = Rails.application.config.database_configuration
- ActiveRecord::Migrator.migrations_paths = Rails.application.paths["db/migrate"].to_a
+ ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
- if engine.paths["db/migrate"].existent
- ActiveRecord::Migrator.migrations_paths += engine.paths["db/migrate"].to_a
+ if engine.paths['db/migrate'].existent
+ ActiveRecord::Migrator.migrations_paths += engine.paths['db/migrate'].to_a
end
end
end
@@ -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))
@@ -94,7 +100,7 @@ db_namespace = namespace :db do
$stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
end
end
- when 'postgresql'
+ when /postgresql/
@encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
begin
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
@@ -137,7 +143,7 @@ db_namespace = namespace :db do
end
def local_database?(config, &block)
- if config['host'].in?(["127.0.0.1", "localhost"]) || config['host'].blank?
+ if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank?
yield
else
$stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
@@ -155,35 +161,35 @@ db_namespace = namespace :db do
namespace :migrate do
# desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).'
task :redo => [:environment, :load_config] do
- if ENV["VERSION"]
- db_namespace["migrate:down"].invoke
- db_namespace["migrate:up"].invoke
+ if ENV['VERSION']
+ db_namespace['migrate:down'].invoke
+ db_namespace['migrate:up'].invoke
else
- db_namespace["rollback"].invoke
- db_namespace["migrate"].invoke
+ db_namespace['rollback'].invoke
+ db_namespace['migrate'].invoke
end
end
# desc 'Resets your database using your migrations for the current environment'
- task :reset => ["db:drop", "db:create", "db:migrate"]
+ task :reset => ['db:drop', 'db:create', 'db:migrate']
# desc 'Runs the "up" for a given migration VERSION.'
task :up => [:environment, :load_config] do
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required" unless version
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
+ raise 'VERSION is required' unless version
ActiveRecord::Migrator.run(:up, ActiveRecord::Migrator.migrations_paths, version)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
# desc 'Runs the "down" for a given migration VERSION.'
task :down => [:environment, :load_config] do
- version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
- raise "VERSION is required" unless version
+ version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil
+ raise 'VERSION is required' unless version
ActiveRecord::Migrator.run(:down, ActiveRecord::Migrator.migrations_paths, version)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
- desc "Display status of migrations"
+ desc 'Display status of migrations'
task :status => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
ActiveRecord::Base.establish_connection(config)
@@ -202,7 +208,7 @@ db_namespace = namespace :db do
end
# output
puts "\ndatabase: #{config['database']}\n\n"
- puts "#{"Status".center(8)} #{"Migration ID".ljust(14)} Migration Name"
+ puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
file_list.each do |file|
puts "#{file[0].center(8)} #{file[1].ljust(14)} #{file[2].humanize}"
@@ -218,14 +224,14 @@ db_namespace = namespace :db do
task :rollback => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
# desc 'Pushes the schema to the next version (specify steps w/ STEP=n).'
task :forward => [:environment, :load_config] do
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
ActiveRecord::Migrator.forward(ActiveRecord::Migrator.migrations_paths, step)
- db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby
+ db_namespace['schema:dump'].invoke if ActiveRecord::Base.schema_format == :ruby
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
@@ -238,10 +244,10 @@ db_namespace = namespace :db do
when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
- when 'postgresql'
+ when /postgresql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.encoding
- when 'sqlite3'
+ when /sqlite/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.encoding
else
@@ -261,7 +267,7 @@ db_namespace = namespace :db do
end
end
- desc "Retrieves the current schema version number"
+ desc 'Retrieves the current schema version number'
task :version => :environment do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
@@ -295,11 +301,11 @@ db_namespace = namespace :db do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
- base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
- fixtures_dir = ENV['FIXTURES_DIR'] ? File.join(base_dir, ENV['FIXTURES_DIR']) : base_dir
+ base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
+ fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
- Fixtures.create_fixtures(fixtures_dir, fixture_file)
+ ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
@@ -307,16 +313,16 @@ db_namespace = namespace :db do
task :identify => :environment do
require 'active_record/fixtures'
- label, id = ENV["LABEL"], ENV["ID"]
- raise "LABEL or ID required" if label.blank? && id.blank?
+ label, id = ENV['LABEL'], ENV['ID']
+ raise 'LABEL or ID required' if label.blank? && id.blank?
- puts %Q(The fixture ID for "#{label}" is #{Fixtures.identify(label)}.) if label
+ puts %Q(The fixture ID for "#{label}" is #{ActiveRecord::Fixtures.identify(label)}.) if label
base_dir = ENV['FIXTURES_PATH'] ? File.join(Rails.root, ENV['FIXTURES_PATH']) : File.join(Rails.root, 'test', 'fixtures')
Dir["#{base_dir}/**/*.yml"].each do |file|
if data = YAML::load(ERB.new(IO.read(file)).result)
data.keys.each do |key|
- key_id = Fixtures.identify(key)
+ key_id = ActiveRecord::Fixtures.identify(key)
if key == label || key_id == id.to_i
puts "#{file}: #{key} (#{key_id})"
@@ -328,16 +334,16 @@ db_namespace = namespace :db do
end
namespace :schema do
- desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
+ desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
task :dump => :load_config do
require 'active_record/schema_dumper'
File.open(ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb", "w") do |file|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
- db_namespace["schema:dump"].reenable
+ db_namespace['schema:dump'].reenable
end
- desc "Load a schema.rb file into the database"
+ desc 'Load a schema.rb file into the database'
task :load => :environment do
file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
@@ -349,29 +355,29 @@ db_namespace = namespace :db do
end
namespace :structure do
- desc "Dump the database structure to an SQL file"
+ desc 'Dump the database structure to an SQL file'
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
- case abcs[Rails.env]["adapter"]
- when /mysql/, "oci", "oracle"
+ case abcs[Rails.env]['adapter']
+ when /mysql/, 'oci', 'oracle'
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when "postgresql"
- ENV['PGHOST'] = abcs[Rails.env]["host"] if abcs[Rails.env]["host"]
- ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]["port"]
- ENV['PGPASSWORD'] = abcs[Rails.env]["password"].to_s if abcs[Rails.env]["password"]
- search_path = abcs[Rails.env]["schema_search_path"]
+ when /postgresql/
+ ENV['PGHOST'] = abcs[Rails.env]['host'] if abcs[Rails.env]['host']
+ ENV['PGPORT'] = abcs[Rails.env]["port"].to_s if abcs[Rails.env]['port']
+ ENV['PGPASSWORD'] = abcs[Rails.env]['password'].to_s if abcs[Rails.env]['password']
+ search_path = abcs[Rails.env]['schema_search_path']
unless search_path.blank?
search_path = search_path.split(",").map{|search_path| "--schema=#{search_path.strip}" }.join(" ")
end
- `pg_dump -i -U "#{abcs[Rails.env]["username"]}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]["database"]}`
- raise "Error dumping database" if $?.exitstatus == 1
- when "sqlite", "sqlite3"
- dbfile = abcs[Rails.env]["database"] || abcs[Rails.env]["dbfile"]
- `#{abcs[Rails.env]["adapter"]} #{dbfile} .schema > db/#{Rails.env}_structure.sql`
- when "sqlserver"
- `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /f db\\#{Rails.env}_structure.sql /q /A /r`
- `scptxfr /s #{abcs[Rails.env]["host"]} /d #{abcs[Rails.env]["database"]} /I /F db\ /q /A /r`
+ `pg_dump -i -U "#{abcs[Rails.env]['username']}" -s -x -O -f db/#{Rails.env}_structure.sql #{search_path} #{abcs[Rails.env]['database']}`
+ raise 'Error dumping database' if $?.exitstatus == 1
+ when /sqlite/
+ dbfile = abcs[Rails.env]['database'] || abcs[Rails.env]['dbfile']
+ `sqlite3 #{dbfile} .schema > db/#{Rails.env}_structure.sql`
+ when 'sqlserver'
+ `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /f db\\#{Rails.env}_structure.sql /q /A /r`
+ `scptxfr /s #{abcs[Rails.env]['host']} /d #{abcs[Rails.env]['database']} /I /F db\ /q /A /r`
when "firebird"
set_firebird_env(abcs[Rails.env])
db_string = firebird_db_string(abcs[Rails.env])
@@ -391,81 +397,81 @@ db_namespace = namespace :db do
task :load => 'db:test:purge' do
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
ActiveRecord::Schema.verbose = false
- db_namespace["schema:load"].invoke
+ db_namespace['schema:load'].invoke
end
# desc "Recreate the test database from the current environment's database schema"
task :clone => %w(db:schema:dump db:test:load)
# desc "Recreate the test databases from the development structure"
- task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
+ task :clone_structure => [ 'db:structure:dump', 'db:test:purge' ] do
abcs = ActiveRecord::Base.configurations
- case abcs["test"]["adapter"]
+ case abcs['test']['adapter']
when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split("\n\n").each do |table|
ActiveRecord::Base.connection.execute(table)
end
- when "postgresql"
- ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"]
- ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"]
- ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"]
- `psql -U "#{abcs["test"]["username"]}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs["test"]["database"]} #{abcs["test"]["template"]}`
- when "sqlite", "sqlite3"
- dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
- `#{abcs["test"]["adapter"]} #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
- when "sqlserver"
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
- when "oci", "oracle"
+ when /postgresql/
+ ENV['PGHOST'] = abcs['test']['host'] if abcs['test']['host']
+ ENV['PGPORT'] = abcs['test']['port'].to_s if abcs['test']['port']
+ ENV['PGPASSWORD'] = abcs['test']['password'].to_s if abcs['test']['password']
+ `psql -U "#{abcs['test']['username']}" -f #{Rails.root}/db/#{Rails.env}_structure.sql #{abcs['test']['database']} #{abcs['test']['template']}`
+ when /sqlite/
+ dbfile = abcs['test']['database'] || abcs['test']['dbfile']
+ `sqlite3 #{dbfile} < #{Rails.root}/db/#{Rails.env}_structure.sql`
+ when 'sqlserver'
+ `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql`
+ when 'oci', 'oracle'
ActiveRecord::Base.establish_connection(:test)
IO.readlines("#{Rails.root}/db/#{Rails.env}_structure.sql").join.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
- when "firebird"
- set_firebird_env(abcs["test"])
- db_string = firebird_db_string(abcs["test"])
+ when 'firebird'
+ set_firebird_env(abcs['test'])
+ db_string = firebird_db_string(abcs['test'])
sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}"
else
- raise "Task not supported by '#{abcs["test"]["adapter"]}'"
+ raise "Task not supported by '#{abcs['test']['adapter']}'"
end
end
# desc "Empty the test database"
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
- case abcs["test"]["adapter"]
+ case abcs['test']['adapter']
when /mysql/
ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
- when "postgresql"
+ ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], abcs['test'])
+ when /postgresql/
ActiveRecord::Base.clear_active_connections!
drop_database(abcs['test'])
create_database(abcs['test'])
- when "sqlite","sqlite3"
- dbfile = abcs["test"]["database"] || abcs["test"]["dbfile"]
+ when /sqlite/
+ dbfile = abcs['test']['database'] || abcs['test']['dbfile']
File.delete(dbfile) if File.exist?(dbfile)
- when "sqlserver"
- dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-')
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}`
- `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{Rails.env}_structure.sql`
+ when 'sqlserver'
+ dropfkscript = "#{abcs['test']['host']}.#{abcs['test']['database']}.DP1".gsub(/\\/,'-')
+ `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{dropfkscript}`
+ `osql -E -S #{abcs['test']['host']} -d #{abcs['test']['database']} -i db\\#{Rails.env}_structure.sql`
when "oci", "oracle"
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.structure_drop.split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
- when "firebird"
+ when 'firebird'
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database!
else
- raise "Task not supported by '#{abcs["test"]["adapter"]}'"
+ raise "Task not supported by '#{abcs['test']['adapter']}'"
end
end
# desc 'Check for pending migrations and load the test schema'
task :prepare => 'db:abort_if_pending_migrations' do
if defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
- db_namespace[{ :sql => "test:clone_structure", :ruby => "test:load" }[ActiveRecord::Base.schema_format]].invoke
+ db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke
end
end
end
@@ -473,11 +479,11 @@ db_namespace = namespace :db do
namespace :sessions do
# desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
task :create => :environment do
- raise "Task unavailable to this database (no migration support)" unless ActiveRecord::Base.connection.supports_migrations?
+ raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
require 'rails/generators'
Rails::Generators.configure!
require 'rails/generators/rails/session_migration/session_migration_generator'
- Rails::Generators::SessionMigrationGenerator.start [ ENV["MIGRATION"] || "add_sessions_table" ]
+ Rails::Generators::SessionMigrationGenerator.start [ ENV['MIGRATION'] || 'add_sessions_table' ]
end
# desc "Clear the sessions table"
@@ -490,13 +496,13 @@ end
namespace :railties do
namespace :install do
# desc "Copies missing migrations from Railties (e.g. plugins, engines). You can specify Railties to use with FROM=railtie1,railtie2"
- task :migrations => :"db:load_config" do
- to_load = ENV["FROM"].blank? ? :all : ENV["FROM"].split(",").map {|n| n.strip }
+ task :migrations => :'db:load_config' do
+ to_load = ENV['FROM'].blank? ? :all : ENV['FROM'].split(",").map {|n| n.strip }
railties = {}
Rails.application.railties.all do |railtie|
next unless to_load == :all || to_load.include?(railtie.railtie_name)
- if railtie.respond_to?(:paths) && (path = railtie.paths["db/migrate"].first)
+ if railtie.respond_to?(:paths) && (path = railtie.paths['db/migrate'].first)
railties[railtie.railtie_name] = path
end
end
@@ -522,13 +528,13 @@ def drop_database(config)
when /mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
- when /^sqlite/
+ when /sqlite/
require 'pathname'
path = Pathname.new(config['database'])
file = path.absolute? ? path.to_s : File.join(Rails.root, path)
FileUtils.rm(file)
- when 'postgresql'
+ when /postgresql/
ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
ActiveRecord::Base.connection.drop_database config['database']
end
@@ -539,8 +545,8 @@ def session_table_name
end
def set_firebird_env(config)
- ENV["ISC_USER"] = config["username"].to_s if config["username"]
- ENV["ISC_PASSWORD"] = config["password"].to_s if config["password"]
+ ENV['ISC_USER'] = config['username'].to_s if config['username']
+ ENV['ISC_PASSWORD'] = config['password'].to_s if config['password']
end
def firebird_db_string(config)
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 359f9d8a66..ae9afad48a 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -56,11 +56,11 @@ module ActiveRecord
end
substitutes.each_with_index do |tuple, i|
- tuple[1] = conn.substitute_at(tuple.first, 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 substitutes
end
@@ -220,7 +220,7 @@ module ActiveRecord
stmt.take limit if limit
stmt.order(*order)
stmt.key = table[primary_key]
- @klass.connection.update stmt.to_sql
+ @klass.connection.update stmt.to_sql, 'SQL', bind_values
end
end
@@ -338,7 +338,9 @@ module ActiveRecord
where(conditions).delete_all
else
statement = arel.compile_delete
- affected = @klass.connection.delete statement.to_sql
+ affected = @klass.connection.delete(
+ statement.to_sql, 'SQL', bind_values)
+
reset
affected
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index aae257a0e7..57c9921ea8 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
@@ -277,8 +279,8 @@ module ActiveRecord
unless record
record = @klass.new do |r|
- r.send(:attributes=, protected_attributes_for_create, true) unless protected_attributes_for_create.empty?
- r.send(:attributes=, unprotected_attributes_for_create, false) unless unprotected_attributes_for_create.empty?
+ r.assign_attributes(protected_attributes_for_create)
+ r.assign_attributes(unprotected_attributes_for_create, :without_protection => true)
end
yield(record) if block_given?
record.save if match.instantiator == :create
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 982b3d7e9f..2814771002 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -25,7 +25,18 @@ module ActiveRecord
values = value.to_a.map { |x|
x.is_a?(ActiveRecord::Base) ? x.id : x
}
- attribute.in(values)
+
+ if values.include?(nil)
+ values = values.compact
+ if values.empty?
+ attribute.eq nil
+ else
+ attribute.in(values.compact).or attribute.eq(nil)
+ end
+ else
+ attribute.in(values)
+ end
+
when Range, Arel::Relation
attribute.in(value)
when ActiveRecord::Base
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index d73fce9fd0..de36dd20b3 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -30,7 +30,7 @@ module ActiveRecord
include ActiveModel::Validations
module ClassMethods
- # Creates an object just like Base.create but calls save! instead of save
+ # Creates an object just like Base.create but calls <tt>save!</tt> instead of +save+
# so an exception is raised if the record is invalid.
def create!(attributes = nil, &block)
if attributes.is_a?(Array)
@@ -44,13 +44,13 @@ module ActiveRecord
end
end
- # The validation process on save can be skipped by passing :validate => false. The regular Base#save method is
+ # The validation process on save can be skipped by passing <tt>:validate => false</tt>. The regular Base#save method is
# replaced with this when the validations module is mixed in, which it is by default.
def save(options={})
perform_validations(options) ? super : false
end
- # Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
+ # Attempts to save the record just like Base#save but will raise a +RecordInvalid+ exception instead of returning false
# if the record is not valid.
def save!(options={})
perform_validations(options) ? super : raise(RecordInvalid.new(self))
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)
diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb
index 0667be7d23..2c20dd997f 100644
--- a/activerecord/lib/active_record/version.rb
+++ b/activerecord/lib/active_record/version.rb
@@ -3,7 +3,7 @@ module ActiveRecord
MAJOR = 3
MINOR = 1
TINY = 0
- PRE = "beta"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
end