aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md25
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb29
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb2
-rw-r--r--activerecord/lib/active_record/model.rb12
-rw-r--r--activerecord/lib/active_record/railties/databases.rake60
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb42
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb20
-rw-r--r--activerecord/lib/active_record/sanitization.rb4
-rw-r--r--activerecord/lib/active_record/store.rb44
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb28
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb32
-rw-r--r--activerecord/test/cases/connection_pool_test.rb2
-rw-r--r--activerecord/test/cases/connection_specification/resolver_test.rb2
-rw-r--r--activerecord/test/cases/relation/where_test.rb61
-rw-r--r--activerecord/test/cases/store_test.rb14
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb21
-rw-r--r--activerecord/test/models/admin/user.rb10
-rw-r--r--activerecord/test/models/price_estimate.rb1
-rw-r--r--activerecord/test/models/treasure.rb3
-rw-r--r--activerecord/test/schema/schema.rb1
23 files changed, 321 insertions, 108 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 6f7b2cb108..8c99a3929e 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,5 +1,28 @@
## Rails 4.0.0 (unreleased) ##
+* You can now override the generated accessor methods for stored attributes
+ and reuse the original behavior with `read_store_attribute` and `write_store_attribute`,
+ which are counterparts to `read_attribute` and `write_attribute`.
+
+ *Matt Jones*
+
+* Accept belongs_to (including polymorphic) association keys in queries
+
+ The following queries are now equivalent:
+
+ Post.where(:author => author)
+ Post.where(:author_id => author)
+
+ PriceEstimate.where(:estimate_of => treasure)
+ PriceEstimate.where(:estimate_of_type => 'Treasure', :estimate_of_id => treasure)
+
+ *Peter Brown*
+
+* Use native `mysqldump` command instead of `structure_dump` method
+ when dumping the database structure to a sql file. Fixes #5547.
+
+ *kennyj*
+
* Attribute predicate methods, such as `article.title?`, will now raise
`ActiveModel::MissingAttributeError` if the attribute being queried for
truthiness was not read from the database, instead of just returning false.
@@ -21,7 +44,7 @@
*Jan Bernacki*
-* Fix bug when call `store_accessor` multiple times.
+* Fix bug when calling `store_accessor` multiple times.
Fixes #7532.
*Matt Jones*
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index ee8b816ef4..66132b7260 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -18,14 +18,8 @@ module ActiveRecord
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
#
- # This class has most of the basic instance methods removed, and delegates
- # unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
- # corner case, it even removes the +class+ method and that's why you get
- #
- # blog.posts.class # => Array
- #
- # though the object behind <tt>blog.posts</tt> is not an Array, but an
- # ActiveRecord::Associations::HasManyAssociation.
+ # This class delegates unknown methods to <tt>@target</tt> via
+ # <tt>method_missing</tt>.
#
# The <tt>@target</tt> object is not \loaded until needed. For example,
#
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 bf08459b3b..f42a5df75f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -5,14 +5,11 @@ require 'active_support/core_ext/module/deprecation'
module ActiveRecord
# Raised when a connection could not be obtained within the connection
- # acquisition timeout period.
+ # acquisition timeout period: because max connections in pool
+ # are in use.
class ConnectionTimeoutError < ConnectionNotEstablished
end
- # Raised when a connection pool is full and another connection is requested
- class PoolFullError < ConnectionNotEstablished
- end
-
module ConnectionAdapters
# Connection pool base class for managing Active Record database
# connections.
@@ -187,7 +184,11 @@ module ActiveRecord
return remove if any?
elapsed = Time.now - t0
- raise ConnectionTimeoutError if elapsed >= timeout
+ if elapsed >= timeout
+ msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
+ [timeout, elapsed]
+ raise ConnectionTimeoutError, msg
+ end
end
ensure
@num_waiting -= 1
@@ -350,12 +351,12 @@ module ActiveRecord
#
# If all connections are leased and the pool is at capacity (meaning the
# number of currently leased connections is greater than or equal to the
- # size limit set), an ActiveRecord::PoolFullError exception will be raised.
+ # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
#
# Returns: an AbstractAdapter object.
#
# Raises:
- # - PoolFullError: no connection can be obtained from the pool.
+ # - ConnectionTimeoutError: no connection can be obtained from the pool.
def checkout
synchronize do
conn = acquire_connection
@@ -416,22 +417,14 @@ module ActiveRecord
# queue for a connection to become available.
#
# Raises:
- # - PoolFullError if a connection could not be acquired (FIXME:
- # why not ConnectionTimeoutError?
+ # - ConnectionTimeoutError if a connection could not be acquired
def acquire_connection
if conn = @available.poll
conn
elsif @connections.size < @size
checkout_new_connection
else
- t0 = Time.now
- begin
- @available.poll(@checkout_timeout)
- rescue ConnectionTimeoutError
- msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
- [@checkout_timeout, Time.now - t0]
- raise PoolFullError, msg
- end
+ @available.poll(@checkout_timeout)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 0390168461..816b5e17c1 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -114,7 +114,7 @@ module ActiveRecord
case type
when :string, :text then var_name
- when :integer then "(#{var_name}.to_i rescue #{var_name} ? 1 : 0)"
+ when :integer then "(#{var_name}.to_i)"
when :float then "#{var_name}.to_f"
when :decimal then "#{klass}.value_to_decimal(#{var_name})"
when :datetime, :timestamp then "#{klass}.string_to_time(#{var_name})"
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index dd40351a38..b9a61f7d91 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -72,7 +72,7 @@ module ActiveRecord
:port => config.port,
:database => config.path.sub(%r{^/},""),
:host => config.host }
- spec.reject!{ |_,value| !value }
+ spec.reject!{ |_,value| value.blank? }
if config.query
options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
spec.merge!(options)
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 57553c29eb..44cde49bd5 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -101,9 +101,19 @@ module ActiveRecord
def abstract_class?
false
end
-
+
# Defines the name of the table column which will store the class name on single-table
# inheritance situations.
+ #
+ # The default inheritance column name is +type+, which means it's a
+ # reserved word inside Active Record. To be able to use single-table
+ # inheritance with another column name, or to use the column +type+ in
+ # your own model for something else, you can override this method to
+ # return a different name:
+ #
+ # def self.inheritance_column
+ # 'zoink'
+ # end
def inheritance_column
'type'
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index ae24542521..bcb26f72c8 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -18,9 +18,13 @@ db_namespace = namespace :db do
end
end
- desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
+ desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
task :create => [:load_config] do
- ActiveRecord::Tasks::DatabaseTasks.create_current
+ if ENV['DATABASE_URL']
+ ActiveRecord::Tasks::DatabaseTasks.create_database_url
+ else
+ ActiveRecord::Tasks::DatabaseTasks.create_current
+ end
end
namespace :drop do
@@ -29,9 +33,13 @@ db_namespace = namespace :db do
end
end
- desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
+ desc 'Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)'
task :drop => [:load_config] do
- ActiveRecord::Tasks::DatabaseTasks.drop_current
+ if ENV['DATABASE_URL']
+ ActiveRecord::Tasks::DatabaseTasks.drop_database_url
+ else
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
+ end
end
desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)."
@@ -88,8 +96,6 @@ db_namespace = namespace :db do
desc 'Display status of migrations'
task :status => [:environment, :load_config] do
- config = ActiveRecord::Base.configurations[Rails.env]
- ActiveRecord::Base.establish_connection(config)
unless ActiveRecord::Base.connection.table_exists?(ActiveRecord::Migrator.schema_migrations_table_name)
puts 'Schema migrations table does not exist yet.'
next # means "return" for rake task
@@ -110,7 +116,7 @@ db_namespace = namespace :db do
['up', version, '********** NO FILE **********']
end
# output
- puts "\ndatabase: #{config['database']}\n\n"
+ puts "\ndatabase: #{ActiveRecord::Base.connection_config[:database]}\n\n"
puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
puts "-" * 50
(db_list + file_list).sort_by {|migration| migration[1]}.each do |migration|
@@ -186,7 +192,6 @@ db_namespace = namespace :db do
task :load => [:environment, :load_config] do
require 'active_record/fixtures'
- ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
@@ -225,7 +230,6 @@ db_namespace = namespace :db do
require 'active_record/schema_dumper'
filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
File.open(filename, "w:utf-8") do |file|
- ActiveRecord::Base.establish_connection(Rails.env)
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
end
db_namespace['schema:dump'].reenable
@@ -277,22 +281,22 @@ db_namespace = namespace :db do
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
task :dump => [:environment, :load_config] do
- abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
- case abcs[Rails.env]['adapter']
+ current_config = ActiveRecord::Tasks::DatabaseTasks.current_config
+ case current_config['adapter']
when /mysql/, /postgresql/, /sqlite/
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(abcs[Rails.env], filename)
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename)
when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(abcs[Rails.env])
+ ActiveRecord::Base.establish_connection(current_config)
File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
when 'sqlserver'
- `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U`
+ `smoscript -s #{current_config['host']} -d #{current_config['database']} -u #{current_config['username']} -p #{current_config['password']} -f #{filename} -A -U`
when "firebird"
- set_firebird_env(abcs[Rails.env])
- db_string = firebird_db_string(abcs[Rails.env])
+ set_firebird_env(current_config)
+ db_string = firebird_db_string(current_config)
sh "isql -a #{db_string} > #{filename}"
else
- raise "Task not supported by '#{abcs[Rails.env]["adapter"]}'"
+ raise "Task not supported by '#{current_config["adapter"]}'"
end
if ActiveRecord::Base.connection.supports_migrations?
@@ -303,26 +307,24 @@ db_namespace = namespace :db do
# desc "Recreate the databases from the structure.sql file"
task :load => [:environment, :load_config] do
- env = ENV['RAILS_ENV'] || 'test'
-
- abcs = ActiveRecord::Base.configurations
+ current_config = ActiveRecord::Tasks::DatabaseTasks.current_config(:env => (ENV['RAILS_ENV'] || 'test'))
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
- case abcs[env]['adapter']
+ case current_config['adapter']
when /mysql/, /postgresql/, /sqlite/
- ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[env], filename)
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(current_config, filename)
when 'sqlserver'
- `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}`
+ `sqlcmd -S #{current_config['host']} -d #{current_config['database']} -U #{current_config['username']} -P #{current_config['password']} -i #{filename}`
when 'oci', 'oracle'
- ActiveRecord::Base.establish_connection(abcs[env])
+ ActiveRecord::Base.establish_connection(current_config)
IO.read(filename).split(";\n\n").each do |ddl|
ActiveRecord::Base.connection.execute(ddl)
end
when 'firebird'
- set_firebird_env(abcs[env])
- db_string = firebird_db_string(abcs[env])
+ set_firebird_env(current_config)
+ db_string = firebird_db_string(current_config)
sh "isql -i #{filename} #{db_string}"
else
- raise "Task not supported by '#{abcs[env]['adapter']}'"
+ raise "Task not supported by '#{current_config['adapter']}'"
end
end
@@ -353,10 +355,10 @@ db_namespace = namespace :db do
# desc "Recreate the test database from an existent structure.sql file"
task :load_structure => 'db:test:purge' do
begin
- old_env, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], 'test'
+ ActiveRecord::Tasks::DatabaseTasks.current_config(:config => ActiveRecord::Base.configurations['test'])
db_namespace["structure:load"].invoke
ensure
- ENV['RAILS_ENV'] = old_env
+ ActiveRecord::Tasks::DatabaseTasks.current_config(:config => nil)
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index cf949a893f..d9c65fa170 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -358,6 +358,10 @@ module ActiveRecord
end
end
+ def polymorphic?
+ options.key? :polymorphic
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index cb8f903474..36fc08e6ad 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -1,23 +1,51 @@
module ActiveRecord
class PredicateBuilder # :nodoc:
- def self.build_from_hash(engine, attributes, default_table)
- attributes.map do |column, value|
+ def self.build_from_hash(klass, attributes, default_table)
+ queries = []
+
+ attributes.each do |column, value|
table = default_table
if value.is_a?(Hash)
- table = Arel::Table.new(column, engine)
- value.map { |k,v| build(table[k.to_sym], v) }
+ table = Arel::Table.new(column, default_table.engine)
+ association = klass.reflect_on_association(column.to_sym)
+
+ value.each do |k, v|
+ queries.concat expand(association && association.klass, table, k, v)
+ end
else
column = column.to_s
if column.include?('.')
table_name, column = column.split('.', 2)
- table = Arel::Table.new(table_name, engine)
+ table = Arel::Table.new(table_name, default_table.engine)
end
- build(table[column.to_sym], value)
+ queries.concat expand(klass, table, column, value)
+ end
+ end
+
+ queries
+ end
+
+ def self.expand(klass, table, column, value)
+ queries = []
+
+ # Find the foreign key when using queries such as:
+ # Post.where(:author => author)
+ #
+ # For polymorphic relationships, find the foreign key and type:
+ # PriceEstimate.where(:estimate_of => treasure)
+ if klass && value.class < Model::Tag && reflection = klass.reflect_on_association(column.to_sym)
+ if reflection.polymorphic?
+ queries << build(table[reflection.foreign_type], value.class.base_class)
end
- end.flatten
+
+ column = reflection.foreign_key
+ end
+
+ queries << build(table[column.to_sym], value)
+ queries
end
def self.references(attributes)
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index f6bacf4822..3c59bd8a68 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -340,6 +340,24 @@ module ActiveRecord
# User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
# # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
#
+ # In the case of a belongs_to relationship, an association key can be used
+ # to specify the model if an ActiveRecord object is used as the value.
+ #
+ # author = Author.find(1)
+ #
+ # # The following queries will be equivalent:
+ # Post.where(:author => author)
+ # Post.where(:author_id => author)
+ #
+ # This also works with polymorphic belongs_to relationships:
+ #
+ # treasure = Treasure.create(:name => 'gold coins')
+ # treasure.price_estimates << PriceEstimate.create(:price => 125)
+ #
+ # # The following queries will be equivalent:
+ # PriceEstimate.where(:estimate_of => treasure)
+ # PriceEstimate.where(:estimate_of_type => 'Treasure', :estimate_of_id => treasure)
+ #
# === Joins
#
# If the relation is the result of a join, you may create a condition which uses any of the
@@ -690,7 +708,7 @@ module ActiveRecord
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
+ PredicateBuilder.build_from_hash(klass, attributes, table)
else
[opts]
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 5c74c07ad1..42b4cff4b8 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -88,8 +88,8 @@ module ActiveRecord
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
attrs = expand_hash_conditions_for_aggregates(attrs)
- table = Arel::Table.new(table_name).alias(default_table_name)
- PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
+ table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
+ PredicateBuilder.build_from_hash(self.class, attrs, table).map { |b|
connection.visitor.accept b
}.join(' AND ')
end
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 8ea0ea239f..df7f58c81f 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -37,6 +37,28 @@ module ActiveRecord
# The stored attribute names can be retrieved using +stored_attributes+.
#
# User.stored_attributes[:settings] # [:color, :homepage]
+ #
+ # == Overwriting default accessors
+ #
+ # All stored values are automatically available through accessors on the Active Record
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
+ # the default accessors (using the same name as the attribute) and calling
+ # <tt>read_store_attribute(store_attribute_name, attr_name)</tt> and
+ # <tt>write_store_attribute(store_attribute_name, attr_name, value)</tt> to actually
+ # change things.
+ #
+ # class Song < ActiveRecord::Base
+ # # Uses a stored integer to hold the volume adjustment of the song
+ # store :settings, accessors: [:volume_adjustment]
+ #
+ # def volume_adjustment=(decibels)
+ # write_store_attribute(:settings, :volume_adjustment, decibels.to_i)
+ # end
+ #
+ # def volume_adjustment
+ # read_store_attribute(:settings, :volume_adjustment).to_i
+ # end
+ # end
module Store
extend ActiveSupport::Concern
@@ -55,15 +77,11 @@ module ActiveRecord
keys = keys.flatten
keys.each do |key|
define_method("#{key}=") do |value|
- attribute = initialize_store_attribute(store_attribute)
- if value != attribute[key]
- send :"#{store_attribute}_will_change!"
- attribute[key] = value
- end
+ write_store_attribute(store_attribute, key, value)
end
define_method(key) do
- initialize_store_attribute(store_attribute)[key]
+ read_store_attribute(store_attribute, key)
end
end
@@ -72,6 +90,20 @@ module ActiveRecord
end
end
+ protected
+ def read_store_attribute(store_attribute, key)
+ attribute = initialize_store_attribute(store_attribute)
+ attribute[key]
+ end
+
+ def write_store_attribute(store_attribute, key, value)
+ attribute = initialize_store_attribute(store_attribute)
+ if value != attribute[key]
+ send :"#{store_attribute}_will_change!"
+ attribute[key] = value
+ end
+ end
+
private
def initialize_store_attribute(store_attribute)
attribute = send(store_attribute)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index b41cc68b6a..fda51b3d76 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -3,6 +3,8 @@ module ActiveRecord
module DatabaseTasks # :nodoc:
extend self
+ attr_writer :current_config
+
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
def register_task(pattern, task)
@@ -14,6 +16,19 @@ module ActiveRecord
register_task(/postgresql/, ActiveRecord::Tasks::PostgreSQLDatabaseTasks)
register_task(/sqlite/, ActiveRecord::Tasks::SQLiteDatabaseTasks)
+ def current_config(options = {})
+ options.reverse_merge! :env => Rails.env
+ if options.has_key?(:config)
+ @current_config = options[:config]
+ else
+ @current_config ||= if ENV['DATABASE_URL']
+ database_url_config
+ else
+ ActiveRecord::Base.configurations[options[:env]]
+ end
+ end
+ end
+
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).create
@@ -33,6 +48,10 @@ module ActiveRecord
ActiveRecord::Base.establish_connection environment
end
+ def create_database_url
+ create database_url_config
+ end
+
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
@@ -51,6 +70,10 @@ module ActiveRecord
}
end
+ def drop_database_url
+ drop database_url_config
+ end
+
def charset_current(environment = Rails.env)
charset ActiveRecord::Base.configurations[environment]
end
@@ -87,6 +110,11 @@ module ActiveRecord
private
+ def database_url_config
+ @database_url_config ||=
+ ConnectionAdapters::ConnectionSpecification::Resolver.new(ENV["DATABASE_URL"], {}).spec.config.stringify_keys
+ end
+
def class_for_adapter(adapter)
key = @tasks.keys.detect { |pattern| adapter[pattern] }
@tasks[key]
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 85d08402f9..2340f949b7 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -27,7 +27,7 @@ module ActiveRecord
rescue error_class => error
$stderr.puts error.error
$stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
- $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset']
+ $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['encoding']
end
def drop
@@ -49,19 +49,17 @@ module ActiveRecord
end
def structure_dump(filename)
- establish_connection configuration
- File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ args = prepare_command_options('mysqldump')
+ args.concat(["--result-file", "#{filename}"])
+ args.concat(["--no-data"])
+ args.concat(["#{configuration['database']}"])
+ Kernel.system(*args)
end
def structure_load(filename)
- args = ['mysql']
- args.concat(['--user', configuration['username']]) if configuration['username']
- args << "--password=#{configuration['password']}" if configuration['password']
- args.concat(['--default-character-set', configuration['charset']]) if configuration['charset']
- configuration.slice('host', 'port', 'socket', 'database').each do |k, v|
- args.concat([ "--#{k}", v ]) if v
- end
+ args = prepare_command_options('mysql')
args.concat(['--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])
+ args.concat(["--database", "#{configuration['database']}"])
Kernel.system(*args)
end
@@ -77,7 +75,7 @@ module ActiveRecord
def creation_options
{
- charset: (configuration['charset'] || DEFAULT_CHARSET),
+ charset: (configuration['encoding'] || DEFAULT_CHARSET),
collation: (configuration['collation'] || DEFAULT_COLLATION)
}
end
@@ -113,6 +111,18 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
$stdout.print "Please provide the root password for your mysql installation\n>"
$stdin.gets.strip
end
+
+ def prepare_command_options(command)
+ args = [command]
+ args.concat(['--user', configuration['username']]) if configuration['username']
+ args << "--password=#{configuration['password']}" if configuration['password']
+ args.concat(['--default-character-set', configuration['encoding']]) if configuration['encoding']
+ configuration.slice('host', 'port', 'socket').each do |k, v|
+ args.concat([ "--#{k}", v ]) if v
+ end
+ args
+ end
+
end
end
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index 8287b35aaf..0718d0886f 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -89,7 +89,7 @@ module ActiveRecord
end
def test_full_pool_exception
- assert_raises(PoolFullError) do
+ assert_raises(ConnectionTimeoutError) do
(@pool.size + 1).times do
@pool.checkout
end
diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb
index 673a2b2b88..434d2b7ba5 100644
--- a/activerecord/test/cases/connection_specification/resolver_test.rb
+++ b/activerecord/test/cases/connection_specification/resolver_test.rb
@@ -13,7 +13,6 @@ module ActiveRecord
spec = resolve 'mysql://foo?encoding=utf8'
assert_equal({
:adapter => "mysql",
- :database => "",
:host => "foo",
:encoding => "utf8" }, spec)
end
@@ -33,7 +32,6 @@ module ActiveRecord
spec = resolve 'mysql://foo:123?encoding=utf8'
assert_equal({
:adapter => "mysql",
- :database => "",
:port => 123,
:host => "foo",
:encoding => "utf8" }, spec)
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index 90c690e266..3163cf79ad 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -1,10 +1,71 @@
require "cases/helper"
+require 'models/author'
+require 'models/price_estimate'
+require 'models/treasure'
require 'models/post'
+require 'models/comment'
module ActiveRecord
class WhereTest < ActiveRecord::TestCase
fixtures :posts
+ def test_belongs_to_shallow_where
+ author = Author.new
+ author.id = 1
+
+ assert_equal Post.where(author_id: 1).to_sql, Post.where(author: author).to_sql
+ end
+
+ def test_belongs_to_nested_where
+ parent = Comment.new
+ parent.id = 1
+
+ expected = Post.where(comments: { parent_id: 1 }).joins(:comments)
+ actual = Post.where(comments: { parent: parent }).joins(:comments)
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
+ def test_polymorphic_shallow_where
+ treasure = Treasure.new
+ treasure.id = 1
+
+ expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1)
+ actual = PriceEstimate.where(estimate_of: treasure)
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
+ def test_polymorphic_sti_shallow_where
+ treasure = HiddenTreasure.new
+ treasure.id = 1
+
+ expected = PriceEstimate.where(estimate_of_type: 'Treasure', estimate_of_id: 1)
+ actual = PriceEstimate.where(estimate_of: treasure)
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
+ def test_polymorphic_nested_where
+ thing = Post.new
+ thing.id = 1
+
+ expected = Treasure.where(price_estimates: { thing_type: 'Post', thing_id: 1 }).joins(:price_estimates)
+ actual = Treasure.where(price_estimates: { thing: thing }).joins(:price_estimates)
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
+ def test_polymorphic_sti_nested_where
+ treasure = HiddenTreasure.new
+ treasure.id = 1
+
+ expected = Treasure.where(price_estimates: { estimate_of_type: 'Treasure', estimate_of_id: 1 }).joins(:price_estimates)
+ actual = Treasure.where(price_estimates: { estimate_of: treasure }).joins(:price_estimates)
+
+ assert_equal expected.to_sql, actual.to_sql
+ end
+
def test_where_error
assert_raises(ActiveRecord::StatementInvalid) do
Post.where(:id => { 'posts.author_id' => 10 }).first
diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb
index 2741f223da..dc47d40f41 100644
--- a/activerecord/test/cases/store_test.rb
+++ b/activerecord/test/cases/store_test.rb
@@ -29,6 +29,12 @@ class StoreTest < ActiveRecord::TestCase
assert_equal 'graeters', @john.reload.settings[:icecream]
end
+ test "overriding a read accessor" do
+ @john.settings[:phone_number] = '1234567890'
+
+ assert_equal '(123) 456-7890', @john.phone_number
+ end
+
test "updating the store will mark it as changed" do
@john.color = 'red'
assert @john.settings_changed?
@@ -54,6 +60,12 @@ class StoreTest < ActiveRecord::TestCase
assert_equal false, @john.remember_login
end
+ test "overriding a write accessor" do
+ @john.phone_number = '(123) 456-7890'
+
+ assert_equal '1234567890', @john.settings[:phone_number]
+ end
+
test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do
@john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy')
@john.height = 'low'
@@ -124,7 +136,7 @@ class StoreTest < ActiveRecord::TestCase
end
test "all stored attributes are returned" do
- assert_equal [:color, :homepage, :favorite_food], Admin::User.stored_attributes[:settings]
+ assert_equal [:color, :homepage, :favorite_food, :phone_number], Admin::User.stored_attributes[:settings]
end
test "stores_attributes are class level settings" do
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index be591da8d6..46b97a1274 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -32,7 +32,7 @@ module ActiveRecord
with('my-app-db', {:charset => 'latin', :collation => 'latin_ci'})
ActiveRecord::Tasks::DatabaseTasks.create @configuration.merge(
- 'charset' => 'latin', 'collation' => 'latin_ci'
+ 'encoding' => 'latin', 'collation' => 'latin_ci'
)
end
@@ -176,7 +176,7 @@ module ActiveRecord
with('test-db', {:charset => 'latin', :collation => 'latin_ci'})
ActiveRecord::Tasks::DatabaseTasks.purge @configuration.merge(
- 'charset' => 'latin', 'collation' => 'latin_ci'
+ 'encoding' => 'latin', 'collation' => 'latin_ci'
)
end
end
@@ -219,44 +219,31 @@ module ActiveRecord
class MySQLStructureDumpTest < ActiveRecord::TestCase
def setup
- @connection = stub(:structure_dump => true)
@configuration = {
'adapter' => 'mysql',
'database' => 'test-db'
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
end
def test_structure_dump
filename = "awesome-file.sql"
- ActiveRecord::Base.expects(:establish_connection).with(@configuration)
- @connection.expects(:structure_dump)
+ Kernel.expects(:system).with("mysqldump", "--result-file", filename, "--no-data", "test-db")
ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)
- assert File.exists?(filename)
- ensure
- FileUtils.rm(filename)
end
end
class MySQLStructureLoadTest < ActiveRecord::TestCase
def setup
- @connection = stub
@configuration = {
'adapter' => 'mysql',
'database' => 'test-db'
}
-
- ActiveRecord::Base.stubs(:connection).returns(@connection)
- ActiveRecord::Base.stubs(:establish_connection).returns(true)
- Kernel.stubs(:system)
end
def test_structure_load
filename = "awesome-file.sql"
- Kernel.expects(:system).with('mysql', '--database', 'test-db', '--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1})
+ Kernel.expects(:system).with('mysql', '--execute', %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db")
ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)
end
diff --git a/activerecord/test/models/admin/user.rb b/activerecord/test/models/admin/user.rb
index 6c4eb03b06..35170faa76 100644
--- a/activerecord/test/models/admin/user.rb
+++ b/activerecord/test/models/admin/user.rb
@@ -1,8 +1,16 @@
class Admin::User < ActiveRecord::Base
belongs_to :account
store :settings, :accessors => [ :color, :homepage ]
- store_accessor :settings, :favorite_food
+ store_accessor :settings, :favorite_food, :phone_number
store :preferences, :accessors => [ :remember_login ]
store :json_data, :accessors => [ :height, :weight ], :coder => JSON
store :json_data_empty, :accessors => [ :is_a_good_guy ], :coder => JSON
+
+ def phone_number
+ read_store_attribute(:settings, :phone_number).gsub(/(\d{3})(\d{3})(\d{4})/,'(\1) \2-\3')
+ end
+
+ def phone_number=(value)
+ write_store_attribute(:settings, :phone_number, value && value.gsub(/[^\d]/,''))
+ end
end
diff --git a/activerecord/test/models/price_estimate.rb b/activerecord/test/models/price_estimate.rb
index ef3bba36a9..d09e2a88a3 100644
--- a/activerecord/test/models/price_estimate.rb
+++ b/activerecord/test/models/price_estimate.rb
@@ -1,3 +1,4 @@
class PriceEstimate < ActiveRecord::Base
belongs_to :estimate_of, :polymorphic => true
+ belongs_to :thing, polymorphic: true
end
diff --git a/activerecord/test/models/treasure.rb b/activerecord/test/models/treasure.rb
index 2a98e74f2c..e864295acf 100644
--- a/activerecord/test/models/treasure.rb
+++ b/activerecord/test/models/treasure.rb
@@ -6,3 +6,6 @@ class Treasure < ActiveRecord::Base
accepts_nested_attributes_for :looter
end
+
+class HiddenTreasure < Treasure
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index b4e611cb09..798ea20efc 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -693,6 +693,7 @@ ActiveRecord::Schema.define do
create_table :treasures, :force => true do |t|
t.column :name, :string
+ t.column :type, :string
t.column :looter_id, :integer
t.column :looter_type, :string
end