diff options
Diffstat (limited to 'activerecord')
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 |