From 17a79437485077a54e65c0d493960c113c419721 Mon Sep 17 00:00:00 2001 From: Cyril Wack Date: Tue, 10 May 2011 15:42:28 -0700 Subject: Use quotes for command substitution --- activerecord/lib/active_record/railties/databases.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 85ad43b35f..bcdabb0f31 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -282,7 +282,7 @@ db_namespace = namespace :db do pending_migrations.each do |pending_migration| puts ' %4d %s' % [pending_migration.version, pending_migration.name] end - abort %{Run "rake db:migrate" to update your database then try again.} + abort %{Run `rake db:migrate` to update your database then try again.} end end end @@ -349,7 +349,7 @@ db_namespace = namespace :db do if File.exists?(file) load(file) else - abort %{#{file} doesn't exist yet. Run "rake db:migrate" to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} + abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} end end end -- cgit v1.2.3 From 993e8c55ca24f0d41364614a0ae3a7d42e216e94 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 20 May 2011 13:09:32 +0900 Subject: No "t." for the migration DSL! Now you can omit |t| block parameter and all the t. from your migration code, that means, the syntax looks more Rails-3-ish, like the routes DSL and ActionMailer DSL. Also, this change won't break any of your existing migration files, since the traditional syntax is still available. --- .../connection_adapters/abstract/schema_statements.rb | 4 ++-- .../connection_adapters/sqlite_adapter.rb | 18 ++++++++++-------- activerecord/lib/active_record/session_store.rb | 7 ++++--- 3 files changed, 16 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') 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 9f9c2c42cb..21612dd15b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -154,11 +154,11 @@ module ActiveRecord # ) # # See also TableDefinition#column for details on how to create columns. - def create_table(table_name, options = {}) + def create_table(table_name, options = {}, &blk) td = table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false - yield td if block_given? + td.instance_eval(&blk) if blk if options[:force] && table_exists?(table_name) drop_table(table_name, options) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index d2785b234a..4b25384664 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -394,27 +394,29 @@ module ActiveRecord drop_table(from) end - def copy_table(from, to, options = {}) #:nodoc: - options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) + def copy_table(from, to, options = {}, &block) #:nodoc: + from_columns, from_primary_key = columns(from), primary_key(from) + options = options.merge(:id => (!from_columns.detect {|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) + table_definition = nil create_table(to, options) do |definition| - @definition = definition - columns(from).each do |column| + table_definition = definition + from_columns.each do |column| column_name = options[:rename] ? (options[:rename][column.name] || options[:rename][column.name.to_sym] || column.name) : column.name - @definition.column(column_name, column.type, + table_definition.column(column_name, column.type, :limit => column.limit, :default => column.default, :null => column.null) end - @definition.primary_key(primary_key(from)) if primary_key(from) - yield @definition if block_given? + table_definition.primary_key from_primary_key if from_primary_key + table_definition.instance_eval(&block) if block end copy_table_indexes(from, to, options[:rename] || {}) copy_table_contents(from, to, - @definition.columns.map {|column| column.name}, + table_definition.columns.map {|column| column.name}, options[:rename] || {}) end diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index c3e976002e..7bbac1505e 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -64,12 +64,13 @@ module ActiveRecord end def create_table! + id_col_name, data_col_name = session_id_column, data_column_name connection_pool.clear_table_cache!(table_name) connection.create_table(table_name) do |t| - t.string session_id_column, :limit => 255 - t.text data_column_name + t.string id_col_name, :limit => 255 + t.text data_col_name end - connection.add_index table_name, session_id_column, :unique => true + connection.add_index table_name, id_col_name, :unique => true end end -- cgit v1.2.3 From a982443ae5bd12535405dbdb40f27df2d612256e Mon Sep 17 00:00:00 2001 From: Daniel Schierbeck Date: Sat, 9 Jul 2011 14:24:28 +0200 Subject: Make #extract_schema_and_table an instance method in Utils Also, move the utils test into its own test case. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a84f73c73f..df753d087c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -948,6 +948,8 @@ module ActiveRecord end module Utils + extend self + # Returns an array of [schema_name, table_name] extracted from +name+. # +schema_name+ is nil if not specified in +name+. # +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+) @@ -958,7 +960,7 @@ module ActiveRecord # * schema_name.table_name # * schema_name."table.name" # * "schema.name"."table name" - def self.extract_schema_and_table(name) + def extract_schema_and_table(name) table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse [schema, table] end -- cgit v1.2.3 From 971a74b81f37ec82e06056b738a7215185c51ee6 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 28 Jul 2011 13:14:11 +0100 Subject: Revert "Merge pull request #2309 from smasry/master" This reverts commit 9d396ee8195e31f646e0b89158ed96f4db4ab38f, reversing changes made to fa2bfd832c1d1e997d93c2269a485cc74782c86d. Reason: the change broke the build. --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 3a7b245c51..1654ae1eac 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -312,7 +312,7 @@ module ActiveRecord when String, Symbol o.to_s.split(',').collect do |s| s.strip! - (s if s =~ /\(/) || s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') end else o -- cgit v1.2.3 From 6dc749596c328c44c80f898d5fa860fff6cab783 Mon Sep 17 00:00:00 2001 From: Pete Campbell Date: Thu, 28 Jul 2011 09:44:51 -0400 Subject: Explicitly included hashes in sentence regarding SQL-injection-safe forms --- activerecord/lib/active_record/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4136868b39..461df0555f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -63,9 +63,9 @@ module ActiveRecord #:nodoc: # == Conditions # # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. - # The array form is to be used when the condition input is tainted and requires sanitization. The string form can - # be used for statements that don't involve tainted data. The hash form works much like the array form, except - # only equality and range is possible. Examples: + # The array form is to be used when the condition input is tainted and requires sanitization. The string and hash + # forms can be used for statements that don't involve tainted data. The hash form works much like the array form, + # except only equality and range is possible. Examples: # # class User < ActiveRecord::Base # def self.authenticate_unsafely(user_name, password) -- cgit v1.2.3 From 3d6e1872550f284dd387ffc87a984a9036376062 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Jul 2011 12:23:37 -0700 Subject: dump IO encoding value along with schema.rb so the file can be reloaded. fixes #1592 --- activerecord/lib/active_record/schema_dumper.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 19585f6214..6fe305f843 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -40,6 +40,10 @@ module ActiveRecord def header(stream) define_params = @version ? ":version => #{@version}" : "" + if stream.respond_to?(:external_encoding) + stream.puts "# encoding: #{stream.external_encoding.name}" + end + stream.puts <
Date: Fri, 29 Jul 2011 12:28:12 -0700 Subject: default writing the schema file as utf-8 --- activerecord/lib/active_record/railties/databases.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 0ee7e20cf1..ec00f7faad 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -341,7 +341,8 @@ db_namespace = namespace :db do 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| + 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 -- cgit v1.2.3 From 74d7bfb200e4590e244558554e147a31d30990df Mon Sep 17 00:00:00 2001 From: Christopher Meiklejohn Date: Fri, 29 Jul 2011 21:26:21 -0400 Subject: Support backwards compatible interface for migration down/up with rails 3.0.x. --- activerecord/lib/active_record/migration.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 507f345ef5..9307d7ef24 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -329,6 +329,7 @@ module ActiveRecord end def self.method_missing(name, *args, &block) # :nodoc: + self.delegate = self.new (delegate || superclass.delegate).send(name, *args, &block) end -- cgit v1.2.3 From 3a29cc341205b7da30c537f31eefb18ede51bb4d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 2 Aug 2011 20:01:38 -0700 Subject: add a migrate class method and delegate to the new instance --- activerecord/lib/active_record/migration.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 9307d7ef24..fa1b303fc7 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -329,10 +329,13 @@ module ActiveRecord end def self.method_missing(name, *args, &block) # :nodoc: - self.delegate = self.new (delegate || superclass.delegate).send(name, *args, &block) end + def self.migrate(direction) + new.migrate direction + end + cattr_accessor :verbose attr_accessor :name, :version -- cgit v1.2.3 From 86b7d83f1ce81284cad37781667a5f204be22273 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 3 Aug 2011 08:57:52 -0700 Subject: initializing @open_transactions in the initialize method --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 65024d76f8..bde31d1cda 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -43,6 +43,7 @@ module ActiveRecord @connection, @logger = connection, logger @query_cache_enabled = false @query_cache = Hash.new { |h,sql| h[sql] = {} } + @open_transactions = 0 @instrumenter = ActiveSupport::Notifications.instrumenter end @@ -177,12 +178,9 @@ module ActiveRecord @connection end - def open_transactions - @open_transactions ||= 0 - end + attr_reader :open_transactions def increment_open_transactions - @open_transactions ||= 0 @open_transactions += 1 end -- cgit v1.2.3 From b386951e4281f1a393a7448d5cccf84843ddac30 Mon Sep 17 00:00:00 2001 From: artemk Date: Thu, 4 Aug 2011 00:34:13 +0300 Subject: accept option for recreate db for postgres (same as mysql now) --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a84f73c73f..aefe69f8ed 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -614,9 +614,11 @@ module ActiveRecord # SCHEMA STATEMENTS ======================================== - def recreate_database(name) #:nodoc: + # 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) + create_database(name, options) end # Create a new PostgreSQL database. Options include :owner, :template, -- cgit v1.2.3 From 3c3f8087647a15a5e88dd18a45d41358eacce142 Mon Sep 17 00:00:00 2001 From: Pete Campbell Date: Thu, 28 Jul 2011 09:44:51 -0400 Subject: Explicitly included hashes in sentence regarding SQL-injection-safe forms --- activerecord/lib/active_record/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4136868b39..461df0555f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -63,9 +63,9 @@ module ActiveRecord #:nodoc: # == Conditions # # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. - # The array form is to be used when the condition input is tainted and requires sanitization. The string form can - # be used for statements that don't involve tainted data. The hash form works much like the array form, except - # only equality and range is possible. Examples: + # The array form is to be used when the condition input is tainted and requires sanitization. The string and hash + # forms can be used for statements that don't involve tainted data. The hash form works much like the array form, + # except only equality and range is possible. Examples: # # class User < ActiveRecord::Base # def self.authenticate_unsafely(user_name, password) -- cgit v1.2.3 From 38bfcffc596a6feb822af50bfd3cab93f7cf74a2 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 30 Jul 2011 23:16:07 +0530 Subject: make the warning clear about the effect of using validates_associated on both sides on an association. --- activerecord/lib/active_record/validations/associated.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 5df85304a2..7af0352a31 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -17,15 +17,7 @@ module ActiveRecord # validates_associated :pages, :library # end # - # Warning: If, after the above definition, you then wrote: - # - # class Page < ActiveRecord::Base - # belongs_to :book - # - # validates_associated :book - # end - # - # this would specify a circular dependency and cause infinite recursion. + # WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion. # # NOTE: This validation will not fail if the association hasn't been assigned. If you want to # ensure that the association is both present and guaranteed to be valid, you also need to -- cgit v1.2.3 From 9e18380a323c7087b2079ec479d26ce899268b72 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 4 Aug 2011 15:14:06 -0700 Subject: Revert "Explicitly included hashes in sentence regarding SQL-injection-safe forms" Reason: The hash form is secure, and preferred over the array form if possible. This reverts commit 6dc749596c328c44c80f898d5fa860fff6cab783. --- activerecord/lib/active_record/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 461df0555f..4136868b39 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -63,9 +63,9 @@ module ActiveRecord #:nodoc: # == Conditions # # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. - # The array form is to be used when the condition input is tainted and requires sanitization. The string and hash - # forms can be used for statements that don't involve tainted data. The hash form works much like the array form, - # except only equality and range is possible. Examples: + # The array form is to be used when the condition input is tainted and requires sanitization. The string form can + # be used for statements that don't involve tainted data. The hash form works much like the array form, except + # only equality and range is possible. Examples: # # class User < ActiveRecord::Base # def self.authenticate_unsafely(user_name, password) -- cgit v1.2.3 From 1b676fc76074047f4784502a9597fb529fd74dd7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 5 Aug 2011 18:42:41 -0300 Subject: Revert "to_key on a destroyed model should return nil". Closes #2440 This reverts commit c5448721b5054b8a467958d60427fdee15eac604. --- activerecord/lib/active_record/attribute_methods/primary_key.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 8bd898d126..ed71b5e7d4 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -3,11 +3,10 @@ module ActiveRecord module PrimaryKey extend ActiveSupport::Concern - # Returns this record's primary key value wrapped in an Array or nil if - # the record is not persisted? or has just been destroyed. + # Returns this record's primary key value wrapped in an Array if one is available def to_key key = send(self.class.primary_key) - persisted? && key ? [key] : nil + [key] if key end module ClassMethods -- cgit v1.2.3 From cdb49fc2f3f66bbae81be837424dcb45602ea5e2 Mon Sep 17 00:00:00 2001 From: Gustavo Delfino Date: Sat, 6 Aug 2011 16:05:45 -0430 Subject: sqlite transactions now logged motivation: http://stackoverflow.com/questions/6892630/sqlite-transactions-not-showing-in-test-log --- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index e2a0f63393..ba65ff4357 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -238,15 +238,15 @@ module ActiveRecord end def begin_db_transaction #:nodoc: - @connection.transaction + log('begin transaction',nil) { @connection.transaction } end def commit_db_transaction #:nodoc: - @connection.commit + log('commit transaction',nil) { @connection.commit } end def rollback_db_transaction #:nodoc: - @connection.rollback + log('rollback transaction',nil) { @connection.rollback } end # SCHEMA STATEMENTS ======================================== -- cgit v1.2.3 From 7db90aa7c7dfe5033ad012b8ee13e6f15d1c66f0 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 8 Aug 2011 23:27:54 +0100 Subject: Make it the responsibility of the connection to hold onto an ARel visitor for generating SQL. This improves the code architecture generally, and solves some problems with marshalling. Adapter authors please take note: you now need to define an Adapter.visitor_for method, but it degrades gracefully with a deprecation warning for now. --- .../has_and_belongs_to_many_association.rb | 4 +-- .../preloader/has_and_belongs_to_many.rb | 2 +- activerecord/lib/active_record/base.rb | 3 +- .../abstract/connection_pool.rb | 19 ++++++++--- .../abstract/database_statements.rb | 37 ++++++++++++++-------- .../connection_adapters/abstract/query_cache.rb | 5 +-- .../connection_adapters/abstract_adapter.rb | 24 ++++++++++++++ .../connection_adapters/mysql2_adapter.rb | 4 +++ .../connection_adapters/mysql_adapter.rb | 4 +++ .../connection_adapters/postgresql_adapter.rb | 4 +++ .../connection_adapters/sqlite_adapter.rb | 4 +++ activerecord/lib/active_record/counter_cache.rb | 2 +- .../lib/active_record/locking/optimistic.rb | 4 +-- activerecord/lib/active_record/migration.rb | 6 ++-- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/relation.rb | 13 ++++---- .../lib/active_record/relation/calculations.rb | 4 +-- .../lib/active_record/relation/finder_methods.rb | 6 ++-- 18 files changed, 103 insertions(+), 44 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb index f7ce70db1a..1f917f58f2 100644 --- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb +++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb @@ -26,7 +26,7 @@ module ActiveRecord join_table[reflection.association_foreign_key] => record.id ) - owner.connection.insert stmt.to_sql + owner.connection.insert stmt end record @@ -46,7 +46,7 @@ module ActiveRecord stmt = relation.where(relation[reflection.foreign_key].eq(owner.id). and(relation[reflection.association_foreign_key].in(records.map { |x| x.id }.compact)) ).compile_delete - owner.connection.delete stmt.to_sql + owner.connection.delete stmt end end diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb index 24be279449..b77b667219 100644 --- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb @@ -13,7 +13,7 @@ module ActiveRecord # access the aliased column on the join table def records_for(ids) scope = super - klass.connection.select_all(scope.arel.to_sql, 'SQL', scope.bind_values) + klass.connection.select_all(scope.arel, 'SQL', scope.bind_values) end def owner_key_name diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4136868b39..06087642d4 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1409,9 +1409,8 @@ MSG attrs = expand_hash_conditions_for_aggregates(attrs) table = Arel::Table.new(table_name).alias(default_table_name) - viz = Arel::Visitors.for(arel_engine) PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b| - viz.accept b + connection.visitor.accept b }.join(' AND ') end alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions 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 ddfdb05297..61994d4a47 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -82,10 +82,11 @@ module ActiveRecord # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 - @connections = [] - @checked_out = [] + @connections = [] + @checked_out = [] @automatic_reconnect = true - @tables = {} + @tables = {} + @visitor = nil @columns = Hash.new do |h, table_name| h[table_name] = with_connection do |conn| @@ -298,8 +299,18 @@ module ActiveRecord :connected?, :disconnect!, :with => :@connection_mutex private + def new_connection - ActiveRecord::Base.send(spec.adapter_method, spec.config) + connection = ActiveRecord::Base.send(spec.adapter_method, spec.config) + + # TODO: This is a bit icky, and in the long term we may want to change the method + # signature for connections. Also, if we switch to have one visitor per + # connection (and therefore per thread), we can get rid of the thread-local + # variable in Arel::Visitors::ToSql. + @visitor ||= connection.class.visitor_for(self) + connection.visitor = @visitor + + connection end def current_connection_id #:nodoc: 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 777ef15dfc..2ae655e68d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -1,30 +1,39 @@ module ActiveRecord module ConnectionAdapters # :nodoc: module DatabaseStatements + # Converts an arel AST to SQL + def to_sql(arel) + if arel.respond_to?(:ast) + visitor.accept(arel.ast) + else + arel + end + end + # Returns an array of record hashes with the column names as keys and # column values as values. - def select_all(sql, name = nil, binds = []) - select(sql, name, binds) + def select_all(arel, name = nil, binds = []) + select(to_sql(arel), name, binds) end # Returns a record hash with the column names as keys and column values # as values. - def select_one(sql, name = nil) - result = select_all(sql, name) + def select_one(arel, name = nil) + result = select_all(arel, name) result.first if result end # Returns a single value from a record - def select_value(sql, name = nil) - if result = select_one(sql, name) + def select_value(arel, name = nil) + if result = select_one(arel, name) result.values.first end end # Returns an array of the values of the first column in a select: # select_values("SELECT id FROM companies LIMIT 3") => [1,2,3] - def select_values(sql, name = nil) - result = select_rows(sql, name) + def select_values(arel, name = nil) + result = select_rows(to_sql(arel), name) result.map { |v| v[0] } end @@ -74,20 +83,20 @@ module ActiveRecord # # If the next id was calculated in advance (as in Oracle), it should be # passed in as +id_value+. - def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) - sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds) + def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) + sql, binds = sql_for_insert(to_sql(arel), pk, id_value, sequence_name, binds) value = exec_insert(sql, name, binds) id_value || last_inserted_id(value) end # Executes the update statement and returns the number of rows affected. - def update(sql, name = nil, binds = []) - exec_update(sql, name, binds) + def update(arel, name = nil, binds = []) + exec_update(to_sql(arel), name, binds) end # Executes the delete statement and returns the number of rows affected. - def delete(sql, name = nil, binds = []) - exec_delete(sql, name, binds) + def delete(arel, name = nil, binds = []) + exec_delete(to_sql(arel), name, binds) end # Checks whether there is currently no transaction active. This is done 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 093c30aa42..27ff13ad89 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -55,9 +55,10 @@ module ActiveRecord @query_cache.clear end - def select_all(sql, name = nil, binds = []) + def select_all(arel, name = nil, binds = []) if @query_cache_enabled - cache_sql(sql, binds) { super } + sql = to_sql(arel) + cache_sql(sql, binds) { super(sql, name, binds) } else super end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index bde31d1cda..88fd180fa5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -2,6 +2,7 @@ require 'date' require 'bigdecimal' require 'bigdecimal/util' require 'active_support/core_ext/benchmark' +require 'active_support/deprecation' # TODO: Autoload these files require 'active_record/connection_adapters/column' @@ -38,6 +39,8 @@ module ActiveRecord define_callbacks :checkout, :checkin + attr_accessor :visitor + def initialize(connection, logger = nil) #:nodoc: @active = nil @connection, @logger = connection, logger @@ -45,6 +48,27 @@ module ActiveRecord @query_cache = Hash.new { |h,sql| h[sql] = {} } @open_transactions = 0 @instrumenter = ActiveSupport::Notifications.instrumenter + @visitor = nil + end + + # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface + def self.visitor_for(pool) # :nodoc: + adapter = pool.spec.config[:adapter] + + if Arel::Visitors::VISITORS[adapter] + # TODO: Add a test for this + + ActiveSupport::Deprecation.warn( + "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \ + "should define a visitor_for method which returns the appropriate visitor for " \ + "the database. For example, MysqlAdapter.visitor_for(pool) returns " \ + "Arel::Visitors::MySQL.new(pool)." + ) + + Arel::Visitors::VISITORS[adapter].new(pool) + else + Arel::Visitors::ToSql.new(pool) + end end # Returns the human-readable name of the adapter. Use mixed case - one diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index f9602bbe77..18fdfa29ec 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -129,6 +129,10 @@ module ActiveRecord configure_connection end + def self.visitor_for(pool) # :nodoc: + Arel::Visitors::MySQL.new(pool) + end + def adapter_name ADAPTER_NAME end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 9e6cb13cca..14b950dbb0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -192,6 +192,10 @@ module ActiveRecord connect end + def self.visitor_for(pool) # :nodoc: + Arel::Visitors::MySQL.new(pool) + end + def adapter_name #:nodoc: ADAPTER_NAME end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index aefe69f8ed..45c13bdcd6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -265,6 +265,10 @@ module ActiveRecord @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end + def self.visitor_for(pool) # :nodoc: + Arel::Visitors::PostgreSQL.new(pool) + end + # Clears the prepared statements cache. def clear_cache! @statements.each_value do |value| diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ba65ff4357..486efc5ba0 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -53,6 +53,10 @@ module ActiveRecord @config = config end + def self.visitor_for(pool) # :nodoc: + Arel::Visitors::SQLite.new(pool) + end + def adapter_name #:nodoc: 'SQLite' end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 4d387565d9..3c7defedac 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -33,7 +33,7 @@ module ActiveRecord stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({ arel_table[counter_name] => object.send(association).count }) - connection.update stmt.to_sql + connection.update stmt end return true end diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 6cfce6e573..d9ad7e4132 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -70,7 +70,7 @@ module ActiveRecord # If the locking column has no default value set, # start the lock version at zero. Note we can't use - # locking_enabled? at this point as + # locking_enabled? at this point as # @attributes may not have been initialized yet. if result.key?(self.class.locking_column) && lock_optimistically @@ -100,7 +100,7 @@ module ActiveRecord ) ).arel.compile_update(arel_attributes_values(false, false, attribute_names)) - affected_rows = connection.update stmt.to_sql + affected_rows = connection.update stmt unless affected_rows == 1 raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}" diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index fa1b303fc7..7166f1b82a 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -563,7 +563,7 @@ module ActiveRecord def get_all_versions table = Arel::Table.new(schema_migrations_table_name) - Base.connection.select_values(table.project(table['version']).to_sql).map{ |v| v.to_i }.sort + Base.connection.select_values(table.project(table['version'])).map{ |v| v.to_i }.sort end def current_version @@ -720,11 +720,11 @@ module ActiveRecord if down? @migrated_versions.delete(version) stmt = table.where(table["version"].eq(version.to_s)).compile_delete - Base.connection.delete stmt.to_sql + Base.connection.delete stmt else @migrated_versions.push(version).sort! stmt = table.compile_insert table["version"] => version.to_s - Base.connection.insert stmt.to_sql + Base.connection.insert stmt end end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index ebda3875cd..2dac9ea0fb 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -304,7 +304,7 @@ module ActiveRecord return 0 if attributes_with_values.empty? klass = self.class stmt = klass.unscoped.where(klass.arel_table[klass.primary_key].eq(id)).arel.compile_update(attributes_with_values) - klass.connection.update stmt.to_sql + klass.connection.update stmt end # Creates a record with values matching those of the instance attributes diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index fff0ad1b83..7e59eb4584 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -68,7 +68,7 @@ module ActiveRecord end conn.insert( - im.to_sql, + im, 'SQL', primary_key, primary_key_value, @@ -108,10 +108,10 @@ module ActiveRecord if default_scoped.equal?(self) @records = if @readonly_value.nil? && !@klass.locking_enabled? - eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values) + eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values) else IdentityMap.without do - eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql, @bind_values) + eager_loading? ? find_with_associations : @klass.find_by_sql(arel, @bind_values) end end @@ -224,7 +224,7 @@ module ActiveRecord stmt.order(*arel.orders) stmt.key = table[primary_key] - @klass.connection.update stmt.to_sql, 'SQL', bind_values + @klass.connection.update stmt, 'SQL', bind_values end end @@ -341,8 +341,7 @@ module ActiveRecord where(conditions).delete_all else statement = arel.compile_delete - affected = @klass.connection.delete( - statement.to_sql, 'SQL', bind_values) + affected = @klass.connection.delete(statement, 'SQL', bind_values) reset affected @@ -388,7 +387,7 @@ module ActiveRecord end def to_sql - @to_sql ||= arel.to_sql + @to_sql ||= klass.connection.to_sql(arel) end def where_values_hash diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 9a7ff87e88..af86771d2d 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -223,7 +223,7 @@ module ActiveRecord query_builder = relation.arel end - type_cast_calculated_value(@klass.connection.select_value(query_builder.to_sql), column_for(column_name), operation) + type_cast_calculated_value(@klass.connection.select_value(query_builder), column_for(column_name), operation) end def execute_grouped_calculation(operation, column_name, distinct) #:nodoc: @@ -259,7 +259,7 @@ module ActiveRecord relation = except(:group).group(group.join(',')) relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation.to_sql) + calculated_data = @klass.connection.select_all(relation) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 8cef4e5554..73368aed18 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -193,8 +193,8 @@ module ActiveRecord else relation = relation.where(table[primary_key].eq(id)) if id end - - connection.select_value(relation.to_sql, "#{name} Exists") ? true : false + + connection.select_value(relation, "#{name} Exists") ? true : false end protected @@ -202,7 +202,7 @@ module ActiveRecord def find_with_associations join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) - rows = connection.select_all(relation.to_sql, 'SQL', relation.bind_values) + rows = connection.select_all(relation, 'SQL', relation.bind_values) join_dependency.instantiate(rows) rescue ThrowResult [] -- cgit v1.2.3 From 9062b75bb7dab38977805c1de35944079a56499a Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 8 Aug 2011 14:41:23 +0100 Subject: Fully marshal AR::Base objects. Fixes #2431. --- activerecord/lib/active_record/base.rb | 21 --------------------- 1 file changed, 21 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 06087642d4..102d8f4175 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -937,17 +937,6 @@ module ActiveRecord #:nodoc: self.current_scope = nil end - # Specifies how the record is loaded by +Marshal+. - # - # +_load+ sets an instance variable for each key in the hash it takes as input. - # Override this method if you require more complex marshalling. - def _load(data) - record = allocate - record.init_with(Marshal.load(data)) - record - end - - # Finder methods must instantiate through this method to work with the # single-table inheritance model that makes it possible to create # objects of different types from the same table. @@ -1588,16 +1577,6 @@ MSG self end - # Specifies how the record is dumped by +Marshal+. - # - # +_dump+ emits a marshalled hash which has been passed to +encode_with+. Override this - # method if you require more complex marshalling. - def _dump(level) - dump = {} - encode_with(dump) - Marshal.dump(dump) - end - # Returns a String, which Action Pack uses for constructing an URL to this # object. The default implementation returns this record's id as a String, # or nil if this record's unsaved. -- cgit v1.2.3 From 5680a51dcbaf4239b53481fd1ae39a6f4ee4034d Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 9 Aug 2011 00:00:52 +0100 Subject: Remove TODO comment I didn't mean to commit --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 88fd180fa5..077cf7df1b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -56,8 +56,6 @@ module ActiveRecord adapter = pool.spec.config[:adapter] if Arel::Visitors::VISITORS[adapter] - # TODO: Add a test for this - ActiveSupport::Deprecation.warn( "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \ "should define a visitor_for method which returns the appropriate visitor for " \ -- cgit v1.2.3 From d48dd18bb2a3d0c46708a9ee217909783b997cb2 Mon Sep 17 00:00:00 2001 From: Andrew Kaspick Date: Wed, 10 Aug 2011 12:55:29 -0500 Subject: fix exists? to return false if passed nil (which may come from a missing URL param) --- activerecord/lib/active_record/relation/finder_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 73368aed18..d0d138d2a6 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -180,7 +180,9 @@ module ActiveRecord # Person.exists?(:name => "David") # Person.exists?(['name LIKE ?', "%#{query}%"]) # Person.exists? - def exists?(id = nil) + def exists?(id = false) + return false if id.nil? + id = id.id if ActiveRecord::Base === id join_dependency = construct_join_dependency_for_association_find -- cgit v1.2.3 From d00e1646f7f0faaddf0f89f3051d165aadb2567b Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 10 Aug 2011 11:39:48 -0700 Subject: add the gem requirement for sqlite3 --- activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index c3a7b039ff..0a0da0b5d3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,4 +1,6 @@ require 'active_record/connection_adapters/sqlite_adapter' + +gem 'sqlite3', '~> 1.3.4' require 'sqlite3' module ActiveRecord -- cgit v1.2.3 From d3c15a1d31d77e44b142c96cb55b654f3552a89d Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Fri, 12 Aug 2011 19:58:37 -0700 Subject: Allow ActiveRecord observers to be disabled. We have to use Observer#update rather than Observer#send since the enabled state is checked in #update before forwarding the method call on. --- activerecord/lib/active_record/observer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/observer.rb b/activerecord/lib/active_record/observer.rb index 5a5351b517..fdf17c003c 100644 --- a/activerecord/lib/active_record/observer.rb +++ b/activerecord/lib/active_record/observer.rb @@ -111,7 +111,7 @@ module ActiveRecord callback_meth = :"_notify_#{observer_name}_for_#{callback}" unless klass.respond_to?(callback_meth) klass.send(:define_method, callback_meth) do |&block| - observer.send(callback, self, &block) + observer.update(callback, self, &block) end klass.send(callback, callback_meth) end -- cgit v1.2.3 From 24f902b1bcfa5dca4bfc7f2b978a4b0dece73894 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 13 Aug 2011 16:37:44 +0100 Subject: Fix default scope thread safety. Thanks @thedarkone for reporting. --- activerecord/lib/active_record/base.rb | 41 +++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 102d8f4175..5a57e1bc70 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1206,6 +1206,14 @@ MSG Thread.current["#{self}_current_scope"] = scope end + def ignore_default_scope? #:nodoc: + Thread.current["#{self}_ignore_default_scope"] + end + + def ignore_default_scope=(ignore) #:nodoc: + Thread.current["#{self}_ignore_default_scope"] = ignore + end + # Use this macro in your model to set a default scope for all operations on # the model. # @@ -1259,24 +1267,27 @@ MSG # The @ignore_default_scope flag is used to prevent an infinite recursion situation where # a default scope references a scope which has a default scope which references a scope... def build_default_scope #:nodoc: - return if defined?(@ignore_default_scope) && @ignore_default_scope - @ignore_default_scope = true - - if method(:default_scope).owner != Base.singleton_class - default_scope - elsif default_scopes.any? - 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) + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + + if method(:default_scope).owner != Base.singleton_class + default_scope + elsif default_scopes.any? + 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 end end + ensure + self.ignore_default_scope = false end - ensure - @ignore_default_scope = false end # Returns the class type of the record using the current module as a prefix. So descendants of -- cgit v1.2.3 From 9ecc4433bbda255cbcb4a2be442c8ee985692d06 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 13 Aug 2011 16:50:03 +0100 Subject: Perf: don't mess around with thread local vars unless we actually need to --- activerecord/lib/active_record/base.rb | 41 +++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5a57e1bc70..0c5248c576 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1206,14 +1206,6 @@ MSG Thread.current["#{self}_current_scope"] = scope end - def ignore_default_scope? #:nodoc: - Thread.current["#{self}_ignore_default_scope"] - end - - def ignore_default_scope=(ignore) #:nodoc: - Thread.current["#{self}_ignore_default_scope"] = ignore - end - # Use this macro in your model to set a default scope for all operations on # the model. # @@ -1264,17 +1256,11 @@ MSG self.default_scopes = default_scopes + [scope] end - # The @ignore_default_scope flag is used to prevent an infinite recursion situation where - # a default scope references a scope which has a default scope which references a scope... def build_default_scope #:nodoc: - return if ignore_default_scope? - - begin - self.ignore_default_scope = true - - if method(:default_scope).owner != Base.singleton_class - default_scope - elsif default_scopes.any? + if method(:default_scope).owner != Base.singleton_class + evaluate_default_scope { default_scope } + elsif default_scopes.any? + evaluate_default_scope do default_scopes.inject(relation) do |default_scope, scope| if scope.is_a?(Hash) default_scope.apply_finder_options(scope) @@ -1285,6 +1271,25 @@ MSG end end end + end + end + + def ignore_default_scope? #:nodoc: + Thread.current["#{self}_ignore_default_scope"] + end + + def ignore_default_scope=(ignore) #:nodoc: + Thread.current["#{self}_ignore_default_scope"] = ignore + end + + # The ignore_default_scope flag is used to prevent an infinite recursion situation where + # a default scope references a scope which has a default scope which references a scope... + def evaluate_default_scope + return if ignore_default_scope? + + begin + self.ignore_default_scope = true + yield ensure self.ignore_default_scope = false end -- cgit v1.2.3 From 7372e9ae9a06e6b15e1a20bc69667bf44c79676c Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 30 Jul 2011 23:16:07 +0530 Subject: make the warning clear about the effect of using validates_associated on both sides on an association. --- activerecord/lib/active_record/validations/associated.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/validations/associated.rb b/activerecord/lib/active_record/validations/associated.rb index 5df85304a2..7af0352a31 100644 --- a/activerecord/lib/active_record/validations/associated.rb +++ b/activerecord/lib/active_record/validations/associated.rb @@ -17,15 +17,7 @@ module ActiveRecord # validates_associated :pages, :library # end # - # Warning: If, after the above definition, you then wrote: - # - # class Page < ActiveRecord::Base - # belongs_to :book - # - # validates_associated :book - # end - # - # this would specify a circular dependency and cause infinite recursion. + # WARNING: This validation must not be used on both ends of an association. Doing so will lead to a circular dependency and cause infinite recursion. # # NOTE: This validation will not fail if the association hasn't been assigned. If you want to # ensure that the association is both present and guaranteed to be valid, you also need to -- cgit v1.2.3 From b537595665527b8ca5ebad97fc053fd102e16d32 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Thu, 4 Aug 2011 15:14:06 -0700 Subject: Revert "Explicitly included hashes in sentence regarding SQL-injection-safe forms" Reason: The hash form is secure, and preferred over the array form if possible. This reverts commit 6dc749596c328c44c80f898d5fa860fff6cab783. --- activerecord/lib/active_record/base.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 461df0555f..4136868b39 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -63,9 +63,9 @@ module ActiveRecord #:nodoc: # == Conditions # # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement. - # The array form is to be used when the condition input is tainted and requires sanitization. The string and hash - # forms can be used for statements that don't involve tainted data. The hash form works much like the array form, - # except only equality and range is possible. Examples: + # The array form is to be used when the condition input is tainted and requires sanitization. The string form can + # be used for statements that don't involve tainted data. The hash form works much like the array form, except + # only equality and range is possible. Examples: # # class User < ActiveRecord::Base # def self.authenticate_unsafely(user_name, password) -- cgit v1.2.3 From e7f7439d068f587db91e959ef803606cae9e7cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awosz=20S=C5=82awi=C5=84ski?= Date: Sat, 23 Jul 2011 21:35:16 +0200 Subject: allow select to have multiple arguments --- activerecord/lib/active_record/relation/query_methods.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1654ae1eac..792ffe1c5d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -37,12 +37,15 @@ module ActiveRecord relation end - def select(value = Proc.new) + def select(*args, &blk) + if !block_given? && args.blank? + raise ArgumentError + end if block_given? - to_a.select {|*block_args| value.call(*block_args) } + to_a.select {|*block_args| blk.call(*block_args) } else relation = clone - relation.select_values += Array.wrap(value) + relation.select_values += args relation end end -- cgit v1.2.3 From 182a4284183c63e9cb8fa879620ce01c98e111d3 Mon Sep 17 00:00:00 2001 From: Piotr Sarnacki Date: Tue, 26 Jul 2011 16:12:17 +0200 Subject: Revert "allow select to have multiple arguments" This reverts commit 04cc446d178653d362510e79a22db5300d463161. I reverted it because apparently we want to use: select([:a, :b]) instead of select(:a, :b), but there was no tests for that form. --- activerecord/lib/active_record/relation/query_methods.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 792ffe1c5d..1654ae1eac 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -37,15 +37,12 @@ module ActiveRecord relation end - def select(*args, &blk) - if !block_given? && args.blank? - raise ArgumentError - end + def select(value = Proc.new) if block_given? - to_a.select {|*block_args| blk.call(*block_args) } + to_a.select {|*block_args| value.call(*block_args) } else relation = clone - relation.select_values += args + relation.select_values += Array.wrap(value) relation end end -- cgit v1.2.3 From 5a92c6627e02fc2e40a4c4a578e7aab7324af5fe Mon Sep 17 00:00:00 2001 From: Vishnu Atrai Date: Tue, 26 Jul 2011 22:33:23 +0530 Subject: remove deprication warning for ruby 1.9.3-head for unused variables --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index d6c167ad36..f9602bbe77 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -577,7 +577,7 @@ module ActiveRecord def quoted_columns_for_index(column_names, options = {}) length = options[:length] if options.is_a?(Hash) - quoted_column_names = case length + case length when Hash column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } when Fixnum -- cgit v1.2.3 From 05f1a9bcc3c9223768187e2379b508638dfa19b6 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 27 Jul 2011 12:36:00 +0100 Subject: Add a proxy_association method to association proxies, which can be called by association extensions to access information about the association. This replaces proxy_owner etc with proxy_association.owner. --- activerecord/lib/active_record/associations.rb | 6 ++++++ .../lib/active_record/associations/collection_proxy.rb | 16 ++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 029d7a9b15..2605a54cb6 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -460,6 +460,12 @@ module ActiveRecord # * record.association(:items).target - Returns the associated object for +belongs_to+ and +has_one+, or # the collection of associated objects for +has_many+ and +has_and_belongs_to_many+. # + # However, inside the actual extension code, you will not have access to the record as + # above. In this case, you can access proxy_association. For example, + # record.association(:items) and record.items.proxy_association will return + # the same object, allowing you to make calls like proxy_association.owner inside + # association extensions. + # # === Association Join Models # # Has Many associations can be configured with the :through option to use an diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 827dfb7ccb..6ba3d45aff 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -58,23 +58,27 @@ module ActiveRecord alias_method :new, :build + def proxy_association + @association + end + def respond_to?(name, include_private = false) super || (load_target && target.respond_to?(name, include_private)) || - @association.klass.respond_to?(name, include_private) + proxy_association.klass.respond_to?(name, include_private) end def method_missing(method, *args, &block) match = DynamicFinderMatch.match(method) if match && match.instantiator? 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 + proxy_association.send :set_owner_attributes, r + proxy_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)) + if target.respond_to?(method) || (!proxy_association.klass.respond_to?(method) && Class.respond_to?(method)) if load_target if target.respond_to?(method) target.send(method, *args, &block) @@ -104,7 +108,7 @@ module ActiveRecord alias_method :to_a, :to_ary def <<(*records) - @association.concat(records) && self + proxy_association.concat(records) && self end alias_method :push, :<< @@ -114,7 +118,7 @@ module ActiveRecord end def reload - @association.reload + proxy_association.reload self end end -- cgit v1.2.3 From 4c873cbf367d82538dbac914e7eee05b3582b6f1 Mon Sep 17 00:00:00 2001 From: Dmitriy Kiriyenko Date: Tue, 5 Jul 2011 13:17:39 +0300 Subject: Fixed failing query when performing calculation with having based on select. --- activerecord/lib/active_record/relation/calculations.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 0ac821b2d7..9a7ff87e88 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -250,6 +250,7 @@ module ActiveRecord operation, distinct).as(aggregate_alias) ] + select_values += @select_values unless @having_values.empty? select_values.concat group_fields.zip(group_aliases).map { |field,aliaz| "#{field} AS #{aliaz}" -- cgit v1.2.3 From c751bb1ac25c1b563cb55da2a25284f430e5e40e Mon Sep 17 00:00:00 2001 From: Samer Masry Date: Wed, 27 Jul 2011 10:17:25 -0700 Subject: Reverse order fix when using function for ActiveRecord::QueryMethods Fixes #1697 --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1654ae1eac..3a7b245c51 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -312,7 +312,7 @@ module ActiveRecord when String, Symbol o.to_s.split(',').collect do |s| s.strip! - s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + (s if s =~ /\(/) || s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') end else o -- cgit v1.2.3 From bec59779326daa35aab483fa8b95c0f87440fede Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 28 Jul 2011 13:14:11 +0100 Subject: Revert "Merge pull request #2309 from smasry/master" This reverts commit 9d396ee8195e31f646e0b89158ed96f4db4ab38f, reversing changes made to fa2bfd832c1d1e997d93c2269a485cc74782c86d. Reason: the change broke the build. --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 3a7b245c51..1654ae1eac 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -312,7 +312,7 @@ module ActiveRecord when String, Symbol o.to_s.split(',').collect do |s| s.strip! - (s if s =~ /\(/) || s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') end else o -- cgit v1.2.3 From 17a66a8ddc5a2ee8263d1dcc86a032322cb8e615 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 29 Jul 2011 12:23:37 -0700 Subject: dump IO encoding value along with schema.rb so the file can be reloaded. fixes #1592 --- activerecord/lib/active_record/schema_dumper.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 19585f6214..6fe305f843 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -40,6 +40,10 @@ module ActiveRecord def header(stream) define_params = @version ? ":version => #{@version}" : "" + if stream.respond_to?(:external_encoding) + stream.puts "# encoding: #{stream.external_encoding.name}" + end + stream.puts <
Date: Fri, 29 Jul 2011 12:28:12 -0700 Subject: default writing the schema file as utf-8 --- activerecord/lib/active_record/railties/databases.rake | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 0ee7e20cf1..ec00f7faad 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -341,7 +341,8 @@ db_namespace = namespace :db do 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| + 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 -- cgit v1.2.3 From 9d19bae233d7a2ce9adac39b6b6e91de85729def Mon Sep 17 00:00:00 2001 From: Christopher Meiklejohn Date: Fri, 29 Jul 2011 21:26:21 -0400 Subject: Support backwards compatible interface for migration down/up with rails 3.0.x. --- activerecord/lib/active_record/migration.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 507f345ef5..9307d7ef24 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -329,6 +329,7 @@ module ActiveRecord end def self.method_missing(name, *args, &block) # :nodoc: + self.delegate = self.new (delegate || superclass.delegate).send(name, *args, &block) end -- cgit v1.2.3 From d6af6fc0c03860f677e8fc85fa7eb4bab181215a Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 2 Aug 2011 20:01:38 -0700 Subject: add a migrate class method and delegate to the new instance --- activerecord/lib/active_record/migration.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 9307d7ef24..fa1b303fc7 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -329,10 +329,13 @@ module ActiveRecord end def self.method_missing(name, *args, &block) # :nodoc: - self.delegate = self.new (delegate || superclass.delegate).send(name, *args, &block) end + def self.migrate(direction) + new.migrate direction + end + cattr_accessor :verbose attr_accessor :name, :version -- cgit v1.2.3 From 1ff52cbe818f9f7bad2a9ff6ae0e515f82b1ab06 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 3 Aug 2011 08:57:52 -0700 Subject: initializing @open_transactions in the initialize method --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 65024d76f8..bde31d1cda 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -43,6 +43,7 @@ module ActiveRecord @connection, @logger = connection, logger @query_cache_enabled = false @query_cache = Hash.new { |h,sql| h[sql] = {} } + @open_transactions = 0 @instrumenter = ActiveSupport::Notifications.instrumenter end @@ -177,12 +178,9 @@ module ActiveRecord @connection end - def open_transactions - @open_transactions ||= 0 - end + attr_reader :open_transactions def increment_open_transactions - @open_transactions ||= 0 @open_transactions += 1 end -- cgit v1.2.3 From bb72183bca60c105c901f6c38cf81dae3a104102 Mon Sep 17 00:00:00 2001 From: artemk Date: Thu, 4 Aug 2011 00:34:13 +0300 Subject: accept option for recreate db for postgres (same as mysql now) --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a84f73c73f..aefe69f8ed 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -614,9 +614,11 @@ module ActiveRecord # SCHEMA STATEMENTS ======================================== - def recreate_database(name) #:nodoc: + # 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) + create_database(name, options) end # Create a new PostgreSQL database. Options include :owner, :template, -- cgit v1.2.3 From 56efdbc6260f49fdf8d82d8557f233a7df3beafa Mon Sep 17 00:00:00 2001 From: Florent Guilleux Date: Mon, 8 Aug 2011 19:31:03 -0500 Subject: Document exclamation point on dynamic finders --- activerecord/lib/active_record/base.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 4136868b39..b3a0267276 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -177,6 +177,10 @@ module ActiveRecord #:nodoc: # And instead of writing Person.where(:last_name => last_name).all, you just do # Person.find_all_by_last_name(last_name). # + # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an + # ActiveRecord::RecordNotFound error if they do not return any records, + # like Person.find_by_last_name!. + # # It's also possible to use multiple attributes in the same find by separating them with "_and_". # # Person.where(:user_name => user_name, :password => password).first -- cgit v1.2.3 From 4bb7abcb7715c06ff816243c61d75b14afc117e0 Mon Sep 17 00:00:00 2001 From: "Jonathon M. Abbott" Date: Wed, 27 Jul 2011 17:35:35 +1000 Subject: Use mysql_creation_options inside rescue block Commit ecd37084b28a05f05251 did not take into account the use of creation_options inside the access denied exception handler. --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ec00f7faad..13c41350fb 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -94,7 +94,7 @@ db_namespace = namespace :db do "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;" ActiveRecord::Base.establish_connection(config.merge( 'database' => nil, 'username' => 'root', 'password' => root_password)) - ActiveRecord::Base.connection.create_database(config['database'], creation_options) + ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config)) ActiveRecord::Base.connection.execute grant_statement ActiveRecord::Base.establish_connection(config) else -- cgit v1.2.3 From 7b56fb034a2f9a03ccbdc485287946b49e5e9b68 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 18 Jul 2011 16:35:21 +0100 Subject: Fix private methods which are delegated to. This previously worked because Module#delegate previously ignored method visibility. --- .../lib/active_record/associations/association.rb | 16 ++++++++-------- .../associations/belongs_to_polymorphic_association.rb | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index bb519c5703..d1e3ff8e38 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -151,20 +151,20 @@ module ActiveRecord reset end + def interpolate(sql, record = nil) + if sql.respond_to?(:to_proc) + owner.send(:instance_exec, record, &sql) + else + sql + end + end + private def find_target? !loaded? && (!owner.new_record? || foreign_key_present?) && klass end - def interpolate(sql, record = nil) - if sql.respond_to?(:to_proc) - owner.send(:instance_exec, record, &sql) - else - sql - end - end - def creation_attributes attributes = {} diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 198ad06360..2ee5dbbd70 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -2,6 +2,11 @@ module ActiveRecord # = Active Record Belongs To Polymorphic Association module Associations class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc: + def klass + type = owner[reflection.foreign_type] + type.presence && type.constantize + end + private def replace_keys(record) @@ -17,11 +22,6 @@ module ActiveRecord reflection.polymorphic_inverse_of(record.class) end - def klass - type = owner[reflection.foreign_type] - type.presence && type.constantize - end - def raise_on_type_mismatch(record) # A polymorphic association cannot have a type mismatch, by definition end -- cgit v1.2.3 From 57423d815b3747aa382cd3859a15bffa538525ad Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 15 Aug 2011 16:00:35 +0100 Subject: Ensure empty has_many :through association preloaded via joins is marked as loaded. Fixes #2054. --- activerecord/lib/active_record/associations/join_dependency.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 504f25271c..6c878f0f00 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -188,13 +188,12 @@ module ActiveRecord association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil? set_target_and_inverse(join_part, association, record) else - return if row[join_part.aliased_primary_key].nil? - association = join_part.instantiate(row) + association = join_part.instantiate(row) unless row[join_part.aliased_primary_key].nil? case macro when :has_many, :has_and_belongs_to_many other = record.association(join_part.reflection.name) other.loaded! - other.target.push(association) + other.target.push(association) if association other.set_inverse_instance(association) when :belongs_to set_target_and_inverse(join_part, association, record) -- cgit v1.2.3 From 128d006242dae07edc65ad03e0e045adac0bbbf3 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 9 Aug 2011 00:12:53 +0100 Subject: Support updates with joins. Fixes #522. --- .../connection_adapters/abstract/database_statements.rb | 9 +++++++++ .../lib/active_record/connection_adapters/mysql2_adapter.rb | 4 ++++ .../lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++++ activerecord/lib/active_record/relation.rb | 13 +++++++++---- 4 files changed, 26 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') 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 2ae655e68d..7543d35d3b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -306,6 +306,15 @@ module ActiveRecord end end + # The default strategy for an UPDATE with joins is to use a subquery. This doesn't work + # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in + # an UPDATE statement, so in the mysql adapters we redefine this to do that. + def join_to_update(update, select) #:nodoc: + subselect = select.clone + subselect.ast.cores.last.projections = [update.ast.key] + update.wheres = [update.ast.key.in(subselect)] + end + protected # Returns an array of record hashes with the column names as keys and # column values as values. diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 18fdfa29ec..c01a64e354 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -577,6 +577,10 @@ module ActiveRecord where_sql end + def join_to_update(update, select) #:nodoc: + update.table select.ast.cores.last.source + end + protected def quoted_columns_for_index(column_names, options = {}) length = options[:length] if options.is_a?(Hash) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 14b950dbb0..ea0970028c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -491,6 +491,10 @@ module ActiveRecord execute("RELEASE SAVEPOINT #{current_savepoint_name}") end + def join_to_update(update, select) #:nodoc: + update.table select.ast.cores.last.source + end + # SCHEMA STATEMENTS ======================================== def structure_dump #:nodoc: diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7e59eb4584..565ece1930 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -217,13 +217,18 @@ module ActiveRecord where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates) else stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))) + stmt.key = table[primary_key] - if limit = arel.limit - stmt.take limit + if joins_values.any? + @klass.connection.join_to_update(stmt, arel) + else + if limit = arel.limit + stmt.take limit + end + + stmt.order(*arel.orders) end - stmt.order(*arel.orders) - stmt.key = table[primary_key] @klass.connection.update stmt, 'SQL', bind_values end end -- cgit v1.2.3 From 43b99f290a8070196919a68999db87873257b7b8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 10 Aug 2011 00:03:49 +0100 Subject: Support for multi-table updates with limits, offsets and orders --- .../abstract/database_statements.rb | 3 +++ .../connection_adapters/mysql2_adapter.rb | 23 +++++++++++++++++++++- .../connection_adapters/mysql_adapter.rb | 23 +++++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') 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 7543d35d3b..83e64d3c43 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -312,6 +312,9 @@ module ActiveRecord def join_to_update(update, select) #:nodoc: subselect = select.clone subselect.ast.cores.last.projections = [update.ast.key] + + update.ast.limit = nil + update.ast.orders = [] update.wheres = [update.ast.key.in(subselect)] end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index c01a64e354..172d08b6f4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -577,8 +577,29 @@ module ActiveRecord where_sql end + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE + # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support + # these, we must use a subquery. However, MySQL is too stupid to create a + # temporary table for this automatically, so we have to give it some prompting + # in the form of a subsubquery. Ugh! def join_to_update(update, select) #:nodoc: - update.table select.ast.cores.last.source + if select.limit || select.offset || select.orders.any? + subsubselect = select.ast.clone + subsubselect.cores.last.projections = [update.ast.key] + subsubselect = Arel::Nodes::TableAlias.new( + Arel::Nodes::Grouping.new(subsubselect), + '__active_record_temp' + ) + + subselect = Arel::SelectManager.new(select.engine, subsubselect) + subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name]) + + update.ast.limit = nil + update.ast.orders = [] + update.wheres = [update.ast.key.in(subselect)] + else + update.table select.ast.cores.last.source + end end protected diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index ea0970028c..bd6cb2d3b8 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -491,8 +491,29 @@ module ActiveRecord execute("RELEASE SAVEPOINT #{current_savepoint_name}") end + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE + # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support + # these, we must use a subquery. However, MySQL is too stupid to create a + # temporary table for this automatically, so we have to give it some prompting + # in the form of a subsubquery. Ugh! def join_to_update(update, select) #:nodoc: - update.table select.ast.cores.last.source + if select.limit || select.offset || select.orders.any? + subsubselect = select.ast.clone + subsubselect.cores.last.projections = [update.ast.key] + subsubselect = Arel::Nodes::TableAlias.new( + Arel::Nodes::Grouping.new(subsubselect), + '__active_record_temp' + ) + + subselect = Arel::SelectManager.new(select.engine, subsubselect) + subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name]) + + update.ast.limit = nil + update.ast.orders = [] + update.wheres = [update.ast.key.in(subselect)] + else + update.table select.ast.cores.last.source + end end # SCHEMA STATEMENTS ======================================== -- cgit v1.2.3 From fe0ec855419e1deba47277c96275a16ecf79bc9a Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 10 Aug 2011 00:06:30 +0100 Subject: Refactor building the update manager --- .../connection_adapters/abstract/database_statements.rb | 8 +++----- .../lib/active_record/connection_adapters/mysql2_adapter.rb | 5 ++--- .../lib/active_record/connection_adapters/mysql_adapter.rb | 5 ++--- activerecord/lib/active_record/relation.rb | 11 ++++++----- 4 files changed, 13 insertions(+), 16 deletions(-) (limited to 'activerecord/lib') 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 83e64d3c43..d8bd33f72a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -310,12 +310,10 @@ module ActiveRecord # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in # an UPDATE statement, so in the mysql adapters we redefine this to do that. def join_to_update(update, select) #:nodoc: - subselect = select.clone - subselect.ast.cores.last.projections = [update.ast.key] + subselect = select.ast.clone + subselect.cores.last.projections = [update.ast.key] - update.ast.limit = nil - update.ast.orders = [] - update.wheres = [update.ast.key.in(subselect)] + update.where update.ast.key.in(subselect) end protected diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 172d08b6f4..41d410e062 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -594,11 +594,10 @@ module ActiveRecord subselect = Arel::SelectManager.new(select.engine, subsubselect) subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name]) - update.ast.limit = nil - update.ast.orders = [] - update.wheres = [update.ast.key.in(subselect)] + update.where update.ast.key.in(subselect) else update.table select.ast.cores.last.source + update.wheres = select.constraints end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index bd6cb2d3b8..d4aaf26bf3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -508,11 +508,10 @@ module ActiveRecord subselect = Arel::SelectManager.new(select.engine, subsubselect) subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name]) - update.ast.limit = nil - update.ast.orders = [] - update.wheres = [update.ast.key.in(subselect)] + update.where update.ast.key.in(subselect) else update.table select.ast.cores.last.source + update.wheres = select.constraints end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 565ece1930..15fd1a58c8 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -216,17 +216,18 @@ module ActiveRecord if conditions || options.present? where(conditions).apply_finder_options(options.slice(:limit, :order)).update_all(updates) else - stmt = arel.compile_update(Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates))) + stmt = Arel::UpdateManager.new(arel.engine) + + stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)) + stmt.table(table) stmt.key = table[primary_key] if joins_values.any? @klass.connection.join_to_update(stmt, arel) else - if limit = arel.limit - stmt.take limit - end - + stmt.take(arel.limit) stmt.order(*arel.orders) + stmt.wheres = arel.constraints end @klass.connection.update stmt, 'SQL', bind_values -- cgit v1.2.3 From 8778c82e32690ed7b25664522d0bd0324ebea840 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 10 Aug 2011 00:49:20 +0100 Subject: Use a SelectCore rather than a full SelectManager --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 5 +++-- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 41d410e062..460745fba0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -591,8 +591,9 @@ module ActiveRecord '__active_record_temp' ) - subselect = Arel::SelectManager.new(select.engine, subsubselect) - subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name]) + subselect = Arel::Nodes::SelectCore.new + subselect.from = subsubselect + subselect.projections << Arel::Table.new('__active_record_temp')[update.ast.key.name] update.where update.ast.key.in(subselect) else diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index d4aaf26bf3..4581c16d25 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -505,8 +505,9 @@ module ActiveRecord '__active_record_temp' ) - subselect = Arel::SelectManager.new(select.engine, subsubselect) - subselect.project(Arel::Table.new('__active_record_temp')[update.ast.key.name]) + subselect = Arel::Nodes::SelectCore.new + subselect.from = subsubselect + subselect.projections << Arel::Table.new('__active_record_temp')[update.ast.key.name] update.where update.ast.key.in(subselect) else -- cgit v1.2.3 From 12aaad0848fb29bf64025043a855b0c0b497a6b8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 11 Aug 2011 08:38:47 +0100 Subject: use update.key instead of update.ast.key. make better use of select manager. --- .../abstract/database_statements.rb | 4 ++-- .../connection_adapters/mysql2_adapter.rb | 20 ++++++++------------ .../connection_adapters/mysql_adapter.rb | 20 ++++++++------------ 3 files changed, 18 insertions(+), 26 deletions(-) (limited to 'activerecord/lib') 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 d8bd33f72a..bfbf953a37 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -311,9 +311,9 @@ module ActiveRecord # an UPDATE statement, so in the mysql adapters we redefine this to do that. def join_to_update(update, select) #:nodoc: subselect = select.ast.clone - subselect.cores.last.projections = [update.ast.key] + subselect.cores.last.projections = [update.key] - update.where update.ast.key.in(subselect) + update.where update.key.in(subselect) end protected diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 460745fba0..379ba162ed 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -584,18 +584,14 @@ module ActiveRecord # in the form of a subsubquery. Ugh! def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? - subsubselect = select.ast.clone - subsubselect.cores.last.projections = [update.ast.key] - subsubselect = Arel::Nodes::TableAlias.new( - Arel::Nodes::Grouping.new(subsubselect), - '__active_record_temp' - ) - - subselect = Arel::Nodes::SelectCore.new - subselect.from = subsubselect - subselect.projections << Arel::Table.new('__active_record_temp')[update.ast.key.name] - - update.where update.ast.key.in(subselect) + subsubselect = select.clone + subsubselect.ast.cores.last.projections = [update.key] + + subselect = Arel::SelectManager.new(select.engine) + subselect.project Arel.sql(update.key.name) + subselect.from subsubselect.as('__active_record_temp') + + update.where update.key.in(subselect) else update.table select.ast.cores.last.source update.wheres = select.constraints diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 4581c16d25..a33e2d8cb0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -498,18 +498,14 @@ module ActiveRecord # in the form of a subsubquery. Ugh! def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? - subsubselect = select.ast.clone - subsubselect.cores.last.projections = [update.ast.key] - subsubselect = Arel::Nodes::TableAlias.new( - Arel::Nodes::Grouping.new(subsubselect), - '__active_record_temp' - ) - - subselect = Arel::Nodes::SelectCore.new - subselect.from = subsubselect - subselect.projections << Arel::Table.new('__active_record_temp')[update.ast.key.name] - - update.where update.ast.key.in(subselect) + subsubselect = select.clone + subsubselect.ast.cores.last.projections = [update.key] + + subselect = Arel::SelectManager.new(select.engine) + subselect.project Arel.sql(update.key.name) + subselect.from subsubselect.as('__active_record_temp') + + update.where update.key.in(subselect) else update.table select.ast.cores.last.source update.wheres = select.constraints -- cgit v1.2.3 From cc206a3507699e2c94b2d62ec5226fd20d5d98b3 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 11 Aug 2011 08:45:42 +0100 Subject: Use new SelectManager#projections= method --- .../active_record/connection_adapters/abstract/database_statements.rb | 4 ++-- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') 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 bfbf953a37..dc4a53034b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -310,8 +310,8 @@ module ActiveRecord # on mysql (even when aliasing the tables), but mysql allows using JOIN directly in # an UPDATE statement, so in the mysql adapters we redefine this to do that. def join_to_update(update, select) #:nodoc: - subselect = select.ast.clone - subselect.cores.last.projections = [update.key] + subselect = select.clone + subselect.projections = [update.key] update.where update.key.in(subselect) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 379ba162ed..cba00dc625 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -585,7 +585,7 @@ module ActiveRecord def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? subsubselect = select.clone - subsubselect.ast.cores.last.projections = [update.key] + subsubselect.projections = [update.key] subselect = Arel::SelectManager.new(select.engine) subselect.project Arel.sql(update.key.name) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a33e2d8cb0..c3a066a622 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -499,7 +499,7 @@ module ActiveRecord def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? subsubselect = select.clone - subsubselect.ast.cores.last.projections = [update.key] + subsubselect.projections = [update.key] subselect = Arel::SelectManager.new(select.engine) subselect.project Arel.sql(update.key.name) -- cgit v1.2.3 From c3dcb795f17017cb8c3f819a30e89136540e9584 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 11 Aug 2011 08:52:48 +0100 Subject: Use new SelectManager#source method --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index cba00dc625..ae5ae16dc7 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -593,7 +593,7 @@ module ActiveRecord update.where update.key.in(subselect) else - update.table select.ast.cores.last.source + update.table select.source update.wheres = select.constraints end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c3a066a622..a44ac08ce4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -507,7 +507,7 @@ module ActiveRecord update.where update.key.in(subselect) else - update.table select.ast.cores.last.source + update.table select.source update.wheres = select.constraints end end -- cgit v1.2.3 From 0d5a6f68dfb930816392f9711f0a6a52872bc72f Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 16 Aug 2011 15:01:18 +0100 Subject: In 1efd88283ef68d912df215125951a87526768a51, ConnectionAdapters was put under eager_autoload. Due to the requires in that file, this caused ConnectionSpecification to be loaded, which references ActiveRecord::Base, which means the database connection is established. We do not want to connect to the database when Active Record is loaded, only when ActiveRecord::Base is first referenced by the user. --- activerecord/lib/active_record.rb | 3 +- activerecord/lib/active_record/base.rb | 3 +- .../connection_adapters/abstract_adapter.rb | 34 ++++++++++++++-------- 3 files changed, 26 insertions(+), 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index d63b9e3f24..511d402ee5 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -42,7 +42,7 @@ module ActiveRecord autoload :ActiveRecordError, 'active_record/errors' autoload :ConnectionNotEstablished, 'active_record/errors' autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter' - + autoload :Aggregations autoload :Associations autoload :AttributeMethods @@ -72,6 +72,7 @@ module ActiveRecord autoload :Persistence autoload :QueryCache autoload :Reflection + autoload :Result autoload :Schema autoload :SchemaDumper autoload :Serialization diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 59977280b3..c76f98d6a0 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -178,7 +178,7 @@ module ActiveRecord #:nodoc: # Person.find_all_by_last_name(last_name). # # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an - # ActiveRecord::RecordNotFound error if they do not return any records, + # ActiveRecord::RecordNotFound error if they do not return any records, # like Person.find_by_last_name!. # # It's also possible to use multiple attributes in the same find by separating them with "_and_". @@ -2163,4 +2163,5 @@ MSG end end +require 'active_record/connection_adapters/abstract/connection_specification' ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 077cf7df1b..60e2f811a2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -4,20 +4,30 @@ require 'bigdecimal/util' require 'active_support/core_ext/benchmark' require 'active_support/deprecation' -# TODO: Autoload these files -require 'active_record/connection_adapters/column' -require 'active_record/connection_adapters/abstract/schema_definitions' -require 'active_record/connection_adapters/abstract/schema_statements' -require 'active_record/connection_adapters/abstract/database_statements' -require 'active_record/connection_adapters/abstract/quoting' -require 'active_record/connection_adapters/abstract/connection_pool' -require 'active_record/connection_adapters/abstract/connection_specification' -require 'active_record/connection_adapters/abstract/query_cache' -require 'active_record/connection_adapters/abstract/database_limits' -require 'active_record/result' - module ActiveRecord module ConnectionAdapters # :nodoc: + extend ActiveSupport::Autoload + + autoload :Column + + autoload_under 'abstract' do + autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions' + autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions' + autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions' + + autoload :SchemaStatements + autoload :DatabaseStatements + autoload :DatabaseLimits + autoload :Quoting + + autoload :ConnectionPool + autoload :ConnectionHandler, 'active_record/connection_adapters/abstract/connection_pool' + autoload :ConnectionManagement, 'active_record/connection_adapters/abstract/connection_pool' + autoload :ConnectionSpecification + + autoload :QueryCache + end + # Active Record supports multiple database systems. AbstractAdapter and # related classes form the abstraction layer which makes this possible. # An AbstractAdapter represents a connection to a database, and provides an -- cgit v1.2.3 From 8a39f411dc3c806422785b1f4d5c7c9d58e4bf85 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 16 Aug 2011 15:17:17 -0700 Subject: prevent sql injection attacks by escaping quotes in column names --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 2 +- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index ae5ae16dc7..ef51f5ebca 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -169,7 +169,7 @@ module ActiveRecord end def quote_column_name(name) #:nodoc: - @quoted_column_names[name] ||= "`#{name}`" + @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" end def quote_table_name(name) #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a44ac08ce4..b844e5ab10 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -250,7 +250,7 @@ module ActiveRecord end def quote_column_name(name) #:nodoc: - @quoted_column_names[name] ||= "`#{name}`" + @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" end def quote_table_name(name) #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 486efc5ba0..da86957028 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -148,7 +148,7 @@ module ActiveRecord end def quote_column_name(name) #:nodoc: - %Q("#{name}") + %Q("#{name.to_s.gsub('"', '""')}") end # Quote date/time values for use in SQL input. Includes microseconds -- cgit v1.2.3 From e2ae015952c5bdcca044504b5be1c13a518eeaac Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Sat, 20 Aug 2011 13:00:26 -0400 Subject: Fix assumption of primary key name in PredicateBuilder subquery. --- activerecord/lib/active_record/relation/predicate_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 2814771002..dc8667b5cd 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -19,7 +19,7 @@ module ActiveRecord case value when ActiveRecord::Relation - value.select_values = [value.klass.arel_table['id']] if value.select_values.empty? + value.select_values = [value.klass.arel_table[value.klass.primary_key]] if value.select_values.empty? attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy values = value.to_a.map { |x| -- cgit v1.2.3 From f74f5f7f00bd591fbca0cbc2b847acb0ec420256 Mon Sep 17 00:00:00 2001 From: Ernie Miller Date: Sat, 20 Aug 2011 13:16:39 -0400 Subject: Fix PredicateBuilder clobbering select_values in subquery. --- activerecord/lib/active_record/relation/predicate_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index dc8667b5cd..7e8ddd1b5d 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -19,7 +19,7 @@ module ActiveRecord case value when ActiveRecord::Relation - value.select_values = [value.klass.arel_table[value.klass.primary_key]] if value.select_values.empty? + value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty? attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy values = value.to_a.map { |x| -- cgit v1.2.3 From dbe9a9b48280e3210f6c42789c3a2980b72febf4 Mon Sep 17 00:00:00 2001 From: Alan Larkin Date: Sat, 20 Aug 2011 13:12:10 +0100 Subject: Updated the docs for the has_many :finder_sql option to reflect changes made in #a7e19b30ca71f62af516, i.e. the use of Procs when interpolation of the SQL is required. --- activerecord/lib/active_record/associations.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2605a54cb6..8d755b6848 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1087,7 +1087,8 @@ module ActiveRecord # # [:finder_sql] # Specify a complete SQL statement to fetch the association. This is a good way to go for complex - # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ + # associations that depend on multiple tables. May be supplied as a string or a proc where interpolation is + # required. Note: When this option is used, +find_in_collection+ # is _not_ added. # [:counter_sql] # Specify a complete SQL statement to fetch the size of the association. If :finder_sql is @@ -1162,11 +1163,14 @@ module ActiveRecord # has_many :tags, :as => :taggable # has_many :reports, :readonly => true # has_many :subscribers, :through => :subscriptions, :source => :user - # has_many :subscribers, :class_name => "Person", :finder_sql => - # 'SELECT DISTINCT people.* ' + - # 'FROM people p, post_subscriptions ps ' + - # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + - # 'ORDER BY p.first_name' + # has_many :subscribers, :class_name => "Person", :finder_sql => Proc.new { + # %Q{ + # SELECT DISTINCT people.* + # FROM people p, post_subscriptions ps + # WHERE ps.post_id = #{id} AND ps.person_id = p.id + # ORDER BY p.first_name + # } + # } def has_many(name, options = {}, &extension) Builder::HasMany.build(self, name, options, &extension) end -- cgit v1.2.3 From 0a0da9d554490f01fe57d69fe8d98f29b02be3e5 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 24 Aug 2011 21:09:41 +0900 Subject: do not compute table names for abstract classes --- activerecord/lib/active_record/base.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index c76f98d6a0..03aea81d2c 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -624,6 +624,8 @@ module ActiveRecord #:nodoc: # Computes the table name, (re)sets it internally, and returns it. def reset_table_name #:nodoc: + return if abstract_class? + self.table_name = compute_table_name end -- cgit v1.2.3 From 098cc186c16e8cb0d4c22ab7ff6826eed32e6c3d Mon Sep 17 00:00:00 2001 From: Claudio Poli Date: Wed, 24 Aug 2011 19:15:49 +0200 Subject: Adding missing autoload --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 60e2f811a2..443e61b527 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -14,6 +14,7 @@ module ActiveRecord autoload :IndexDefinition, 'active_record/connection_adapters/abstract/schema_definitions' autoload :ColumnDefinition, 'active_record/connection_adapters/abstract/schema_definitions' autoload :TableDefinition, 'active_record/connection_adapters/abstract/schema_definitions' + autoload :Table, 'active_record/connection_adapters/abstract/schema_definitions' autoload :SchemaStatements autoload :DatabaseStatements -- cgit v1.2.3 From 42a7979cf1810c75343b18007858b81718b90678 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 25 Aug 2011 16:07:44 -0700 Subject: Force binary data inserted for a string data type to utf-8 and log an error. Strings tagged as binary will be stored in sqlite3 as blobs. It is an error to insert binary data to a string column, so an error is emitted in the log file. People are highly encouraged to track down the source of the binary strings and make sure that the encoding is set correctly before inserting to the database. --- .../connection_adapters/sqlite_adapter.rb | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index da86957028..1996e49296 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -161,10 +161,25 @@ module ActiveRecord end end - def type_cast(value, column) # :nodoc: - return super unless BigDecimal === value + if "<3".encoding_aware? + def type_cast(value, column) # :nodoc: + return value.to_f if BigDecimal === value + return super unless String === value + return super unless column && value + + value = super + if column.type == :string && value.encoding == Encoding::ASCII_8BIT + @logger.error "Binary data inserted for `string` type on column `#{column.name}`" + value.encode! 'utf-8' + end + value + end + else + def type_cast(value, column) # :nodoc: + return super unless BigDecimal === value - value.to_f + value.to_f + end end # DATABASE STATEMENTS ====================================== -- cgit v1.2.3 From e8b525f16906458001fb1d10ed84d787760f19f3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Fri, 26 Aug 2011 10:14:57 -0700 Subject: stop messing with the load path, load path should be adjusted in the test task --- activerecord/lib/active_record.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 511d402ee5..132dc12680 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -21,13 +21,6 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ - -activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__) -$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path) - -activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__) -$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path) - require 'active_support' require 'active_support/i18n' require 'active_model' -- cgit v1.2.3 From 24674b344864298135f4f1cecca1214c6b9ddf42 Mon Sep 17 00:00:00 2001 From: jbbarth Date: Sun, 28 Aug 2011 06:02:15 +0200 Subject: Fixed bad options order in ActiveRecord::Migration::CommandRecorder#invert_rename_index --- activerecord/lib/active_record/migration/command_recorder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 2eeff7e36f..ffee5a081a 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -71,7 +71,7 @@ module ActiveRecord end def invert_rename_index(args) - [:rename_index, args.reverse] + [:rename_index, [args.first] + args.last(2).reverse] end def invert_rename_column(args) -- cgit v1.2.3 From b4ff82a79177757509cefa2b103ae56d84b84f6d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 28 Aug 2011 14:15:51 -0700 Subject: clear and disable query cache when an exception is raised from called middleware --- activerecord/lib/active_record/query_cache.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index e485901440..10c0dc6f2a 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -61,6 +61,12 @@ module ActiveRecord status, headers, body = @app.call(env) [status, headers, BodyProxy.new(old, body)] + rescue Exception => e + ActiveRecord::Base.connection.clear_query_cache + unless old + ActiveRecord::Base.connection.disable_query_cache! + end + raise e end end end -- cgit v1.2.3 From 5766539342426e956980bf6f54ef99600cbfc33e Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 25 Aug 2011 22:28:46 +0100 Subject: Create an AbstractMysqlAdapter to abstract the common code between MysqlAdapter and Mysql2Adapter. --- .../connection_adapters/abstract_mysql_adapter.rb | 552 +++++++++++++++++ .../connection_adapters/mysql2_adapter.rb | 584 ++---------------- .../connection_adapters/mysql_adapter.rb | 667 +++------------------ 3 files changed, 706 insertions(+), 1097 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb new file mode 100644 index 0000000000..72cf490d7e --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -0,0 +1,552 @@ +require 'active_support/core_ext/object/blank' + +module ActiveRecord + module ConnectionAdapters + class AbstractMysqlAdapter < AbstractAdapter + class Column < ConnectionAdapters::Column + def extract_default(default) + if sql_type =~ /blob/i || type == :text + if default.blank? + return null ? nil : '' + else + raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" + end + elsif missing_default_forged_as_empty_string?(default) + nil + else + super + end + end + + def has_default? + return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns + super + end + + private + + def extract_limit(sql_type) + case sql_type + when /blob|text/i + case sql_type + when /tiny/i + 255 + when /medium/i + 16777215 + when /long/i + 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases + else + super # we could return 65535 here, but we leave it undecorated by default + end + when /^bigint/i; 8 + when /^int/i; 4 + when /^mediumint/i; 3 + when /^smallint/i; 2 + when /^tinyint/i; 1 + else + super + end + end + + # MySQL misreports NOT NULL column default when none is given. + # We can't detect this for columns which may have a legitimate '' + # default (string) but we can for others (integer, datetime, boolean, + # and the rest). + # + # Test whether the column has default '', is not null, and is not + # a type allowing default ''. + def missing_default_forged_as_empty_string?(default) + type != :string && !null && default == '' + end + end + + ## + # :singleton-method: + # By default, the MysqlAdapter will consider all columns of type tinyint(1) + # as boolean. If you wish to disable this emulation (which was the default + # behavior in versions 0.13.1 and earlier) you can add the following line + # to your application.rb file: + # + # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false + class_attribute :emulate_booleans + self.emulate_booleans = true + + LOST_CONNECTION_ERROR_MESSAGES = [ + "Server shutdown in progress", + "Broken pipe", + "Lost connection to MySQL server during query", + "MySQL server has gone away" ] + + QUOTED_TRUE, QUOTED_FALSE = '1', '0' + + NATIVE_DATABASE_TYPES = { + :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", + :string => { :name => "varchar", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "int", :limit => 4 }, + :float => { :name => "float" }, + :decimal => { :name => "decimal" }, + :datetime => { :name => "datetime" }, + :timestamp => { :name => "datetime" }, + :time => { :name => "time" }, + :date => { :name => "date" }, + :binary => { :name => "blob" }, + :boolean => { :name => "tinyint", :limit => 1 } + } + + # FIXME: Make the first parameter more similar for the two adapters + def initialize(connection, logger, connection_options, config) + super(connection, logger) + @connection_options, @config = connection_options, config + @quoted_column_names, @quoted_table_names = {}, {} + end + + def self.visitor_for(pool) # :nodoc: + Arel::Visitors::MySQL.new(pool) + end + + def adapter_name #:nodoc: + self.class::ADAPTER_NAME + end + + # Returns true, since this connection adapter supports migrations. + def supports_migrations? + true + end + + def supports_primary_key? + true + end + + # Returns true, since this connection adapter supports savepoints. + def supports_savepoints? + true + end + + def native_database_types + NATIVE_DATABASE_TYPES + end + + # HELPER METHODS =========================================== + + # The two drivers have slightly different ways of yielding hashes of results, so + # this method must be implemented to provide a uniform interface. + def each_hash(result) # :nodoc: + raise NotImplementedError + end + + # Overridden by the adapters to instantiate their specific Column type. + def new_column(field, default, type, null) # :nodoc: + Column.new(field, default, type, null) + end + + # Must return the Mysql error number from the exception, if the exception has an + # error number. + def error_number(exception) # :nodoc: + raise NotImplementedError + end + + # QUOTING ================================================== + + def quote(value, column = nil) + if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) + s = column.class.string_to_binary(value).unpack("H*")[0] + "x'#{s}'" + elsif value.kind_of?(BigDecimal) + value.to_s("F") + else + super + end + end + + def quote_column_name(name) #:nodoc: + @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" + end + + def quote_table_name(name) #:nodoc: + @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`') + end + + def quoted_true + QUOTED_TRUE + end + + def quoted_false + QUOTED_FALSE + end + + # REFERENTIAL INTEGRITY ==================================== + + def disable_referential_integrity(&block) #:nodoc: + old = select_value("SELECT @@FOREIGN_KEY_CHECKS") + + begin + update("SET FOREIGN_KEY_CHECKS = 0") + yield + ensure + update("SET FOREIGN_KEY_CHECKS = #{old}") + end + end + + # DATABASE STATEMENTS ====================================== + + # Executes the SQL statement in the context of this connection. + def execute(sql, name = nil) + if name == :skip_logging + @connection.query(sql) + else + log(sql, name) { @connection.query(sql) } + end + rescue ActiveRecord::StatementInvalid => exception + if exception.message.split(":").first =~ /Packets out of order/ + raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." + else + raise + end + end + + # MysqlAdapter has to free a result after using it, so we use this method to write + # stuff in a abstract way without concerning ourselves about whether it needs to be + # explicitly freed or not. + def execute_and_free(sql, name = nil) #:nodoc: + yield execute(sql, name) + end + + def update_sql(sql, name = nil) #:nodoc: + super + @connection.affected_rows + end + + def begin_db_transaction + execute "BEGIN" + rescue Exception + # Transactions aren't supported + end + + def commit_db_transaction #:nodoc: + execute "COMMIT" + rescue Exception + # Transactions aren't supported + end + + def rollback_db_transaction #:nodoc: + execute "ROLLBACK" + rescue Exception + # Transactions aren't supported + end + + def create_savepoint + execute("SAVEPOINT #{current_savepoint_name}") + end + + def rollback_to_savepoint + execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") + end + + def release_savepoint + execute("RELEASE SAVEPOINT #{current_savepoint_name}") + end + + # In the simple case, MySQL allows us to place JOINs directly into the UPDATE + # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support + # these, we must use a subquery. However, MySQL is too stupid to create a + # temporary table for this automatically, so we have to give it some prompting + # in the form of a subsubquery. Ugh! + def join_to_update(update, select) #:nodoc: + if select.limit || select.offset || select.orders.any? + subsubselect = select.clone + subsubselect.projections = [update.key] + + subselect = Arel::SelectManager.new(select.engine) + subselect.project Arel.sql(update.key.name) + subselect.from subsubselect.as('__active_record_temp') + + update.where update.key.in(subselect) + else + update.table select.source + update.wheres = select.constraints + end + end + + # SCHEMA STATEMENTS ======================================== + + def structure_dump #:nodoc: + if supports_views? + sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'" + else + sql = "SHOW TABLES" + end + + select_all(sql).map do |table| + table.delete('Table_type') + sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}" + exec_without_stmt(sql).first['Create Table'] + ";\n\n" + end.join("") + 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) + end + + # Create a new MySQL database with optional :charset and :collation. + # Charset defaults to utf8. + # + # Example: + # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin' + # create_database 'matt_development' + # create_database 'matt_development', :charset => :big5 + def create_database(name, options = {}) + if options[:collation] + execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" + else + execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" + end + end + + # Drops a MySQL database. + # + # Example: + # drop_database('sebastian_development') + def drop_database(name) #:nodoc: + execute "DROP DATABASE IF EXISTS `#{name}`" + end + + def current_database + select_value 'SELECT DATABASE() as db' + end + + # Returns the database character set. + def charset + show_variable 'character_set_database' + end + + # Returns the database collation strategy. + def collation + show_variable 'collation_database' + end + + def tables(name = nil, database = nil) #:nodoc: + sql = ["SHOW TABLES", database].compact.join(' IN ') + + execute_and_free(sql, 'SCHEMA') do |result| + result.collect { |field| field.first } + end + 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 + + # Returns an array of indexes for the given table. + def indexes(table_name, name = nil) #:nodoc: + indexes = [] + current_index = nil + execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result| + each_hash(result) do |row| + if current_index != row[:Key_name] + next if row[:Key_name] == 'PRIMARY' # skip the primary key + current_index = row[:Key_name] + indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], []) + end + + indexes.last.columns << row[:Column_name] + indexes.last.lengths << row[:Sub_part] + end + end + + indexes + end + + # Returns an array of +Column+ objects for the table specified by +table_name+. + def columns(table_name, name = nil)#:nodoc: + sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" + execute_and_free(sql, 'SCHEMA') do |result| + each_hash(result).map do |field| + new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") + end + end + end + + def create_table(table_name, options = {}) #:nodoc: + 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 + + def add_column(table_name, column_name, type, options = {}) + execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}") + end + + def change_column_default(table_name, column_name, default) + column = column_for(table_name, column_name) + change_column table_name, column_name, column.sql_type, :default => default + end + + def change_column_null(table_name, column_name, null, default = nil) + column = column_for(table_name, column_name) + + unless null || default.nil? + execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") + end + + change_column table_name, column_name, column.sql_type, :null => null + end + + def change_column(table_name, column_name, type, options = {}) #:nodoc: + execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}") + end + + def rename_column(table_name, column_name, new_column_name) #:nodoc: + execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}") + end + + # Maps logical Rails types to MySQL-specific data types. + def type_to_sql(type, limit = nil, precision = nil, scale = nil) + return super unless type.to_s == 'integer' + + case limit + when 1; 'tinyint' + when 2; 'smallint' + when 3; 'mediumint' + when nil, 4, 11; 'int(11)' # compatibility with MySQL default + when 5..8; 'bigint' + else raise(ActiveRecordError, "No integer type has byte size #{limit}") + end + end + + def add_column_position!(sql, options) + if options[:first] + sql << " FIRST" + elsif options[:after] + sql << " AFTER #{quote_column_name(options[:after])}" + 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) + execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result| + keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] } + keys.length == 1 ? [keys.first, nil] : nil + end + end + + # Returns just a table's primary key + def primary_key(table) + pk_and_sequence = pk_and_sequence_for(table) + pk_and_sequence && pk_and_sequence.first + end + + def case_sensitive_modifier(node) + Arel::Nodes::Bin.new(node) + end + + def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) + where_sql + end + + protected + + def quoted_columns_for_index(column_names, options = {}) + length = options[:length] if options.is_a?(Hash) + + case length + when Hash + column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } + when Fixnum + column_names.map {|name| "#{quote_column_name(name)}(#{length})"} + else + column_names.map {|name| quote_column_name(name) } + end + end + + def translate_exception(exception, message) + case error_number(exception) + when 1062 + RecordNotUnique.new(message, exception) + when 1452 + InvalidForeignKey.new(message, exception) + else + super + end + end + + def add_column_sql(table_name, column_name, type, options = {}) + add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + add_column_options!(add_column_sql, options) + add_column_position!(add_column_sql, options) + add_column_sql + end + + def change_column_sql(table_name, column_name, type, options = {}) + column = column_for(table_name, column_name) + + unless options_include_default?(options) + options[:default] = column.default + end + + unless options.has_key?(:null) + options[:null] = column.null + end + + change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" + add_column_options!(change_column_sql, options) + add_column_position!(change_column_sql, options) + change_column_sql + end + + def rename_column_sql(table_name, column_name, new_column_name) + options = {} + + if column = columns(table_name).find { |c| c.name == column_name.to_s } + options[:default] = column.default + options[:null] = column.null + else + raise ActiveRecordError, "No such column: #{table_name}.#{column_name}" + end + + current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"] + rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" + add_column_options!(rename_column_sql, options) + rename_column_sql + end + + private + + def supports_views? + version[0] >= 5 + end + + def column_for(table_name, column_name) + unless column = columns(table_name).find { |c| c.name == column_name.to_s } + raise "No such column: #{table_name}.#{column_name}" + end + column + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index ef51f5ebca..00d9caa8ee 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,4 +1,4 @@ -# encoding: utf-8 +require 'active_record/connection_adapters/abstract_mysql_adapter' gem 'mysql2', '~> 0.3.6' require 'mysql2' @@ -20,31 +20,13 @@ module ActiveRecord end module ConnectionAdapters - class Mysql2IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc: - end - - class Mysql2Column < Column - BOOL = "tinyint(1)" - def extract_default(default) - if sql_type =~ /blob/i || type == :text - if default.blank? - return null ? nil : '' - else - raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" - end - elsif missing_default_forged_as_empty_string?(default) - nil - else - super - end - end + class Mysql2Adapter < AbstractMysqlAdapter + class Column < AbstractMysqlAdapter::Column # :nodoc: + BOOL = "tinyint(1)" - def has_default? - return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns - super - end + private - private + # FIXME: Combine with the mysql version and move to abstract adapter def simplified_type(field_type) return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL) @@ -56,155 +38,45 @@ module ActiveRecord super end end - - def extract_limit(sql_type) - case sql_type - when /blob|text/i - case sql_type - when /tiny/i - 255 - when /medium/i - 16777215 - when /long/i - 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases - else - super # we could return 65535 here, but we leave it undecorated by default - end - when /^bigint/i; 8 - when /^int/i; 4 - when /^mediumint/i; 3 - when /^smallint/i; 2 - when /^tinyint/i; 1 - else - super - end - end - - # MySQL misreports NOT NULL column default when none is given. - # We can't detect this for columns which may have a legitimate '' - # default (string) but we can for others (integer, datetime, boolean, - # and the rest). - # - # Test whether the column has default '', is not null, and is not - # a type allowing default ''. - def missing_default_forged_as_empty_string?(default) - type != :string && !null && default == '' - end - end - - class Mysql2Adapter < AbstractAdapter - cattr_accessor :emulate_booleans - self.emulate_booleans = true + end ADAPTER_NAME = 'Mysql2' - PRIMARY = "PRIMARY" - - LOST_CONNECTION_ERROR_MESSAGES = [ - "Server shutdown in progress", - "Broken pipe", - "Lost connection to MySQL server during query", - "MySQL server has gone away" ] - - QUOTED_TRUE, QUOTED_FALSE = '1', '0' - - NATIVE_DATABASE_TYPES = { - :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "int", :limit => 4 }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :timestamp => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "tinyint", :limit => 1 } - } def initialize(connection, logger, connection_options, config) - super(connection, logger) - @connection_options, @config = connection_options, config - @quoted_column_names, @quoted_table_names = {}, {} + super configure_connection end - def self.visitor_for(pool) # :nodoc: - Arel::Visitors::MySQL.new(pool) - end - - def adapter_name - ADAPTER_NAME - end - - # Returns true, since this connection adapter supports migrations. - def supports_migrations? - true - end - - def supports_primary_key? - true - end - - # Returns true, since this connection adapter supports savepoints. - def supports_savepoints? - true - end - - def native_database_types - NATIVE_DATABASE_TYPES - end + # HELPER METHODS =========================================== - # QUOTING ================================================== - - def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) - s = column.class.string_to_binary(value).unpack("H*")[0] - "x'#{s}'" - elsif value.kind_of?(BigDecimal) - value.to_s("F") + def each_hash(result) # :nodoc: + if block_given? + result.each(:as => :hash, :symbolize_keys => true) do |row| + yield row + end else - super + to_enum(:each_hash, result) end end - def quote_column_name(name) #:nodoc: - @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" + def new_column(field, default, type, null) # :nodoc: + Column.new(field, default, type, null) end - def quote_table_name(name) #:nodoc: - @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`') + def error_number(exception) + exception.error_number if exception.respond_to?(:error_number) end + # QUOTING ================================================== + def quote_string(string) @connection.escape(string) end - def quoted_true - QUOTED_TRUE - end - - def quoted_false - QUOTED_FALSE - end - def substitute_at(column, index) Arel.sql "\0" end - # REFERENTIAL INTEGRITY ==================================== - - def disable_referential_integrity(&block) #:nodoc: - old = select_value("SELECT @@FOREIGN_KEY_CHECKS") - - begin - update("SET FOREIGN_KEY_CHECKS = 0") - yield - ensure - update("SET FOREIGN_KEY_CHECKS = #{old}") - end - end - # CONNECTION MANAGEMENT ==================================== def active? @@ -217,11 +89,6 @@ module ActiveRecord connect end - # this is set to true in 2.3, but we don't want it to be - def requires_reloading? - false - end - # Disconnects from the database if already connected. # Otherwise, this method does nothing. def disconnect! @@ -277,17 +144,22 @@ module ActiveRecord # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been # made since we established the connection @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone - if name == :skip_logging - @connection.query(sql) - else - log(sql, name) { @connection.query(sql) } - end - rescue ActiveRecord::StatementInvalid => exception - if exception.message.split(":").first =~ /Packets out of order/ - raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." - else - raise - end + + super + end + + def exec_query(sql, name = 'SQL', binds = []) + result = execute(sql, name) + ActiveRecord::Result.new(result.fields, result.to_a) + end + + alias exec_without_stmt exec_query + + # Returns an array of record hashes with the column names as keys and + # column values as values. + def select(sql, name = nil, binds = []) + binds = binds.dup + exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a end def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) @@ -316,379 +188,35 @@ module ActiveRecord @connection.last_id end - def update_sql(sql, name = nil) - super - @connection.affected_rows - end - - def begin_db_transaction - execute "BEGIN" - rescue Exception - # Transactions aren't supported - end - - def commit_db_transaction - execute "COMMIT" - rescue Exception - # Transactions aren't supported - end - - def rollback_db_transaction - execute "ROLLBACK" - rescue Exception - # Transactions aren't supported - end - - def create_savepoint - execute("SAVEPOINT #{current_savepoint_name}") - end - - def rollback_to_savepoint - execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") - end - - def release_savepoint - execute("RELEASE SAVEPOINT #{current_savepoint_name}") - end - - # SCHEMA STATEMENTS ======================================== - - def structure_dump - if supports_views? - sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'" - else - sql = "SHOW TABLES" - end - - select_all(sql).inject("") do |structure, table| - table.delete('Table_type') - structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n" - 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) - end - - # Create a new MySQL database with optional :charset and :collation. - # Charset defaults to utf8. - # - # Example: - # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin' - # create_database 'matt_development' - # create_database 'matt_development', :charset => :big5 - def create_database(name, options = {}) - if options[:collation] - execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" - else - execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" - end - end - - # Drops a MySQL database. - # - # Example: - # drop_database('sebastian_development') - def drop_database(name) #:nodoc: - execute "DROP DATABASE IF EXISTS `#{name}`" - end - - def current_database - select_value 'SELECT DATABASE() as db' - end - - # Returns the database character set. - def charset - show_variable 'character_set_database' - end - - # Returns the database collation strategy. - def collation - show_variable 'collation_database' - end - - def tables(name = nil, database = nil) #:nodoc: - sql = ["SHOW TABLES", database].compact.join(' IN ') - execute(sql, 'SCHEMA').collect do |field| - field.first - end - 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 - - # 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)}", '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 - current_index = row[:Key_name] - indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], []) - end - - indexes.last.columns << row[:Column_name] - indexes.last.lengths << row[:Sub_part] - end - 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, 'SCHEMA') - result.each(:symbolize_keys => true, :as => :hash) { |field| - columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") - } - columns - end - - def create_table(table_name, options = {}) - 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 - - def add_column(table_name, column_name, type, options = {}) - add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(add_column_sql, options) - add_column_position!(add_column_sql, options) - execute(add_column_sql) - end - - def change_column_default(table_name, column_name, default) - column = column_for(table_name, column_name) - change_column table_name, column_name, column.sql_type, :default => default - end - - def change_column_null(table_name, column_name, null, default = nil) - column = column_for(table_name, column_name) - - unless null || default.nil? - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") - end - - change_column table_name, column_name, column.sql_type, :null => null - end - - def change_column(table_name, column_name, type, options = {}) - column = column_for(table_name, column_name) - - unless options_include_default?(options) - options[:default] = column.default - end - - unless options.has_key?(:null) - options[:null] = column.null - end - - change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(change_column_sql, options) - add_column_position!(change_column_sql, options) - execute(change_column_sql) - end - - def rename_column(table_name, column_name, new_column_name) - options = {} - if column = columns(table_name).find { |c| c.name == column_name.to_s } - options[:default] = column.default - options[:null] = column.null - else - raise ActiveRecordError, "No such column: #{table_name}.#{column_name}" - end - current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"] - rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" - add_column_options!(rename_column_sql, options) - execute(rename_column_sql) - end - - # Maps logical Rails types to MySQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil) - return super unless type.to_s == 'integer' - - case limit - when 1; 'tinyint' - when 2; 'smallint' - when 3; 'mediumint' - when nil, 4, 11; 'int(11)' # compatibility with MySQL default - when 5..8; 'bigint' - else raise(ActiveRecordError, "No integer type has byte size #{limit}") - end - end + private - def add_column_position!(sql, options) - if options[:first] - sql << " FIRST" - elsif options[:after] - sql << " AFTER #{quote_column_name(options[:after])}" - end + def connect + @connection = Mysql2::Client.new(@config) + configure_connection end - # SHOW VARIABLES LIKE 'name'. - def show_variable(name) - variables = select_all("SHOW VARIABLES LIKE '#{name}'") - variables.first['Value'] unless variables.empty? - end + def configure_connection + @connection.query_options.merge!(:as => :array) - # Returns a table's primary key and belonging sequence. - def pk_and_sequence_for(table) - keys = [] - 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 - keys.length == 1 ? [keys.first, nil] : nil - end + # By default, MySQL 'where id is null' selects the last inserted id. + # Turn this off. http://dev.rubyonrails.org/ticket/6778 + variable_assignments = ['SQL_AUTO_IS_NULL=0'] + encoding = @config[:encoding] - # Returns just a table's primary key - def primary_key(table) - pk_and_sequence = pk_and_sequence_for(table) - pk_and_sequence && pk_and_sequence.first - end + # make sure we set the encoding + variable_assignments << "NAMES '#{encoding}'" if encoding - def case_sensitive_modifier(node) - Arel::Nodes::Bin.new(node) - end + # increase timeout so mysql server doesn't disconnect us + wait_timeout = @config[:wait_timeout] + wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum) + variable_assignments << "@@wait_timeout = #{wait_timeout}" - def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) - where_sql + execute("SET #{variable_assignments.join(', ')}", :skip_logging) end - # In the simple case, MySQL allows us to place JOINs directly into the UPDATE - # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support - # these, we must use a subquery. However, MySQL is too stupid to create a - # temporary table for this automatically, so we have to give it some prompting - # in the form of a subsubquery. Ugh! - def join_to_update(update, select) #:nodoc: - if select.limit || select.offset || select.orders.any? - subsubselect = select.clone - subsubselect.projections = [update.key] - - subselect = Arel::SelectManager.new(select.engine) - subselect.project Arel.sql(update.key.name) - subselect.from subsubselect.as('__active_record_temp') - - update.where update.key.in(subselect) - else - update.table select.source - update.wheres = select.constraints - end + def version + @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } end - - protected - def quoted_columns_for_index(column_names, options = {}) - length = options[:length] if options.is_a?(Hash) - - case length - when Hash - column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } - when Fixnum - column_names.map {|name| "#{quote_column_name(name)}(#{length})"} - else - column_names.map {|name| quote_column_name(name) } - end - end - - def translate_exception(exception, message) - return super unless exception.respond_to?(:error_number) - - case exception.error_number - when 1062 - RecordNotUnique.new(message, exception) - when 1452 - InvalidForeignKey.new(message, exception) - else - super - end - end - - private - def connect - @connection = Mysql2::Client.new(@config) - configure_connection - end - - def configure_connection - @connection.query_options.merge!(:as => :array) - - # By default, MySQL 'where id is null' selects the last inserted id. - # Turn this off. http://dev.rubyonrails.org/ticket/6778 - variable_assignments = ['SQL_AUTO_IS_NULL=0'] - encoding = @config[:encoding] - - # make sure we set the encoding - variable_assignments << "NAMES '#{encoding}'" if encoding - - # increase timeout so mysql server doesn't disconnect us - wait_timeout = @config[:wait_timeout] - wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum) - variable_assignments << "@@wait_timeout = #{wait_timeout}" - - execute("SET #{variable_assignments.join(', ')}", :skip_logging) - end - - # Returns an array of record hashes with the column names as keys and - # column values as values. - def select(sql, name = nil, binds = []) - binds = binds.dup - exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a - end - - def exec_query(sql, name = 'SQL', binds = []) - @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone - - log(sql, name, binds) do - begin - result = @connection.query(sql) - rescue ActiveRecord::StatementInvalid => exception - if exception.message.split(":").first =~ /Packets out of order/ - raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." - else - raise - end - end - - ActiveRecord::Result.new(result.fields, result.to_a) - end - end - - def supports_views? - version[0] >= 5 - end - - def version - @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i } - end - - def column_for(table_name, column_name) - unless column = columns(table_name).find { |c| c.name == column_name.to_s } - raise "No such column: #{table_name}.#{column_name}" - end - column - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index b844e5ab10..d61875195a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -1,6 +1,5 @@ -require 'active_record/connection_adapters/abstract_adapter' -require 'active_support/core_ext/object/blank' -require 'set' +require 'active_record/connection_adapters/abstract_mysql_adapter' +require 'active_support/core_ext/hash/keys' gem 'mysql', '~> 2.8.1' require 'mysql' @@ -40,9 +39,28 @@ module ActiveRecord end module ConnectionAdapters - class MysqlColumn < Column #:nodoc: - class << self - def string_to_time(value) + # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with + # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/). + # + # Options: + # + # * :host - Defaults to "localhost". + # * :port - Defaults to 3306. + # * :socket - Defaults to "/tmp/mysql.sock". + # * :username - Defaults to "root" + # * :password - Defaults to nothing. + # * :database - The name of the database. No default, must be provided. + # * :encoding - (Optional) Sets the client encoding by executing "SET NAMES " after connection. + # * :reconnect - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). + # * :sslca - Necessary to use MySQL with an SSL connection. + # * :sslkey - Necessary to use MySQL with an SSL connection. + # * :sslcert - Necessary to use MySQL with an SSL connection. + # * :sslcapath - Necessary to use MySQL with an SSL connection. + # * :sslcipher - Necessary to use MySQL with an SSL connection. + # + class MysqlAdapter < AbstractMysqlAdapter + class Column < AbstractMysqlAdapter::Column #:nodoc: + def self.string_to_time(value) return super unless Mysql::Time === value new_time( value.year, @@ -54,152 +72,36 @@ module ActiveRecord value.second_part) end - def string_to_dummy_time(v) + def self.string_to_dummy_time(v) return super unless Mysql::Time === v new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part) end - def string_to_date(v) + def self.string_to_date(v) return super unless Mysql::Time === v new_date(v.year, v.month, v.day) end - end - def extract_default(default) - if sql_type =~ /blob/i || type == :text - if default.blank? - return null ? nil : '' - else - raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" - end - elsif missing_default_forged_as_empty_string?(default) - nil - else - super - end - end + private - def has_default? - return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns - super - end - - private + # FIXME: Combine with the mysql2 version and move to abstract adapter def simplified_type(field_type) return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)") return :string if field_type =~ /enum/i super end - - def extract_limit(sql_type) - case sql_type - when /blob|text/i - case sql_type - when /tiny/i - 255 - when /medium/i - 16777215 - when /long/i - 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases - else - super # we could return 65535 here, but we leave it undecorated by default - end - when /^bigint/i; 8 - when /^int/i; 4 - when /^mediumint/i; 3 - when /^smallint/i; 2 - when /^tinyint/i; 1 - else - super - end - end - - # MySQL misreports NOT NULL column default when none is given. - # We can't detect this for columns which may have a legitimate '' - # default (string) but we can for others (integer, datetime, boolean, - # and the rest). - # - # Test whether the column has default '', is not null, and is not - # a type allowing default ''. - def missing_default_forged_as_empty_string?(default) - type != :string && !null && default == '' - end - end - - # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with - # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/). - # - # Options: - # - # * :host - Defaults to "localhost". - # * :port - Defaults to 3306. - # * :socket - Defaults to "/tmp/mysql.sock". - # * :username - Defaults to "root" - # * :password - Defaults to nothing. - # * :database - The name of the database. No default, must be provided. - # * :encoding - (Optional) Sets the client encoding by executing "SET NAMES " after connection. - # * :reconnect - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html). - # * :sslca - Necessary to use MySQL with an SSL connection. - # * :sslkey - Necessary to use MySQL with an SSL connection. - # * :sslcert - Necessary to use MySQL with an SSL connection. - # * :sslcapath - Necessary to use MySQL with an SSL connection. - # * :sslcipher - Necessary to use MySQL with an SSL connection. - # - class MysqlAdapter < AbstractAdapter - - ## - # :singleton-method: - # By default, the MysqlAdapter will consider all columns of type tinyint(1) - # as boolean. If you wish to disable this emulation (which was the default - # behavior in versions 0.13.1 and earlier) you can add the following line - # to your application.rb file: - # - # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false - cattr_accessor :emulate_booleans - self.emulate_booleans = true + end ADAPTER_NAME = 'MySQL' - LOST_CONNECTION_ERROR_MESSAGES = [ - "Server shutdown in progress", - "Broken pipe", - "Lost connection to MySQL server during query", - "MySQL server has gone away" ] - - QUOTED_TRUE, QUOTED_FALSE = '1', '0' - - NATIVE_DATABASE_TYPES = { - :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY", - :string => { :name => "varchar", :limit => 255 }, - :text => { :name => "text" }, - :integer => { :name => "int", :limit => 4 }, - :float => { :name => "float" }, - :decimal => { :name => "decimal" }, - :datetime => { :name => "datetime" }, - :timestamp => { :name => "datetime" }, - :time => { :name => "time" }, - :date => { :name => "date" }, - :binary => { :name => "blob" }, - :boolean => { :name => "tinyint", :limit => 1 } - } - def initialize(connection, logger, connection_options, config) - super(connection, logger) - @connection_options, @config = connection_options, config - @quoted_column_names, @quoted_table_names = {}, {} + super @statements = {} @client_encoding = nil connect end - def self.visitor_for(pool) # :nodoc: - Arel::Visitors::MySQL.new(pool) - end - - def adapter_name #:nodoc: - ADAPTER_NAME - end - + # FIXME: Move to abstract adapter def supports_bulk_alter? #:nodoc: true end @@ -210,78 +112,39 @@ module ActiveRecord true end - # Returns true, since this connection adapter supports migrations. - def supports_migrations? #:nodoc: - true - end + # HELPER METHODS =========================================== - # Returns true. - def supports_primary_key? #:nodoc: - true + def each_hash(result) # :nodoc: + if block_given? + result.each_hash do |row| + row.symbolize_keys! + yield row + end + else + to_enum(:each_hash, result) + end end - # Returns true, since this connection adapter supports savepoints. - def supports_savepoints? #:nodoc: - true + def new_column(field, default, type, null) # :nodoc: + Column.new(field, default, type, null) end - def native_database_types #:nodoc: - NATIVE_DATABASE_TYPES + def error_number(exception) # :nodoc: + exception.errno if exception.respond_to?(:errno) end - # QUOTING ================================================== - def quote(value, column = nil) - if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary) - s = column.class.string_to_binary(value).unpack("H*")[0] - "x'#{s}'" - elsif value.kind_of?(BigDecimal) - value.to_s("F") - else - super - end - end - def type_cast(value, column) return super unless value == true || value == false value ? 1 : 0 end - def quote_column_name(name) #:nodoc: - @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`" - end - - def quote_table_name(name) #:nodoc: - @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`') - end - def quote_string(string) #:nodoc: @connection.quote(string) end - def quoted_true - QUOTED_TRUE - end - - def quoted_false - QUOTED_FALSE - end - - # REFERENTIAL INTEGRITY ==================================== - - def disable_referential_integrity #:nodoc: - old = select_value("SELECT @@FOREIGN_KEY_CHECKS") - - begin - update("SET FOREIGN_KEY_CHECKS = 0") - yield - ensure - update("SET FOREIGN_KEY_CHECKS = #{old}") - end - end - # CONNECTION MANAGEMENT ==================================== def active? @@ -425,20 +288,11 @@ module ActiveRecord end end - # Executes an SQL query and returns a MySQL::Result object. Note that you have to free - # the Result object after you're done using it. - def execute(sql, name = nil) #:nodoc: - if name == :skip_logging - @connection.query(sql) - else - log(sql, name) { @connection.query(sql) } - end - rescue ActiveRecord::StatementInvalid => exception - if exception.message.split(":").first =~ /Packets out of order/ - raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings." - else - raise - end + def execute_and_free(sql, name = nil) + result = execute(sql, name) + ret = yield result + result.free + ret end def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc: @@ -447,11 +301,6 @@ module ActiveRecord end alias :create :insert_sql - def update_sql(sql, name = nil) #:nodoc: - super - @connection.affected_rows - end - def exec_delete(sql, name, binds) log(sql, name, binds) do exec_stmt(sql, name, binds) do |cols, stmt| @@ -467,172 +316,8 @@ module ActiveRecord # Transactions aren't supported end - def commit_db_transaction #:nodoc: - execute "COMMIT" - rescue Exception - # Transactions aren't supported - end - - def rollback_db_transaction #:nodoc: - execute "ROLLBACK" - rescue Exception - # Transactions aren't supported - end - - def create_savepoint - execute("SAVEPOINT #{current_savepoint_name}") - end - - def rollback_to_savepoint - execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}") - end - - def release_savepoint - execute("RELEASE SAVEPOINT #{current_savepoint_name}") - end - - # In the simple case, MySQL allows us to place JOINs directly into the UPDATE - # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support - # these, we must use a subquery. However, MySQL is too stupid to create a - # temporary table for this automatically, so we have to give it some prompting - # in the form of a subsubquery. Ugh! - def join_to_update(update, select) #:nodoc: - if select.limit || select.offset || select.orders.any? - subsubselect = select.clone - subsubselect.projections = [update.key] - - subselect = Arel::SelectManager.new(select.engine) - subselect.project Arel.sql(update.key.name) - subselect.from subsubselect.as('__active_record_temp') - - update.where update.key.in(subselect) - else - update.table select.source - update.wheres = select.constraints - end - end - # SCHEMA STATEMENTS ======================================== - def structure_dump #:nodoc: - if supports_views? - sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'" - else - sql = "SHOW TABLES" - end - - select_all(sql).map do |table| - table.delete('Table_type') - sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}" - exec_without_stmt(sql).first['Create Table'] + ";\n\n" - 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) - end - - # Create a new MySQL database with optional :charset and :collation. - # Charset defaults to utf8. - # - # Example: - # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin' - # create_database 'matt_development' - # create_database 'matt_development', :charset => :big5 - def create_database(name, options = {}) - if options[:collation] - execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`" - else - execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`" - end - end - - # Drops a MySQL database. - # - # Example: - # drop_database 'sebastian_development' - def drop_database(name) #:nodoc: - execute "DROP DATABASE IF EXISTS `#{name}`" - end - - def current_database - select_value 'SELECT DATABASE() as db' - end - - # Returns the database character set. - def charset - show_variable 'character_set_database' - end - - # Returns the database collation strategy. - def collation - show_variable 'collation_database' - end - - def tables(name = nil, database = nil) #:nodoc: - result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA') - tables = result.collect { |field| field[0] } - result.free - 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 - - # Returns an array of indexes for the given table. - def indexes(table_name, name = nil)#:nodoc: - indexes = [] - current_index = nil - result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name) - result.each do |row| - if current_index != row[2] - next if row[2] == "PRIMARY" # skip the primary key - current_index = row[2] - indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], []) - end - - indexes.last.columns << row[4] - indexes.last.lengths << row[7] - end - result.free - 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)}" - result = execute(sql, 'SCHEMA') - columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") } - result.free - columns - end - - def create_table(table_name, options = {}) #:nodoc: - 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 - def bulk_change_table(table_name, operations) #:nodoc: sqls = operations.map do |command, args| table, arguments = args.shift, args @@ -648,177 +333,33 @@ module ActiveRecord execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") end - def add_column(table_name, column_name, type, options = {}) - execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}") - end - - def change_column_default(table_name, column_name, default) #:nodoc: - column = column_for(table_name, column_name) - change_column table_name, column_name, column.sql_type, :default => default - end - - def change_column_null(table_name, column_name, null, default = nil) - column = column_for(table_name, column_name) - - unless null || default.nil? - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") - end - - change_column table_name, column_name, column.sql_type, :null => null - end - - def change_column(table_name, column_name, type, options = {}) #:nodoc: - execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}") - end - - def rename_column(table_name, column_name, new_column_name) #:nodoc: - execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}") - end - - # Maps logical Rails types to MySQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil) - return super unless type.to_s == 'integer' - - case limit - when 1; 'tinyint' - when 2; 'smallint' - when 3; 'mediumint' - when nil, 4, 11; 'int(11)' # compatibility with MySQL default - when 5..8; 'bigint' - else raise(ActiveRecordError, "No integer type has byte size #{limit}") - end - end - - def add_column_position!(sql, options) - if options[:first] - sql << " FIRST" - elsif options[:after] - sql << " AFTER #{quote_column_name(options[:after])}" - end - end + protected - # SHOW VARIABLES LIKE 'name' - def show_variable(name) - variables = select_all("SHOW VARIABLES LIKE '#{name}'") - variables.first['Value'] unless variables.empty? + def remove_column_sql(table_name, *column_names) + columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" } end + alias :remove_columns_sql :remove_column - # Returns a table's primary key and belonging sequence. - def pk_and_sequence_for(table) #:nodoc: - keys = [] - result = execute("describe #{quote_table_name(table)}", 'SCHEMA') - result.each_hash do |h| - keys << h["Field"]if h["Key"] == "PRI" - end - result.free - keys.length == 1 ? [keys.first, nil] : nil + def add_index_sql(table_name, column_name, options = {}) + index_name, index_type, index_columns = add_index_options(table_name, column_name, options) + "ADD #{index_type} INDEX #{index_name} (#{index_columns})" end - # Returns just a table's primary key - def primary_key(table) - pk_and_sequence = pk_and_sequence_for(table) - pk_and_sequence && pk_and_sequence.first + def remove_index_sql(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + "DROP INDEX #{index_name}" end - def case_sensitive_modifier(node) - Arel::Nodes::Bin.new(node) + def add_timestamps_sql(table_name) + [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)] end - def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) - where_sql + def remove_timestamps_sql(table_name) + [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] end - protected - def quoted_columns_for_index(column_names, options = {}) - length = options[:length] if options.is_a?(Hash) - - case length - when Hash - column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } - when Fixnum - column_names.map {|name| "#{quote_column_name(name)}(#{length})"} - else - column_names.map {|name| quote_column_name(name) } - end - end - - def translate_exception(exception, message) - return super unless exception.respond_to?(:errno) - - case exception.errno - when 1062 - RecordNotUnique.new(message, exception) - when 1452 - InvalidForeignKey.new(message, exception) - else - super - end - end - - def add_column_sql(table_name, column_name, type, options = {}) - add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(add_column_sql, options) - add_column_position!(add_column_sql, options) - add_column_sql - end - - def remove_column_sql(table_name, *column_names) - columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" } - end - alias :remove_columns_sql :remove_column - - def change_column_sql(table_name, column_name, type, options = {}) - column = column_for(table_name, column_name) - - unless options_include_default?(options) - options[:default] = column.default - end - - unless options.has_key?(:null) - options[:null] = column.null - end - - change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" - add_column_options!(change_column_sql, options) - add_column_position!(change_column_sql, options) - change_column_sql - end - - def rename_column_sql(table_name, column_name, new_column_name) - options = {} - - if column = columns(table_name).find { |c| c.name == column_name.to_s } - options[:default] = column.default - options[:null] = column.null - else - raise ActiveRecordError, "No such column: #{table_name}.#{column_name}" - end - - current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"] - rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}" - add_column_options!(rename_column_sql, options) - rename_column_sql - end - - def add_index_sql(table_name, column_name, options = {}) - index_name, index_type, index_columns = add_index_options(table_name, column_name, options) - "ADD #{index_type} INDEX #{index_name} (#{index_columns})" - end - - def remove_index_sql(table_name, options = {}) - index_name = index_name_for_remove(table_name, options) - "DROP INDEX #{index_name}" - end - - def add_timestamps_sql(table_name) - [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)] - end - - def remove_timestamps_sql(table_name) - [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] - end - private + def exec_stmt(sql, name, binds) cache = {} if binds.empty? @@ -830,7 +371,6 @@ module ActiveRecord stmt = cache[:stmt] end - begin stmt.execute(*binds.map { |col, val| type_cast(val, col) }) rescue Mysql::Error => e @@ -859,59 +399,48 @@ module ActiveRecord result end - def connect - encoding = @config[:encoding] - if encoding - @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil - end - - if @config[:sslca] || @config[:sslkey] - @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) - end + def connect + encoding = @config[:encoding] + if encoding + @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil + end - @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout] - @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout] - @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout] + if @config[:sslca] || @config[:sslkey] + @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) + end - @connection.real_connect(*@connection_options) + @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout] + @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout] + @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout] - # reconnect must be set after real_connect is called, because real_connect sets it to false internally - @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=) + @connection.real_connect(*@connection_options) - configure_connection - end + # reconnect must be set after real_connect is called, because real_connect sets it to false internally + @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=) - def configure_connection - encoding = @config[:encoding] - execute("SET NAMES '#{encoding}'", :skip_logging) if encoding - - # By default, MySQL 'where id is null' selects the last inserted id. - # Turn this off. http://dev.rubyonrails.org/ticket/6778 - execute("SET SQL_AUTO_IS_NULL=0", :skip_logging) - end + configure_connection + end - def select(sql, name = nil, binds = []) - @connection.query_with_result = true - rows = exec_query(sql, name, binds).to_a - @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped - rows - end + def configure_connection + encoding = @config[:encoding] + execute("SET NAMES '#{encoding}'", :skip_logging) if encoding - def supports_views? - version[0] >= 5 - end + # By default, MySQL 'where id is null' selects the last inserted id. + # Turn this off. http://dev.rubyonrails.org/ticket/6778 + execute("SET SQL_AUTO_IS_NULL=0", :skip_logging) + 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 + def select(sql, name = nil, binds = []) + @connection.query_with_result = true + rows = exec_query(sql, name, binds).to_a + @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped + rows + end - def column_for(table_name, column_name) - unless column = columns(table_name).find { |c| c.name == column_name.to_s } - raise "No such column: #{table_name}.#{column_name}" - end - column - 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 end end end -- cgit v1.2.3 From 4fcd847c8d9fb2b22e1c2e3c840c8d1c590b56b4 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 29 Aug 2011 12:25:15 +0100 Subject: Extract simplified_type into the abstract class --- .../connection_adapters/abstract_mysql_adapter.rb | 19 ++++++++++++++++++- .../connection_adapters/mysql2_adapter.rb | 18 +++--------------- .../connection_adapters/mysql_adapter.rb | 10 +++------- 3 files changed, 24 insertions(+), 23 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 72cf490d7e..548ca83353 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/object/blank' module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter - class Column < ConnectionAdapters::Column + class Column < ConnectionAdapters::Column # :nodoc: def extract_default(default) if sql_type =~ /blob/i || type == :text if default.blank? @@ -23,8 +23,25 @@ module ActiveRecord super end + # Must return the relevant concrete adapter + def adapter + raise NotImplementedError + end + private + def simplified_type(field_type) + return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)") + + case field_type + when /enum/i, /set/i then :string + when /year/i then :integer + when /bit/i then :binary + else + super + end + end + def extract_limit(sql_type) case sql_type when /blob|text/i diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 00d9caa8ee..8b574518e5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -21,22 +21,10 @@ module ActiveRecord module ConnectionAdapters class Mysql2Adapter < AbstractMysqlAdapter - class Column < AbstractMysqlAdapter::Column # :nodoc: - BOOL = "tinyint(1)" - - private - - # FIXME: Combine with the mysql version and move to abstract adapter - def simplified_type(field_type) - return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL) - case field_type - when /enum/i, /set/i then :string - when /year/i then :integer - when /bit/i then :binary - else - super - end + class Column < AbstractMysqlAdapter::Column # :nodoc: + def adapter + Mysql2Adapter end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index d61875195a..182db65165 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -59,6 +59,7 @@ module ActiveRecord # * :sslcipher - Necessary to use MySQL with an SSL connection. # class MysqlAdapter < AbstractMysqlAdapter + class Column < AbstractMysqlAdapter::Column #:nodoc: def self.string_to_time(value) return super unless Mysql::Time === value @@ -82,13 +83,8 @@ module ActiveRecord new_date(v.year, v.month, v.day) end - private - - # FIXME: Combine with the mysql2 version and move to abstract adapter - def simplified_type(field_type) - return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)") - return :string if field_type =~ /enum/i - super + def adapter + MysqlAdapter end end -- cgit v1.2.3 From fd22d040fef48778a519825dd2f0cf2fd73a8965 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 29 Aug 2011 12:43:17 +0100 Subject: Move the bulk alter table code into the abstract mysql adapter, hence it is supported for mysql2 as well now. --- .../connection_adapters/abstract_mysql_adapter.rb | 42 +++++++++++++++++++ .../connection_adapters/mysql_adapter.rb | 47 ---------------------- 2 files changed, 42 insertions(+), 47 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 548ca83353..4b7c74e0b8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -140,6 +140,10 @@ module ActiveRecord true end + def supports_bulk_alter? #:nodoc: + true + end + def native_database_types NATIVE_DATABASE_TYPES end @@ -401,6 +405,21 @@ module ActiveRecord super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB")) end + def bulk_change_table(table_name, operations) #:nodoc: + sqls = operations.map do |command, args| + table, arguments = args.shift, args + method = :"#{command}_sql" + + if respond_to?(method) + send(method, table, *arguments) + else + raise "Unknown method called : #{method}(#{arguments.inspect})" + end + end.flatten.join(", ") + + execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") + end + # Renames a table. # # Example: @@ -552,6 +571,29 @@ module ActiveRecord rename_column_sql end + def remove_column_sql(table_name, *column_names) + columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" } + end + alias :remove_columns_sql :remove_column + + def add_index_sql(table_name, column_name, options = {}) + index_name, index_type, index_columns = add_index_options(table_name, column_name, options) + "ADD #{index_type} INDEX #{index_name} (#{index_columns})" + end + + def remove_index_sql(table_name, options = {}) + index_name = index_name_for_remove(table_name, options) + "DROP INDEX #{index_name}" + end + + def add_timestamps_sql(table_name) + [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)] + end + + def remove_timestamps_sql(table_name) + [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] + end + private def supports_views? diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 182db65165..e9bdcc2104 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -97,11 +97,6 @@ module ActiveRecord connect end - # FIXME: Move to abstract adapter - def supports_bulk_alter? #:nodoc: - true - end - # Returns true, since this connection adapter supports prepared statement # caching. def supports_statement_cache? @@ -312,48 +307,6 @@ module ActiveRecord # Transactions aren't supported end - # SCHEMA STATEMENTS ======================================== - - def bulk_change_table(table_name, operations) #:nodoc: - sqls = operations.map do |command, args| - table, arguments = args.shift, args - method = :"#{command}_sql" - - if respond_to?(method) - send(method, table, *arguments) - else - raise "Unknown method called : #{method}(#{arguments.inspect})" - end - end.flatten.join(", ") - - execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") - end - - protected - - def remove_column_sql(table_name, *column_names) - columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" } - end - alias :remove_columns_sql :remove_column - - def add_index_sql(table_name, column_name, options = {}) - index_name, index_type, index_columns = add_index_options(table_name, column_name, options) - "ADD #{index_type} INDEX #{index_name} (#{index_columns})" - end - - def remove_index_sql(table_name, options = {}) - index_name = index_name_for_remove(table_name, options) - "DROP INDEX #{index_name}" - end - - def add_timestamps_sql(table_name) - [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)] - end - - def remove_timestamps_sql(table_name) - [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] - end - private def exec_stmt(sql, name, binds) -- cgit v1.2.3 From 735d985b0162976e7e900cf36d4cbb0d657fb5e9 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 29 Aug 2011 15:01:17 +0100 Subject: The join_nodes must be passed to the JoinDependency initializer and therefore counted by the alias tracker. This is because the association_joins are aliased on initialization and then the tables are cached, so it is no use to alias the join_nodes later. Fixes #2556. --- .../lib/active_record/associations/alias_tracker.rb | 18 ++++++++++++------ .../lib/active_record/relation/query_methods.rb | 9 ++------- 2 files changed, 14 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 92ed844a2e..0248c7483c 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -53,12 +53,18 @@ module ActiveRecord # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase quoted_name = connection.quote_table_name(name).downcase - table_joins.map { |join| - # Table names + table aliases - join.left.downcase.scan( - /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ - ).size - }.sum + counts = table_joins.map do |join| + if join.is_a?(Arel::Nodes::StringJoin) + # Table names + table aliases + join.left.downcase.scan( + /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ + ).size + else + join.left.table_name == name ? 1 : 0 + end + end + + counts.sum end def truncate(name) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1654ae1eac..7eda9ad8e8 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -254,12 +254,12 @@ module ActiveRecord association_joins = buckets['association_join'] || [] stashed_association_joins = buckets['stashed_join'] || [] - join_nodes = buckets['join_node'] || [] + join_nodes = (buckets['join_node'] || []).uniq string_joins = (buckets['string_join'] || []).map { |x| x.strip }.uniq - join_list = custom_join_ast(manager, string_joins) + join_list = join_nodes + custom_join_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( @klass, @@ -267,10 +267,6 @@ module ActiveRecord join_list ) - join_nodes.each do |join| - join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase) - end - join_dependency.graft(*stashed_association_joins) @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty? @@ -280,7 +276,6 @@ module ActiveRecord association.join_to(manager) end - manager.join_sources.concat join_nodes.uniq manager.join_sources.concat join_list manager -- cgit v1.2.3 From c59c9bb8bc275a2be8695d4d431a0512d29353f1 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 29 Aug 2011 17:39:09 +0100 Subject: Move clear_timestamp_attributes into Timestamp module --- activerecord/lib/active_record/base.rb | 10 +--------- activerecord/lib/active_record/timestamp.rb | 14 +++++++++++++- 2 files changed, 14 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 03aea81d2c..374791deb1 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1849,7 +1849,7 @@ MSG ensure_proper_type populate_with_current_scope_attributes - clear_timestamp_attributes + super end # Returns +true+ if the record is read only. Records loaded through joins with piggy-back @@ -2113,14 +2113,6 @@ MSG send("#{att}=", value) if respond_to?("#{att}=") end end - - # Clear attributes and changed_attributes - def clear_timestamp_attributes - all_timestamp_attributes_in_model.each do |attribute_name| - self[attribute_name] = nil - changed_attributes.delete(attribute_name) - end - end end Base.class_eval do diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 1511c71ffc..6b8c52861e 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -37,6 +37,11 @@ module ActiveRecord self.record_timestamps = true end + def initialize_dup(other) + clear_timestamp_attributes + super + end + private def create #:nodoc: @@ -95,6 +100,13 @@ module ActiveRecord def current_time_from_proper_timezone #:nodoc: self.class.default_timezone == :utc ? Time.now.utc : Time.now end + + # Clear attributes and changed_attributes + def clear_timestamp_attributes + all_timestamp_attributes_in_model.each do |attribute_name| + self[attribute_name] = nil + changed_attributes.delete(attribute_name) + end + end end end - -- cgit v1.2.3 From 92619e4f78e9417dbfa3e22acce25ff343f45f0b Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 29 Aug 2011 23:04:50 +0100 Subject: Fix test failures on 1.8.7, since Object#initialize_dup is not defined there (and this call to super is no essential, so easiest to simply remove) --- activerecord/lib/active_record/timestamp.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 6b8c52861e..cccac6ffd7 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -39,7 +39,6 @@ module ActiveRecord def initialize_dup(other) clear_timestamp_attributes - super end private -- cgit v1.2.3 From 84dad446c6a23a15f67b9d558e8039891a008bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Mej=C3=ADa?= Date: Sat, 27 Aug 2011 23:10:25 -0500 Subject: Adding first_or_create, first_or_create!, first_or_new and first_or_build to Active Record. This let's you write things like: User.where(:first_name => "Scarlett").first_or_create!(:last_name => "Johansson", :hot => true) Related to #2420. --- activerecord/lib/active_record/base.rb | 1 + activerecord/lib/active_record/relation.rb | 43 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 374791deb1..2979ad1cb3 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -442,6 +442,7 @@ module ActiveRecord #:nodoc: class << self # Class methods delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped + delegate :first_or_create, :first_or_create!, :first_or_new, :first_or_build, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 15fd1a58c8..461237c7ad 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -94,6 +94,49 @@ module ActiveRecord scoping { @klass.create!(*args, &block) } end + # Tries to load the first record; if it fails, then create is called with the same arguments as this method. + # + # Expects arguments in the same format as Base.create. + # + # ==== Examples + # # Find the first user named Scarlett or create a new one. + # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson') + # # => + # + # # Find the first user named Scarlett or create one with a different last name. + # # We already have one Scarlett, so she'll be returned. + # User.where(:first_name => 'Scarlett').first_or_create do |user| + # user.last_name = "O'Hara" + # end + # # => + # + # # Find the first user named Andy or create several at a time. + # User.where(:first_name => 'Andy').first_or_create([{:last_name => 'García'}, {:last_name => 'Mejía'}]) + # # => [#, + # #] + # + # # Find the first user with last name García or create several at a time. + # User.where(:last_name => 'García').first_or_create([{:first_name => 'Jorge'}, {:first_name => 'Andy'}]) + # # => + def first_or_create(*args, &block) + first || create(*args, &block) + end + + # Like first_or_create but calls create! so an exception is raised if the created record is invalid. + # + # Expects arguments in the same format as Base.create. + def first_or_create!(*args, &block) + first || create!(*args, &block) + end + + # Like first_or_create but calls new instead of create. + # + # Expects arguments in the same format as Base.new. + def first_or_new(*args, &block) + first || new(*args, &block) + end + alias :first_or_build :first_or_new + def respond_to?(method, include_private = false) arel.respond_to?(method, include_private) || Array.method_defined?(method) || -- cgit v1.2.3 From 0df27c98d982ec87d2fb48cfda82694eb267993e Mon Sep 17 00:00:00 2001 From: Joshua Wehner Date: Wed, 31 Aug 2011 15:04:41 -0400 Subject: Fixes bug in ActiveRecord::QueryMethods, #1697 Replace split on comma with a regexp that will reverse all ASC/DESC specifically --- activerecord/lib/active_record/relation/query_methods.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7eda9ad8e8..355540782f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -305,10 +305,8 @@ module ActiveRecord when Arel::Nodes::Ordering o.reverse when String, Symbol - o.to_s.split(',').collect do |s| - s.strip! - s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') - end + s = o.to_s.gsub(/\s((desc)|(asc))\s*(,|\Z)/i) { |m| " #{$2 ? 'ASC' : 'DESC'}#{$4}" } + s.match(/\s(de|a)sc\Z/i) ? s : s.concat(" DESC") else o end -- cgit v1.2.3 From de6660b5ec4aa8c0e2415f3483aa9346ef8c9223 Mon Sep 17 00:00:00 2001 From: Gabe Berke-Williams Date: Wed, 31 Aug 2011 20:52:35 -0400 Subject: Improve examples: use each instead of for...in --- activerecord/lib/active_record/fixtures.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 3f36dcde14..2674430116 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -126,7 +126,7 @@ class FixturesFileNotFound < StandardError; end # Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can # mix ERB in with your YAML fixtures to create a bunch of fixtures for load testing, like: # -# <% for i in 1..1000 %> +# <% (1..1000).each do |i| %> # fix_<%= i %>: # id: <%= i %> # name: guy_<%= 1 %> -- cgit v1.2.3 From 8b9ddbd8f912d4aac475bcbcbd3e3d39b9b73bf9 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 1 Sep 2011 14:06:20 -0700 Subject: make sure encoding_aware? is available --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 1996e49296..7d793e3cb8 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -1,4 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' +require 'active_support/core_ext/kernel/requires' +require 'active_support/core_ext/string/encoding' module ActiveRecord module ConnectionAdapters #:nodoc: -- cgit v1.2.3 From 6f3c6992c529a09c8f8bfdb1f714bb8ff1e23300 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 1 Sep 2011 15:00:31 -0700 Subject: * Psych errors with poor yaml formatting are proxied. Fixes #2645, #2731 --- .../lib/active_record/connection_adapters/sqlite_adapter.rb | 1 - activerecord/lib/active_record/fixtures/file.rb | 12 +++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 7d793e3cb8..a90c675bf6 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -1,5 +1,4 @@ require 'active_record/connection_adapters/abstract_adapter' -require 'active_support/core_ext/kernel/requires' require 'active_support/core_ext/string/encoding' module ActiveRecord diff --git a/activerecord/lib/active_record/fixtures/file.rb b/activerecord/lib/active_record/fixtures/file.rb index 04f494db2c..6bad36abb9 100644 --- a/activerecord/lib/active_record/fixtures/file.rb +++ b/activerecord/lib/active_record/fixtures/file.rb @@ -29,11 +29,21 @@ module ActiveRecord rows.each(&block) end + RESCUE_ERRORS = [ ArgumentError ] # :nodoc: + private + if defined?(Psych) && defined?(Psych::SyntaxError) + RESCUE_ERRORS << Psych::SyntaxError + end + def rows return @rows if @rows - data = YAML.load(render(IO.read(@file))) + begin + data = YAML.load(render(IO.read(@file))) + rescue *RESCUE_ERRORS => error + raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. 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}", error.backtrace + end @rows = data ? validate(data).to_a : [] end -- cgit v1.2.3 From 0a38e2a5ce2eaf7393958721edbfcf2a7fe87334 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Sat, 3 Sep 2011 15:20:18 -0700 Subject: restores the API docs of AR::Fixtures, made a quick pass over them, revises link in fixture template [closes #2840] --- activerecord/lib/active_record/fixtures.rb | 729 ++++++++++++++--------------- 1 file changed, 361 insertions(+), 368 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 2674430116..6f1ec7f9b3 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -24,375 +24,368 @@ end class FixturesFileNotFound < StandardError; end -# Fixtures are a way of organizing data that you want to test against; in short, sample data. -# -# = Fixture formats -# -# Fixtures come in 1 flavor: -# -# 1. YAML fixtures -# -# == YAML fixtures -# -# This type of fixture is in YAML format and the preferred default. YAML is a file format which describes data structures -# in a non-verbose, human-readable format. It ships with Ruby 1.8.1+. -# -# Unlike single-file fixtures, YAML fixtures are stored in a single file per model, which are placed -# in the directory appointed by ActiveSupport::TestCase.fixture_path=(path) (this is -# automatically configured for Rails, so you can just put your files in /test/fixtures/). -# The fixture file ends with the .yml file extension (Rails example: -# /test/fixtures/web_sites.yml). The format of a YAML fixture file looks like this: -# -# rubyonrails: -# id: 1 -# name: Ruby on Rails -# url: http://www.rubyonrails.org -# -# google: -# id: 2 -# name: Google -# url: http://www.google.com -# -# This YAML fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and is followed by an -# indented list of key/value pairs in the "key: value" format. Records are separated by a blank line for your viewing -# pleasure. -# -# Note that YAML fixtures are unordered. If you want ordered fixtures, use the omap YAML type. -# See http://yaml.org/type/omap.html -# for the specification. You will need ordered fixtures when you have foreign key constraints on keys in the same table. -# This is commonly needed for tree structures. Example: -# -# --- !omap -# - parent: -# id: 1 -# parent_id: NULL -# title: Parent -# - child: -# id: 2 -# parent_id: 1 -# title: Child -# -# = Using fixtures in testcases -# -# Since fixtures are a testing construct, we use them in our unit and functional tests. There are two ways to use the -# fixtures, but first let's take a look at a sample unit test: -# -# require 'test_helper' -# -# class WebSiteTest < ActiveSupport::TestCase -# test "web_site_count" do -# assert_equal 2, WebSite.count -# end -# end -# -# By default, the test_helper module will load all of your fixtures into your test database, -# so this test will succeed. -# The testing environment will automatically load the all fixtures into the database before each test. -# To ensure consistent data, the environment deletes the fixtures before running the load. -# -# In addition to being available in the database, the fixture's data may also be accessed by -# using a special dynamic method, which has the same name as the model, and accepts the -# name of the fixture to instantiate: -# -# test "find" do -# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name -# end -# -# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the following tests: -# -# test "find_alt_method_1" do -# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name'] -# end -# -# test "find_alt_method_2" do -# assert_equal "Ruby on Rails", @rubyonrails.news -# end -# -# In order to use these methods to access fixtured data within your testcases, you must specify one of the -# following in your ActiveSupport::TestCase-derived class: -# -# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above) -# self.use_instantiated_fixtures = true -# -# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only) -# self.use_instantiated_fixtures = :no_instances -# -# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully -# traversed in the database to create the fixture hash and/or instance variables. This is expensive for -# large sets of fixtured data. -# -# = Dynamic fixtures with ERB -# -# Some times you don't care about the content of the fixtures as much as you care about the volume. In these cases, you can -# mix ERB in with your YAML fixtures to create a bunch of fixtures for load testing, like: -# -# <% (1..1000).each do |i| %> -# fix_<%= i %>: -# id: <%= i %> -# name: guy_<%= 1 %> -# <% end %> -# -# This will create 1000 very simple YAML fixtures. -# -# Using ERB, you can also inject dynamic values into your fixtures with inserts like <%= Date.today.strftime("%Y-%m-%d") %>. -# This is however a feature to be used with some caution. The point of fixtures are that they're -# stable units of predictable sample data. If you feel that you need to inject dynamic values, then -# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values -# in fixtures are to be considered a code smell. -# -# = Transactional fixtures -# -# TestCases can use begin+rollback to isolate their changes to the database instead of having to -# delete+insert for every test case. -# -# class FooTest < ActiveSupport::TestCase -# self.use_transactional_fixtures = true -# -# test "godzilla" do -# assert !Foo.all.empty? -# Foo.destroy_all -# assert Foo.all.empty? -# end -# -# test "godzilla aftermath" do -# assert !Foo.all.empty? -# end -# end -# -# If you preload your test database with all fixture data (probably in the Rakefile task) and use transactional fixtures, -# then you may omit all fixtures declarations in your test cases since all the data's already there -# and every case rolls back its changes. -# -# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to true. This will provide -# access to fixture data for every table that has been loaded through fixtures (depending on the -# value of +use_instantiated_fixtures+) -# -# When *not* to use transactional fixtures: -# -# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until -# all parent transactions commit, particularly, the fixtures transaction which is begun in setup -# and rolled back in teardown. Thus, you won't be able to verify -# the results of your transaction until Active Record supports nested transactions or savepoints (in progress). -# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. -# Use InnoDB, MaxDB, or NDB instead. -# -# = Advanced YAML Fixtures -# -# YAML fixtures that don't specify an ID get some extra features: -# -# * Stable, autogenerated IDs -# * Label references for associations (belongs_to, has_one, has_many) -# * HABTM associations as inline lists -# * Autofilled timestamp columns -# * Fixture label interpolation -# * Support for YAML defaults -# -# == Stable, autogenerated IDs -# -# Here, have a monkey fixture: -# -# george: -# id: 1 -# name: George the Monkey -# -# reginald: -# id: 2 -# name: Reginald the Pirate -# -# Each of these fixtures has two unique identifiers: one for the database -# and one for the humans. Why don't we generate the primary key instead? -# Hashing each fixture's label yields a consistent ID: -# -# george: # generated id: 503576764 -# name: George the Monkey -# -# reginald: # generated id: 324201669 -# name: Reginald the Pirate -# -# Active Record looks at the fixture's model class, discovers the correct -# primary key, and generates it right before inserting the fixture -# into the database. -# -# The generated ID for a given label is constant, so we can discover -# any fixture's ID without loading anything, as long as we know the label. -# -# == Label references for associations (belongs_to, has_one, has_many) -# -# Specifying foreign keys in fixtures can be very fragile, not to -# mention difficult to read. Since Active Record can figure out the ID of -# any fixture from its label, you can specify FK's by label instead of ID. -# -# === belongs_to -# -# Let's break out some more monkeys and pirates. -# -# ### in pirates.yml -# -# reginald: -# id: 1 -# name: Reginald the Pirate -# monkey_id: 1 -# -# ### in monkeys.yml -# -# george: -# id: 1 -# name: George the Monkey -# pirate_id: 1 -# -# Add a few more monkeys and pirates and break this into multiple files, -# and it gets pretty hard to keep track of what's going on. Let's -# use labels instead of IDs: -# -# ### in pirates.yml -# -# reginald: -# name: Reginald the Pirate -# monkey: george -# -# ### in monkeys.yml -# -# george: -# name: George the Monkey -# pirate: reginald -# -# Pow! All is made clear. Active Record reflects on the fixture's model class, -# finds all the +belongs_to+ associations, and allows you to specify -# a target *label* for the *association* (monkey: george) rather than -# a target *id* for the *FK* (monkey_id: 1). -# -# ==== Polymorphic belongs_to -# -# Supporting polymorphic relationships is a little bit more complicated, since -# Active Record needs to know what type your association is pointing at. Something -# like this should look familiar: -# -# ### in fruit.rb -# -# belongs_to :eater, :polymorphic => true -# -# ### in fruits.yml -# -# apple: -# id: 1 -# name: apple -# eater_id: 1 -# eater_type: Monkey -# -# Can we do better? You bet! -# -# apple: -# eater: george (Monkey) -# -# Just provide the polymorphic target type and Active Record will take care of the rest. -# -# === has_and_belongs_to_many -# -# Time to give our monkey some fruit. -# -# ### in monkeys.yml -# -# george: -# id: 1 -# name: George the Monkey -# -# ### in fruits.yml -# -# apple: -# id: 1 -# name: apple -# -# orange: -# id: 2 -# name: orange -# -# grape: -# id: 3 -# name: grape -# -# ### in fruits_monkeys.yml -# -# apple_george: -# fruit_id: 1 -# monkey_id: 1 -# -# orange_george: -# fruit_id: 2 -# monkey_id: 1 -# -# grape_george: -# fruit_id: 3 -# monkey_id: 1 -# -# Let's make the HABTM fixture go away. -# -# ### in monkeys.yml -# -# george: -# id: 1 -# name: George the Monkey -# fruits: apple, orange, grape -# -# ### in fruits.yml -# -# apple: -# name: apple -# -# orange: -# name: orange -# -# grape: -# name: grape -# -# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits -# on George's fixture, but we could've just as easily specified a list -# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on -# the fixture's model class and discovers the +has_and_belongs_to_many+ -# associations. -# -# == Autofilled timestamp columns -# -# If your table/model specifies any of Active Record's -# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+), -# they will automatically be set to Time.now. -# -# If you've set specific values, they'll be left alone. -# -# == Fixture label interpolation -# -# The label of the current fixture is always available as a column value: -# -# geeksomnia: -# name: Geeksomnia's Account -# subdomain: $LABEL -# -# Also, sometimes (like when porting older join table fixtures) you'll need -# to be able to get a hold of the identifier for a given label. ERB -# to the rescue: -# -# george_reginald: -# monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %> -# pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %> -# -# == Support for YAML defaults -# -# You probably already know how to use YAML to set and reuse defaults in -# your database.yml file. You can use the same technique in your fixtures: -# -# DEFAULTS: &DEFAULTS -# created_on: <%= 3.weeks.ago.to_s(:db) %> -# -# first: -# name: Smurf -# *DEFAULTS -# -# second: -# name: Fraggle -# *DEFAULTS -# -# Any fixture labeled "DEFAULTS" is safely ignored. - module ActiveRecord + # \Fixtures are a way of organizing data that you want to test against; in short, sample data. + # + # They are stored in YAML files, one file per model, which are placed in the directory + # appointed by ActiveSupport::TestCase.fixture_path=(path) (this is automatically + # configured for Rails, so you can just put your files in /test/fixtures/). + # The fixture file ends with the .yml file extension (Rails example: + # /test/fixtures/web_sites.yml). The format of a fixture file looks + # like this: + # + # rubyonrails: + # id: 1 + # name: Ruby on Rails + # url: http://www.rubyonrails.org + # + # google: + # id: 2 + # name: Google + # url: http://www.google.com + # + # This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and + # is followed by an indented list of key/value pairs in the "key: value" format. Records are + # separated by a blank line for your viewing pleasure. + # + # Note that fixtures are unordered. If you want ordered fixtures, use the omap YAML type. + # See http://yaml.org/type/omap.html + # for the specification. You will need ordered fixtures when you have foreign key constraints + # on keys in the same table. This is commonly needed for tree structures. Example: + # + # --- !omap + # - parent: + # id: 1 + # parent_id: NULL + # title: Parent + # - child: + # id: 2 + # parent_id: 1 + # title: Child + # + # = Using Fixtures in Test Cases + # + # Since fixtures are a testing construct, we use them in our unit and functional tests. There + # are two ways to use the fixtures, but first let's take a look at a sample unit test: + # + # require 'test_helper' + # + # class WebSiteTest < ActiveSupport::TestCase + # test "web_site_count" do + # assert_equal 2, WebSite.count + # end + # end + # + # By default, test_helper.rb will load all of your fixtures into your test database, + # so this test will succeed. + # + # The testing environment will automatically load the all fixtures into the database before each + # test. To ensure consistent data, the environment deletes the fixtures before running the load. + # + # In addition to being available in the database, the fixture's data may also be accessed by + # using a special dynamic method, which has the same name as the model, and accepts the + # name of the fixture to instantiate: + # + # test "find" do + # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name + # end + # + # Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the + # following tests: + # + # test "find_alt_method_1" do + # assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name'] + # end + # + # test "find_alt_method_2" do + # assert_equal "Ruby on Rails", @rubyonrails.news + # end + # + # In order to use these methods to access fixtured data within your testcases, you must specify one of the + # following in your ActiveSupport::TestCase-derived class: + # + # - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above) + # self.use_instantiated_fixtures = true + # + # - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only) + # self.use_instantiated_fixtures = :no_instances + # + # Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully + # traversed in the database to create the fixture hash and/or instance variables. This is expensive for + # large sets of fixtured data. + # + # = Dynamic fixtures with ERB + # + # Some times you don't care about the content of the fixtures as much as you care about the volume. + # In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load + # testing, like: + # + # <% 1.upto(1000) do |i| %> + # fix_<%= i %>: + # id: <%= i %> + # name: guy_<%= 1 %> + # <% end %> + # + # This will create 1000 very simple fixtures. + # + # Using ERB, you can also inject dynamic values into your fixtures with inserts like + # <%= Date.today.strftime("%Y-%m-%d") %>. + # This is however a feature to be used with some caution. The point of fixtures are that they're + # stable units of predictable sample data. If you feel that you need to inject dynamic values, then + # perhaps you should reexamine whether your application is properly testable. Hence, dynamic values + # in fixtures are to be considered a code smell. + # + # = Transactional Fixtures + # + # Test cases can use begin+rollback to isolate their changes to the database instead of having to + # delete+insert for every test case. + # + # class FooTest < ActiveSupport::TestCase + # self.use_transactional_fixtures = true + # + # test "godzilla" do + # assert !Foo.all.empty? + # Foo.destroy_all + # assert Foo.all.empty? + # end + # + # test "godzilla aftermath" do + # assert !Foo.all.empty? + # end + # end + # + # If you preload your test database with all fixture data (probably in the rake task) and use + # transactional fixtures, then you may omit all fixtures declarations in your test cases since + # all the data's already there and every case rolls back its changes. + # + # In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to + # true. This will provide access to fixture data for every table that has been loaded through + # fixtures (depending on the value of +use_instantiated_fixtures+). + # + # When *not* to use transactional fixtures: + # + # 1. You're testing whether a transaction works correctly. Nested transactions don't commit until + # all parent transactions commit, particularly, the fixtures transaction which is begun in setup + # and rolled back in teardown. Thus, you won't be able to verify + # the results of your transaction until Active Record supports nested transactions or savepoints (in progress). + # 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM. + # Use InnoDB, MaxDB, or NDB instead. + # + # = Advanced Fixtures + # + # Fixtures that don't specify an ID get some extra features: + # + # * Stable, autogenerated IDs + # * Label references for associations (belongs_to, has_one, has_many) + # * HABTM associations as inline lists + # * Autofilled timestamp columns + # * Fixture label interpolation + # * Support for YAML defaults + # + # == Stable, Autogenerated IDs + # + # Here, have a monkey fixture: + # + # george: + # id: 1 + # name: George the Monkey + # + # reginald: + # id: 2 + # name: Reginald the Pirate + # + # Each of these fixtures has two unique identifiers: one for the database + # and one for the humans. Why don't we generate the primary key instead? + # Hashing each fixture's label yields a consistent ID: + # + # george: # generated id: 503576764 + # name: George the Monkey + # + # reginald: # generated id: 324201669 + # name: Reginald the Pirate + # + # Active Record looks at the fixture's model class, discovers the correct + # primary key, and generates it right before inserting the fixture + # into the database. + # + # The generated ID for a given label is constant, so we can discover + # any fixture's ID without loading anything, as long as we know the label. + # + # == Label references for associations (belongs_to, has_one, has_many) + # + # Specifying foreign keys in fixtures can be very fragile, not to + # mention difficult to read. Since Active Record can figure out the ID of + # any fixture from its label, you can specify FK's by label instead of ID. + # + # === belongs_to + # + # Let's break out some more monkeys and pirates. + # + # ### in pirates.yml + # + # reginald: + # id: 1 + # name: Reginald the Pirate + # monkey_id: 1 + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # pirate_id: 1 + # + # Add a few more monkeys and pirates and break this into multiple files, + # and it gets pretty hard to keep track of what's going on. Let's + # use labels instead of IDs: + # + # ### in pirates.yml + # + # reginald: + # name: Reginald the Pirate + # monkey: george + # + # ### in monkeys.yml + # + # george: + # name: George the Monkey + # pirate: reginald + # + # Pow! All is made clear. Active Record reflects on the fixture's model class, + # finds all the +belongs_to+ associations, and allows you to specify + # a target *label* for the *association* (monkey: george) rather than + # a target *id* for the *FK* (monkey_id: 1). + # + # ==== Polymorphic belongs_to + # + # Supporting polymorphic relationships is a little bit more complicated, since + # Active Record needs to know what type your association is pointing at. Something + # like this should look familiar: + # + # ### in fruit.rb + # + # belongs_to :eater, :polymorphic => true + # + # ### in fruits.yml + # + # apple: + # id: 1 + # name: apple + # eater_id: 1 + # eater_type: Monkey + # + # Can we do better? You bet! + # + # apple: + # eater: george (Monkey) + # + # Just provide the polymorphic target type and Active Record will take care of the rest. + # + # === has_and_belongs_to_many + # + # Time to give our monkey some fruit. + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # + # ### in fruits.yml + # + # apple: + # id: 1 + # name: apple + # + # orange: + # id: 2 + # name: orange + # + # grape: + # id: 3 + # name: grape + # + # ### in fruits_monkeys.yml + # + # apple_george: + # fruit_id: 1 + # monkey_id: 1 + # + # orange_george: + # fruit_id: 2 + # monkey_id: 1 + # + # grape_george: + # fruit_id: 3 + # monkey_id: 1 + # + # Let's make the HABTM fixture go away. + # + # ### in monkeys.yml + # + # george: + # id: 1 + # name: George the Monkey + # fruits: apple, orange, grape + # + # ### in fruits.yml + # + # apple: + # name: apple + # + # orange: + # name: orange + # + # grape: + # name: grape + # + # Zap! No more fruits_monkeys.yml file. We've specified the list of fruits + # on George's fixture, but we could've just as easily specified a list + # of monkeys on each fruit. As with +belongs_to+, Active Record reflects on + # the fixture's model class and discovers the +has_and_belongs_to_many+ + # associations. + # + # == Autofilled Timestamp Columns + # + # If your table/model specifies any of Active Record's + # standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+), + # they will automatically be set to Time.now. + # + # If you've set specific values, they'll be left alone. + # + # == Fixture label interpolation + # + # The label of the current fixture is always available as a column value: + # + # geeksomnia: + # name: Geeksomnia's Account + # subdomain: $LABEL + # + # Also, sometimes (like when porting older join table fixtures) you'll need + # to be able to get a hold of the identifier for a given label. ERB + # to the rescue: + # + # george_reginald: + # monkey_id: <%= ActiveRecord::Fixtures.identify(:reginald) %> + # pirate_id: <%= ActiveRecord::Fixtures.identify(:george) %> + # + # == Support for YAML defaults + # + # You probably already know how to use YAML to set and reuse defaults in + # your database.yml file. You can use the same technique in your fixtures: + # + # DEFAULTS: &DEFAULTS + # created_on: <%= 3.weeks.ago.to_s(:db) %> + # + # first: + # name: Smurf + # *DEFAULTS + # + # second: + # name: Fraggle + # *DEFAULTS + # + # Any fixture labeled "DEFAULTS" is safely ignored. class Fixtures MAX_ID = 2 ** 30 - 1 -- cgit v1.2.3 From ef7e60f008aab5b6390f44ae8f5923edcdf470b8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 4 Sep 2011 09:56:55 +0100 Subject: Revert "Fixes bug in ActiveRecord::QueryMethods, #1697" This reverts commit 0df27c98d982ec87d2fb48cfda82694eb267993e. Reverted due to failing test, see #2845. --- activerecord/lib/active_record/relation/query_methods.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 355540782f..7eda9ad8e8 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -305,8 +305,10 @@ module ActiveRecord when Arel::Nodes::Ordering o.reverse when String, Symbol - s = o.to_s.gsub(/\s((desc)|(asc))\s*(,|\Z)/i) { |m| " #{$2 ? 'ASC' : 'DESC'}#{$4}" } - s.match(/\s(de|a)sc\Z/i) ? s : s.concat(" DESC") + o.to_s.split(',').collect do |s| + s.strip! + s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC') + end else o end -- cgit v1.2.3 From 9066e3402548d365dc7cf2abda8e7cd64370675c Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Mon, 5 Sep 2011 00:12:57 +0530 Subject: We need to recorder here. Need to drop the order from default scope. Fixes #2832 --- activerecord/lib/active_record/relation/batches.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index ec1176e3dd..2fd89882ff 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -62,7 +62,7 @@ module ActiveRecord start = options.delete(:start).to_i batch_size = options.delete(:batch_size) || 1000 - relation = relation.except(:order).order(batch_order).limit(batch_size) + relation = relation.reorder(batch_order).limit(batch_size) records = relation.where(table[primary_key].gteq(start)).all while records.any? -- cgit v1.2.3 From 451cdd62ce54080d0a4944551fbc3f10cc34a42d Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Thu, 1 Sep 2011 11:24:19 +0200 Subject: use LIMIT SQL word in first - Closes #2783 --- activerecord/lib/active_record/relation/finder_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 73368aed18..2639c46709 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -114,7 +114,7 @@ module ActiveRecord def first(*args) if args.any? if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) - to_a.first(*args) + limit(*args).to_a else apply_finder_options(args.first).first end -- cgit v1.2.3 From 5f5527c726841cdefb82965a645d554767c5a6a9 Mon Sep 17 00:00:00 2001 From: Damien Mathieu <42@dmathieu.com> Date: Mon, 5 Sep 2011 13:40:49 +0200 Subject: Use LIMIT sql word in last when it's possible --- activerecord/lib/active_record/relation/finder_methods.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 2639c46709..83d650d3f4 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -134,7 +134,11 @@ module ActiveRecord def last(*args) if args.any? if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash)) - to_a.last(*args) + if order_values.empty? && reorder_value.nil? + order("#{primary_key} DESC").limit(*args).reverse + else + to_a.last(*args) + end else apply_finder_options(args.first).last end -- cgit v1.2.3 From 21750122308a38cd2eaf9b46aa1789966eb8abd9 Mon Sep 17 00:00:00 2001 From: Georg Friedrich Date: Mon, 5 Sep 2011 21:25:57 +1000 Subject: Don't find belongs_to target when the foreign_key is NULL. Fixes #2828 --- activerecord/lib/active_record/associations/belongs_to_association.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index adb6af7165..58c9648ce8 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -20,6 +20,10 @@ module ActiveRecord private + def find_target? + !loaded? && foreign_key_present? && klass + end + def update_counters(record) counter_cache_name = reflection.counter_cache_column -- cgit v1.2.3 From d03aff7f645bdc16eae35264bb0e95a0ab814d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Mej=C3=ADa?= Date: Tue, 6 Sep 2011 02:02:28 -0500 Subject: Adding first example with no arguments to AR::Relation#first_or_create and removing examples that create several users at the same time (this is confusing and not really helpful). --- activerecord/lib/active_record/relation.rb | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 461237c7ad..849f01a047 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -99,32 +99,32 @@ module ActiveRecord # Expects arguments in the same format as Base.create. # # ==== Examples - # # Find the first user named Scarlett or create a new one. + # # Find the first user named Penélope or create a new one. + # User.where(:first_name => 'Penélope').first_or_create + # # => + # + # # Find the first user named Penélope or create a new one. + # # We already have one so the existing record will be returned. + # User.where(:first_name => 'Penélope').first_or_create + # # => + # + # # Find the first user named Scarlett or create a new one with a particular last name. # User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson') - # # => + # # => # - # # Find the first user named Scarlett or create one with a different last name. - # # We already have one Scarlett, so she'll be returned. + # # Find the first user named Scarlett or create a new one with a different last name. + # # We already have one so the existing record will be returned. # User.where(:first_name => 'Scarlett').first_or_create do |user| # user.last_name = "O'Hara" # end - # # => - # - # # Find the first user named Andy or create several at a time. - # User.where(:first_name => 'Andy').first_or_create([{:last_name => 'García'}, {:last_name => 'Mejía'}]) - # # => [#, - # #] - # - # # Find the first user with last name García or create several at a time. - # User.where(:last_name => 'García').first_or_create([{:first_name => 'Jorge'}, {:first_name => 'Andy'}]) - # # => + # # => def first_or_create(*args, &block) first || create(*args, &block) end # Like first_or_create but calls create! so an exception is raised if the created record is invalid. # - # Expects arguments in the same format as Base.create. + # Expects arguments in the same format as Base.create!. def first_or_create!(*args, &block) first || create!(*args, &block) end -- cgit v1.2.3 From 72317883ed3fd035a94ca1536def364d2d00a6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Mej=C3=ADa?= Date: Tue, 6 Sep 2011 02:45:54 -0500 Subject: Using more precise method signatures for AR::Relation#first_or_create family of methods. --- activerecord/lib/active_record/relation.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 849f01a047..d3f1347e03 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -118,22 +118,22 @@ module ActiveRecord # user.last_name = "O'Hara" # end # # => - def first_or_create(*args, &block) - first || create(*args, &block) + def first_or_create(attributes = nil, options = {}, &block) + first || create(attributes, options, &block) end # Like first_or_create but calls create! so an exception is raised if the created record is invalid. # # Expects arguments in the same format as Base.create!. - def first_or_create!(*args, &block) - first || create!(*args, &block) + def first_or_create!(attributes = nil, options = {}, &block) + first || create!(attributes, options, &block) end # Like first_or_create but calls new instead of create. # # Expects arguments in the same format as Base.new. - def first_or_new(*args, &block) - first || new(*args, &block) + def first_or_new(attributes = nil, options = {}, &block) + first || new(attributes, options, &block) end alias :first_or_build :first_or_new -- cgit v1.2.3 From 9f3e732e65dfa978e036a3b0df3578b2d6a6510a Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 6 Sep 2011 12:59:11 +0100 Subject: Make the logic easier to read --- .../lib/active_record/connection_adapters/abstract/quoting.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 3de850ec9e..f93c7cd74a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -102,10 +102,13 @@ module ActiveRecord def quoted_date(value) if value.acts_like?(:time) zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal - value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value - else - value - end.to_s(:db) + + if value.respond_to?(zone_conversion_method) + value = value.send(zone_conversion_method) + end + end + + value.to_s(:db) end end end -- cgit v1.2.3 From b24d668859c5836c0e3ed277b2022a1a39eb3f8e Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 6 Sep 2011 15:58:07 +0100 Subject: Ensure we are not comparing a string with a symbol in HasManyAssociation#inverse_updates_counter_cache?. Fixes #2755, where a counter cache could be decremented twice as far as it was supposed to be. --- activerecord/lib/active_record/reflection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index a2324039cf..89179779e3 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -226,7 +226,7 @@ module ActiveRecord if options[:counter_cache] == true "#{active_record.name.demodulize.underscore.pluralize}_count" elsif options[:counter_cache] - options[:counter_cache] + options[:counter_cache].to_s end end -- cgit v1.2.3 From 9bde73ff72812f6f3c59ad97be6ca6c628e109ea Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 6 Sep 2011 16:34:32 +0100 Subject: Nested through associations: preloads from the default scope of a through model should not be included in the association scope. (We're already excluding includes.) Fixes #2834. --- activerecord/lib/active_record/associations/through_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 81172179e0..b010b5e9ef 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -16,7 +16,7 @@ module ActiveRecord chain[1..-1].each do |reflection| scope = scope.merge( reflection.klass.scoped.with_default_scope. - except(:select, :create_with, :includes) + except(:select, :create_with, :includes, :preload) ) end scope -- cgit v1.2.3 From a4fa6eab396e703eb70b70ed708220a6405f2899 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 6 Sep 2011 16:06:27 -0700 Subject: adding a statement pool for mysql and sqlite3 --- .../connection_adapters/mysql_adapter.rb | 33 +++++++++++++++--- .../connection_adapters/sqlite_adapter.rb | 38 +++++++++++++++++--- .../connection_adapters/statement_pool.rb | 40 ++++++++++++++++++++++ 3 files changed, 102 insertions(+), 9 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/statement_pool.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index e9bdcc2104..8ecb3cefde 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -1,4 +1,5 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' +require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/hash/keys' gem 'mysql', '~> 2.8.1' @@ -90,9 +91,36 @@ module ActiveRecord ADAPTER_NAME = 'MySQL' + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max = 1000) + super + @cache = {} + end + + def each(&block); @cache.each(&block); end + def key?(key); @cache.key?(key); end + def [](key); @cache[key]; end + def length; @cache.length; end + def delete(key); @cache.delete(key); end + + def []=(sql, key) + while @max <= @cache.size + @cache.shift.last[:stmt].close + end + @cache[sql] = key + end + + def clear + @cache.values.each do |hash| + hash[:stmt].close + end + @cache.clear + end + end + def initialize(connection, logger, connection_options, config) super - @statements = {} + @statements = StatementPool.new(@connection) @client_encoding = nil connect end @@ -187,9 +215,6 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.values.each do |cache| - cache[:stmt].close - end @statements.clear end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index a90c675bf6..a4e21b714b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -1,4 +1,5 @@ require 'active_record/connection_adapters/abstract_adapter' +require 'active_record/connection_adapters/statement_pool' require 'active_support/core_ext/string/encoding' module ActiveRecord @@ -48,9 +49,40 @@ module ActiveRecord end end + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max = 1000) + super + @cache = {} + end + + def each(&block); @cache.each(&block); end + def key?(key); @cache.key?(key); end + def [](key); @cache[key]; end + def length; @cache.length; end + + def []=(sql, key) + while @max <= @cache.size + dealloc(@cache.shift.last[:stmt]) + end + @cache[sql] = key + end + + def clear + @cache.values.each do |hash| + dealloc hash[:stmt] + end + @cache.clear + end + + private + def dealloc(stmt) + stmt.close unless stmt.closed? + end + end + def initialize(connection, logger, config) super(connection, logger) - @statements = {} + @statements = StatementPool.new(@connection) @config = config end @@ -107,10 +139,6 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.values.map { |hash| hash[:stmt] }.each { |stmt| - stmt.close unless stmt.closed? - } - @statements.clear end diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb new file mode 100644 index 0000000000..c6b1bc8b5b --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb @@ -0,0 +1,40 @@ +module ActiveRecord + module ConnectionAdapters + class StatementPool + include Enumerable + + def initialize(connection, max = 1000) + @connection = connection + @max = max + end + + def each + raise NotImplementedError + end + + def key?(key) + raise NotImplementedError + end + + def [](key) + raise NotImplementedError + end + + def length + raise NotImplementedError + end + + def []=(sql, key) + raise NotImplementedError + end + + def clear + raise NotImplementedError + end + + def delete(key) + raise NotImplementedError + end + end + end +end -- cgit v1.2.3 From 54b7e783ef202d022a57dcdd54f7edf021c1df78 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 6 Sep 2011 16:47:34 -0700 Subject: Database adapters use a statement pool. Database adapters use a statement pool for limiting the number of open prepared statments on the database. The limit defaults to 1000, but can be adjusted in your database config by changing 'statement_limit'. --- .../connection_adapters/mysql_adapter.rb | 3 +- .../connection_adapters/postgresql_adapter.rb | 46 +++++++++++++++++++--- .../connection_adapters/sqlite_adapter.rb | 5 ++- 3 files changed, 46 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 8ecb3cefde..73e3afe1d5 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -120,7 +120,8 @@ module ActiveRecord def initialize(connection, logger, connection_options, config) super - @statements = StatementPool.new(@connection) + @statements = StatementPool.new(@connection, + config.fetch(:statement_limit) { 1000 }) @client_encoding = nil connect end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index ba4a6c7a78..a09bf9c73f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_support/core_ext/object/blank' +require 'active_record/connection_adapters/statement_pool' # Make sure we're using pg high enough for PGResult#values gem 'pg', '~> 0.11' @@ -246,6 +247,43 @@ module ActiveRecord true end + class StatementPool < ConnectionAdapters::StatementPool + def initialize(connection, max) + super + @counter = 0 + @cache = {} + end + + def each(&block); @cache.each(&block); end + def key?(key); @cache.key?(key); end + def [](key); @cache[key]; end + def length; @cache.length; end + + def next_key + "a#{@counter + 1}" + end + + def []=(sql, key) + while @max <= @cache.size + dealloc(@cache.shift.last) + end + @counter += 1 + @cache[sql] = key + end + + def clear + @cache.each_value do |stmt_key| + dealloc stmt_key + end + @cache.clear + end + + private + def dealloc(key) + @connection.query "DEALLOCATE #{key}" + end + end + # Initializes and connects a PostgreSQL adapter. def initialize(connection, logger, connection_parameters, config) super(connection, logger) @@ -254,9 +292,10 @@ module ActiveRecord # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil @table_alias_length = nil - @statements = {} connect + @statements = StatementPool.new @connection, + config.fetch(:statement_limit) { 1000 } if postgresql_version < 80200 raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" @@ -271,9 +310,6 @@ module ActiveRecord # Clears the prepared statements cache. def clear_cache! - @statements.each_value do |value| - @connection.query "DEALLOCATE #{value}" - end @statements.clear end @@ -996,7 +1032,7 @@ module ActiveRecord def exec_cache(sql, binds) unless @statements.key? sql - nextkey = "a#{@statements.length + 1}" + nextkey = @statements.next_key @connection.prepare nextkey, sql @statements[sql] = nextkey end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index a4e21b714b..7c7e762c19 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -50,7 +50,7 @@ module ActiveRecord end class StatementPool < ConnectionAdapters::StatementPool - def initialize(connection, max = 1000) + def initialize(connection, max) super @cache = {} end @@ -82,7 +82,8 @@ module ActiveRecord def initialize(connection, logger, config) super(connection, logger) - @statements = StatementPool.new(@connection) + @statements = StatementPool.new(@connection, + config.fetch(:statement_limit) { 1000 }) @config = config end -- cgit v1.2.3 From 4edf6ea0eae9cb8be46a2e963e047eb89a0f5531 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 6 Sep 2011 17:43:14 -0700 Subject: Merge pull request #2897 from rsutphin/ar31-remove_connection Patch for issue #2820 --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 2 +- .../connection_adapters/abstract/connection_specification.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') 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 61994d4a47..20863e73aa 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -421,7 +421,7 @@ module ActiveRecord # can be used as an argument for establish_connection, for easily # re-establishing the connection. def remove_connection(klass) - pool = @connection_pools[klass.name] + pool = @connection_pools.delete(klass.name) return nil unless pool pool.automatic_reconnect = false diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 7312e34f01..c08c0263b9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -126,7 +126,7 @@ module ActiveRecord end def connection_pool - connection_handler.retrieve_connection_pool(self) + connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished end def retrieve_connection -- cgit v1.2.3 From 143769051bcf5b3a7600744be2d8db6e9c859288 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 7 Sep 2011 09:26:02 +0100 Subject: Don't include any of includes, preload, joins, eager_load in the through association scope. --- activerecord/lib/active_record/associations/through_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index b010b5e9ef..b347a94978 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -16,7 +16,7 @@ module ActiveRecord chain[1..-1].each do |reflection| scope = scope.merge( reflection.klass.scoped.with_default_scope. - except(:select, :create_with, :includes, :preload) + except(:select, :create_with, :includes, :preload, :joins, :eager_load) ) end scope -- cgit v1.2.3 From f5ea24b3db952071eea73cdef7fd52e38e7e540f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 7 Sep 2011 10:24:25 -0700 Subject: Merge commit 'refs/pull/2909/head' of https://github.com/rails/rails into rawr * https://github.com/rails/rails: Postgresql adapter: added current_schema check for table_exists? Postgresql adapter: added current_schema check for table_exists? --- .../active_record/connection_adapters/postgresql_adapter.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index a09bf9c73f..9181cc78fc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -719,12 +719,10 @@ module ActiveRecord binds << [nil, schema] if schema exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 - SELECT COUNT(*) - FROM pg_class c - LEFT JOIN pg_namespace n ON n.oid = c.relnamespace - WHERE c.relkind in ('v','r') - AND c.relname = $1 - AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} + SELECT COUNT(*) + FROM pg_tables + WHERE tablename = $1 + AND schemaname = #{schema ? "'#{schema}'" : "ANY (current_schemas(false))"} SQL end -- cgit v1.2.3 From d9a20711da15efb781a8b8402871be4890d05c6d Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 7 Sep 2011 10:30:30 -0700 Subject: use the supplied bind values --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 9181cc78fc..45ca104ad3 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -722,7 +722,7 @@ module ActiveRecord SELECT COUNT(*) FROM pg_tables WHERE tablename = $1 - AND schemaname = #{schema ? "'#{schema}'" : "ANY (current_schemas(false))"} + AND schemaname = #{schema ? "$2" : "ANY (current_schemas(false))"} SQL end -- cgit v1.2.3 From b9f66f4db157eb6c85e1c72d9b1d577cd93cf9de Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 7 Sep 2011 10:44:18 -0700 Subject: fixing view queries --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 45ca104ad3..272663a91d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -720,9 +720,11 @@ module ActiveRecord exec_query(<<-SQL, 'SCHEMA', binds).rows.first[0].to_i > 0 SELECT COUNT(*) - FROM pg_tables - WHERE tablename = $1 - AND schemaname = #{schema ? "$2" : "ANY (current_schemas(false))"} + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind in ('v','r') + AND c.relname = $1 + AND n.nspname = #{schema ? '$2' : 'ANY (current_schemas(false))'} SQL end -- cgit v1.2.3 From 834d429e849b590ee7a283909eda9affed065387 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 7 Sep 2011 15:06:42 -0700 Subject: LRU should cache per process in postgresql. fixes #1339 --- .../connection_adapters/postgresql_adapter.rb | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 272663a91d..5402918b1d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -251,34 +251,38 @@ module ActiveRecord def initialize(connection, max) super @counter = 0 - @cache = {} + @cache = Hash.new { |h,pid| h[pid] = {} } end - def each(&block); @cache.each(&block); end - def key?(key); @cache.key?(key); end - def [](key); @cache[key]; end - def length; @cache.length; end + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end def next_key "a#{@counter + 1}" end def []=(sql, key) - while @max <= @cache.size - dealloc(@cache.shift.last) + while @max <= cache.size + dealloc(cache.shift.last) end @counter += 1 - @cache[sql] = key + cache[sql] = key end def clear - @cache.each_value do |stmt_key| + cache.each_value do |stmt_key| dealloc stmt_key end - @cache.clear + cache.clear end private + def cache + @cache[$$] + end + def dealloc(key) @connection.query "DEALLOCATE #{key}" end -- cgit v1.2.3 From e1b500ec96987de595da1541a73a7d5fb9eece9c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 7 Sep 2011 15:26:26 -0700 Subject: LRU cache in mysql and sqlite are now per-process caches. --- .../connection_adapters/mysql_adapter.rb | 27 +++++++++++++--------- .../connection_adapters/sqlite_adapter.rb | 24 +++++++++++-------- 2 files changed, 30 insertions(+), 21 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 73e3afe1d5..a1824fe396 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -94,27 +94,32 @@ module ActiveRecord class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max = 1000) super - @cache = {} + @cache = Hash.new { |h,pid| h[pid] = {} } end - def each(&block); @cache.each(&block); end - def key?(key); @cache.key?(key); end - def [](key); @cache[key]; end - def length; @cache.length; end - def delete(key); @cache.delete(key); end + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end + def delete(key); cache.delete(key); end def []=(sql, key) - while @max <= @cache.size - @cache.shift.last[:stmt].close + while @max <= cache.size + cache.shift.last[:stmt].close end - @cache[sql] = key + cache[sql] = key end def clear - @cache.values.each do |hash| + cache.values.each do |hash| hash[:stmt].close end - @cache.clear + cache.clear + end + + private + def cache + @cache[$$] 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 7c7e762c19..1932a849ee 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -52,29 +52,33 @@ module ActiveRecord class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) super - @cache = {} + @cache = Hash.new { |h,pid| h[pid] = {} } end - def each(&block); @cache.each(&block); end - def key?(key); @cache.key?(key); end - def [](key); @cache[key]; end - def length; @cache.length; end + def each(&block); cache.each(&block); end + def key?(key); cache.key?(key); end + def [](key); cache[key]; end + def length; cache.length; end def []=(sql, key) - while @max <= @cache.size - dealloc(@cache.shift.last[:stmt]) + while @max <= cache.size + dealloc(cache.shift.last[:stmt]) end - @cache[sql] = key + cache[sql] = key end def clear - @cache.values.each do |hash| + cache.values.each do |hash| dealloc hash[:stmt] end - @cache.clear + cache.clear end private + def cache + @cache[$$] + end + def dealloc(stmt) stmt.close unless stmt.closed? end -- cgit v1.2.3 From 4bcacc80667a548664b5ca09d85074c6c383748e Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 11 Sep 2011 17:05:16 -0700 Subject: Merge pull request #2936 from joelmoss/migration_status db:migrate:status not looking at all migration paths --- activerecord/lib/active_record/railties/databases.rake | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 13c41350fb..b3316fd1a2 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -203,11 +203,13 @@ db_namespace = namespace :db do end db_list = ActiveRecord::Base.connection.select_values("SELECT version FROM #{ActiveRecord::Migrator.schema_migrations_table_name}") file_list = [] - Dir.foreach(File.join(Rails.root, 'db', 'migrate')) do |file| - # only files matching "20091231235959_some_name.rb" pattern - if match_data = /^(\d{14})_(.+)\.rb$/.match(file) - status = db_list.delete(match_data[1]) ? 'up' : 'down' - file_list << [status, match_data[1], match_data[2].humanize] + ActiveRecord::Migrator.migrations_paths.each do |path| + Dir.foreach(path) do |file| + # only files matching "20091231235959_some_name.rb" pattern + if match_data = /^(\d{14})_(.+)\.rb$/.match(file) + status = db_list.delete(match_data[1]) ? 'up' : 'down' + file_list << [status, match_data[1], match_data[2].humanize] + end end end db_list.map! do |version| -- cgit v1.2.3 From 8d59e0b2633c26f9de8942a2d676afe39b0ee3f8 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 4 Sep 2011 22:05:53 +0100 Subject: Alias id= if necessary, rather than relying on method_missing --- activerecord/lib/active_record/attribute_methods/write.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index c77a3ac145..5621b44c8c 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,6 +17,10 @@ module ActiveRecord write_attribute(attr_name, new_value) end end + + if attr_name == primary_key && attr_name != "id" + generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='") + end end end -- cgit v1.2.3 From 99bd6b53da9555450afb1e050324007868e0768c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 9 Sep 2011 09:08:27 +0100 Subject: Add deprecation for doing `attribute_method_suffix ''` --- activerecord/lib/active_record/attribute_methods/read.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 9a50a20fbc..4174e4da09 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -6,8 +6,6 @@ module ActiveRecord ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] included do - attribute_method_suffix "" - cattr_accessor :attribute_types_cached_by_default, :instance_writer => false self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT -- cgit v1.2.3 From 3386a089eff05a8a990ca56909c9a427f4f2fe25 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 10 Sep 2011 09:36:27 +0100 Subject: Fix warnings. Make sure we don't redefine an already-defined attribute method. --- activerecord/lib/active_record/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index d0687ed0b6..1937ac4272 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -36,7 +36,7 @@ module ActiveRecord @@_defined_activerecord_methods ||= defined_activerecord_methods raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) - @_defined_class_methods.include?(method_name) + @_defined_class_methods.include?(method_name) || generated_attribute_methods.method_defined?(method_name) end def defined_activerecord_methods -- cgit v1.2.3 From 50d395f96ea05da1e02459688e94bff5872c307b Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 10 Sep 2011 21:10:01 +0100 Subject: Raise error when using write_attribute with a non-existent attribute. Previously we would just silently write the attribute. This can lead to subtle bugs (for example, see the change in AutosaveAssociation where a through association would wrongly gain an attribute. Also, ensuring that we never gain any new attributes after initialization will allow me to reduce our dependence on method_missing. --- activerecord/lib/active_record/attribute_methods/write.rb | 10 +++++++--- activerecord/lib/active_record/autosave_association.rb | 5 ++++- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/timestamp.rb | 4 +++- 4 files changed, 15 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 5621b44c8c..e9cdb130db 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -28,12 +28,16 @@ module ActiveRecord # for fixnum and float columns are turned into +nil+. def write_attribute(attr_name, value) attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' + attr_name = self.class.primary_key if attr_name == 'id' && self.class.primary_key @attributes_cache.delete(attr_name) - if (column = column_for_attribute(attr_name)) && column.number? + column = column_for_attribute(attr_name) + + if column && column.number? @attributes[attr_name] = convert_number_column_value(value) - else + elsif column || @attributes.has_key?(attr_name) @attributes[attr_name] = value + else + raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" end end alias_method :raw_write_attribute, :write_attribute diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 085fdba639..056170d82a 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -370,7 +370,10 @@ module ActiveRecord else key = reflection.options[:primary_key] ? send(reflection.options[:primary_key]) : id if autosave != false && (new_record? || record.new_record? || record[reflection.foreign_key] != key || autosave) - record[reflection.foreign_key] = key + unless reflection.through_reflection + record[reflection.foreign_key] = key + end + saved = record.save(:validate => !autosave) raise ActiveRecord::Rollback if !saved && autosave saved diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 2dac9ea0fb..5e65e46a7d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -314,7 +314,7 @@ module ActiveRecord new_id = self.class.unscoped.insert attributes_values - self.id ||= new_id + self.id ||= new_id if self.class.primary_key IdentityMap.add(self) if IdentityMap.enabled? @new_record = false diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index cccac6ffd7..4d5e469a7f 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -48,7 +48,9 @@ module ActiveRecord current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| - write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil? + if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil? + write_attribute(column.to_s, current_time) + end end end -- cgit v1.2.3 From eecfa84a9065da2b1bd1e02f37e8653a2825c624 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sun, 11 Sep 2011 14:48:40 +0100 Subject: Always generate attribute methods on the base class. This fixes a situation I encountered where a subclass would cache the name of a generated attribute method in @_defined_class_methods. Then, when the superclass has it's attribute methods undefined, the subclass would always have to dispatch through method_missing, because the presence of the attribute in @_defined_class_methods would mean that it is never generated again, even if undefine_attribute_methods is called on the subclass. There various other confusing edge cases like this. STI classes share columns, so let's just keep all the attribute method generation state isolated to the base class. --- .../lib/active_record/attribute_methods.rb | 23 +++++++++++++++++----- activerecord/lib/active_record/base.rb | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1937ac4272..a8cb89fb65 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -11,17 +11,30 @@ module ActiveRecord # accessors, mutators and query methods. def define_attribute_methods return if attribute_methods_generated? - super(column_names) - @attribute_methods_generated = true + + if base_class == self + super(column_names) + @attribute_methods_generated = true + else + base_class.define_attribute_methods + end end def attribute_methods_generated? - @attribute_methods_generated ||= false + if base_class == self + @attribute_methods_generated ||= false + else + base_class.attribute_methods_generated? + end end def undefine_attribute_methods(*args) - super - @attribute_methods_generated = false + if base_class == self + super + @attribute_methods_generated = false + else + base_class.undefine_attribute_methods(*args) + end end # Checks whether the method is defined in the model or any of its subclasses diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2979ad1cb3..92f80c6eaa 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1332,7 +1332,7 @@ MSG # Returns the class descending directly from ActiveRecord::Base or an # abstract class, if any, in the inheritance hierarchy. def class_of_active_record_descendant(klass) - if klass.superclass == Base || klass.superclass.abstract_class? + if klass == Base || klass.superclass == Base || klass.superclass.abstract_class? klass elsif klass.superclass.nil? raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" -- cgit v1.2.3 From ac687ed651773fccecbc22cd6d8b07d5439ceb76 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 12 Sep 2011 22:12:12 +0100 Subject: Let Ruby deal with method visibility. Check respond_to_without_attributes? in method_missing. If there is any method that responds (even private), let super handle it and raise NoMethodError if necessary. --- activerecord/lib/active_record/attribute_methods.rb | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index a8cb89fb65..8d7eb4a48d 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -61,14 +61,17 @@ module ActiveRecord end end - def method_missing(method_id, *args, &block) - # If we haven't generated any methods yet, generate them, then - # see if we've created the method we're looking for. - if !self.class.attribute_methods_generated? + # If we haven't generated any methods yet, generate them, then + # see if we've created the method we're looking for. + def method_missing(method, *args, &block) + unless self.class.attribute_methods_generated? self.class.define_attribute_methods - method_name = method_id.to_s - guard_private_attribute_method!(method_name, args) - send(method_id, *args, &block) + + if respond_to_without_attributes?(method) + send(method, *args, &block) + else + super + end else super end -- cgit v1.2.3 From 1a421ceccb25b15c87e97567fff573c941aa3ab3 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 12 Sep 2011 23:58:37 +0100 Subject: Deprecate using method_missing for attributes that are columns. This shouldn't ever happen unless people are doing something particularly weird, but adding a deprecation in case there are bugs not caught by our tests. --- activerecord/lib/active_record/attribute_methods.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 8d7eb4a48d..dc6dc2e63a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/enumerable' +require 'active_support/deprecation' module ActiveRecord # = Active Record Attribute Methods @@ -77,6 +78,20 @@ module ActiveRecord end end + def attribute_missing(match, *args, &block) + if self.class.columns_hash[match.attr_name] + ActiveSupport::Deprecation.warn( + "The method `#{match.method_name}', matching the attribute `#{match.attr_name}' has " \ + "dispatched through method_missing. This shouldn't happen, because `#{match.attr_name}' " \ + "is a column of the table. If this error has happened through normal usage of Active " \ + "Record (rather than through your own code or external libraries), please report it as " \ + "a bug." + ) + end + + super + end + def respond_to?(name, include_private = false) self.class.define_attribute_methods unless self.class.attribute_methods_generated? super -- cgit v1.2.3 From 11870117c6d9231b79e8125218728423e9dff207 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 13 Sep 2011 19:09:01 +0100 Subject: Rename first_or_new to first_or_initialize. For consistency with find_or_initialize_by. Also remove first_or_build alias. --- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 92f80c6eaa..558b341c06 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -442,7 +442,7 @@ module ActiveRecord #:nodoc: class << self # Class methods delegate :find, :first, :first!, :last, :last!, :all, :exists?, :any?, :many?, :to => :scoped - delegate :first_or_create, :first_or_create!, :first_or_new, :first_or_build, :to => :scoped + delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d3f1347e03..ecefaa633c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -132,10 +132,9 @@ module ActiveRecord # Like first_or_create but calls new instead of create. # # Expects arguments in the same format as Base.new. - def first_or_new(attributes = nil, options = {}, &block) + def first_or_initialize(attributes = nil, options = {}, &block) first || new(attributes, options, &block) end - alias :first_or_build :first_or_new def respond_to?(method, include_private = false) arel.respond_to?(method, include_private) || -- cgit v1.2.3 From 3777d4217b58bbb20a7301521f5451b5f55af78d Mon Sep 17 00:00:00 2001 From: Erik Behrends Date: Tue, 13 Sep 2011 22:42:39 +0300 Subject: [:class_name] option in belongs_to should mention belongs_to and not has_one --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 8d755b6848..9e7d609d19 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1329,7 +1329,7 @@ module ActiveRecord # # [:class_name] # Specify the class name of the association. Use it only if that name can't be inferred - # from the association name. So has_one :author will by default be linked to the Author class, but + # from the association name. So belongs_to :author will by default be linked to the Author class, but # if the real class name is Person, you'll have to specify it with this option. # [:conditions] # Specify the conditions that the associated object must meet in order to be included as a +WHERE+ -- cgit v1.2.3 From 55da28dd2fa734de256a13fb09469eaa3ab15599 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 13 Sep 2011 23:23:29 +0100 Subject: We don't need to build a set for DangerousAttributeError. We can just use method_defined? and private_method_defined? --- activerecord/lib/active_record/attribute_methods.rb | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index dc6dc2e63a..0b074da0a5 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -42,23 +42,29 @@ module ActiveRecord # that also derive from Active Record. Raises DangerousAttributeError if the # method is defined by Active Record though. def instance_method_already_implemented?(method_name) + if dangerous_attribute_method?(method_name) + raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" + end + method_name = method_name.to_s index = ancestors.index(ActiveRecord::Base) || ancestors.length @_defined_class_methods ||= ancestors.first(index).map { |m| m.instance_methods(false) | m.private_instance_methods(false) }.flatten.map {|m| m.to_s }.to_set - @@_defined_activerecord_methods ||= defined_activerecord_methods - raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @_defined_class_methods.include?(method_name) || generated_attribute_methods.method_defined?(method_name) end - def defined_activerecord_methods + # A method name is 'dangerous' if it is already defined by Active Record, but + # not by any ancestors. (So 'puts' is not dangerous but 'save' is.) + def dangerous_attribute_method?(method_name) active_record = ActiveRecord::Base - super_klass = ActiveRecord::Base.superclass - methods = (active_record.instance_methods - super_klass.instance_methods) + - (active_record.private_instance_methods - super_klass.private_instance_methods) - methods.map {|m| m.to_s }.to_set + superclass = ActiveRecord::Base.superclass + + (active_record.method_defined?(method_name) || + active_record.private_method_defined?(method_name)) && + !superclass.method_defined?(method_name) && + !superclass.private_method_defined?(method_name) end end -- cgit v1.2.3 From 3b8a7cf2a7b2ffce671ca7f655de736c1054edbc Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 13 Sep 2011 23:52:38 +0100 Subject: Stop trying to be clever about when to define attribute methods. There is no meaningful performance penalty in defining attribute methods, since it only happens once. There is also no reason *not* to define them, since they get thrown in an included module, so they will not 'overwrite' anything. In fact, this is desirable, since it allows us to call super. --- activerecord/lib/active_record/attribute_methods.rb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 0b074da0a5..d7bfaa5655 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -38,21 +38,12 @@ module ActiveRecord end end - # Checks whether the method is defined in the model or any of its subclasses - # that also derive from Active Record. Raises DangerousAttributeError if the - # method is defined by Active Record though. def instance_method_already_implemented?(method_name) if dangerous_attribute_method?(method_name) raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" end - method_name = method_name.to_s - index = ancestors.index(ActiveRecord::Base) || ancestors.length - @_defined_class_methods ||= ancestors.first(index).map { |m| - m.instance_methods(false) | m.private_instance_methods(false) - }.flatten.map {|m| m.to_s }.to_set - - @_defined_class_methods.include?(method_name) || generated_attribute_methods.method_defined?(method_name) + super end # A method name is 'dangerous' if it is already defined by Active Record, but -- cgit v1.2.3 From 681c4dbb0222ac403de8ea0bfcf8ad77c5430585 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 14 Sep 2011 09:43:31 +1000 Subject: Add documentation for the extending method in ActiveRecord:QueryMethods --- .../lib/active_record/relation/query_methods.rb | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 7eda9ad8e8..8dc59583c1 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -147,6 +147,42 @@ module ActiveRecord relation end + # Used to extend a scope with additional methods, either through + # a module or through a block provided. + # + # The object returned is a relation, which can be further extended. + # + # === Using a module + # + # module Pagination + # def page(number) + # # pagination code goes here + # end + # end + # + # scope = Model.scoped.extending(Pagination) + # scope.page(params[:page]) + # + # This can also take a list of modules also: + # + # scope = Model.scoped.extending(Pagination, SomethingElse) + # + # === Using a block + # + # scope = Model.scoped.extending do + # def page(number) + # # pagination code goes here + # end + # end + # scope.page(params[:page]) + # + # You can also use a block and a module list: + # + # scope = Model.scoped.extending(Pagination) do + # def per_page(number) + # # pagination code goes here + # end + # end def extending(*modules) modules << Module.new(&Proc.new) if block_given? -- cgit v1.2.3 From dcd70773fc66f84555cf55de0c0c2b8cd8ae8cc1 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 14 Sep 2011 23:15:18 +0530 Subject: minor edit --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8dc59583c1..a11b7a3864 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -163,7 +163,7 @@ module ActiveRecord # scope = Model.scoped.extending(Pagination) # scope.page(params[:page]) # - # This can also take a list of modules also: + # You can also pass a list of modules: # # scope = Model.scoped.extending(Pagination, SomethingElse) # -- cgit v1.2.3 From 908f2616d5c2b9e26eb180859f7df529a7a59f08 Mon Sep 17 00:00:00 2001 From: Ryan Bigg Date: Wed, 14 Sep 2011 18:53:08 +1000 Subject: Document ActiveRecord::QueryMethods#select --- .../lib/active_record/relation/query_methods.rb | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a11b7a3864..4468a38ee6 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -37,6 +37,35 @@ module ActiveRecord relation end + # Works in two unique ways. + # + # First: takes a block so it can be used just like Array#select. + # + # Model.scoped.select { |m| m.field == value } + # + # This will build an array of objects from the database for the scope, + # converting them into an array and iterating through them using Array#select. + # + # Second: Modifies the SELECT statement for the query so that only certain + # fields are retreived: + # + # >> Model.select(:field) + # => [#] + # + # Although in the above example it looks as though this method returns an + # array, in actual fact it returns a relation object and can have other query + # methods appended to it, such as the other methods in ActiveRecord::QueryMethods. + # + # This method will also take multiple parameters: + # + # >> Model.select(:field, :other_field, :and_one_more) + # => [#] + # + # Any attributes that do not have fields retreived by a select + # will return `nil` when the getter method for that attribute is used: + # + # >> Model.select(:field).first.other_field + # => nil def select(value = Proc.new) if block_given? to_a.select {|*block_args| value.call(*block_args) } -- cgit v1.2.3 From 51bef9d8fb0b4da7a104425ab8545e9331387743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 18 Sep 2011 09:09:01 -0700 Subject: to_xml should also rely on serializable hash. --- activerecord/lib/active_record/serializers/xml_serializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index cbfa1ad609..0e7f57aa43 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -179,7 +179,7 @@ module ActiveRecord #:nodoc: class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: def initialize(*args) super - options[:except] |= Array.wrap(@serializable.class.inheritance_column) + options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column) end class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: -- cgit v1.2.3 From ed1ada84ac5b443ef979c14a4d0f1a8a57fe9ab9 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Tue, 20 Sep 2011 19:39:46 +0530 Subject: copy edits 908f2616 --- activerecord/lib/active_record/relation/query_methods.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 4468a38ee6..670ba0987d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -47,21 +47,21 @@ module ActiveRecord # converting them into an array and iterating through them using Array#select. # # Second: Modifies the SELECT statement for the query so that only certain - # fields are retreived: + # fields are retrieved: # # >> Model.select(:field) # => [#] # # Although in the above example it looks as though this method returns an - # array, in actual fact it returns a relation object and can have other query + # array, it actually returns a relation object and can have other query # methods appended to it, such as the other methods in ActiveRecord::QueryMethods. # # This method will also take multiple parameters: # # >> Model.select(:field, :other_field, :and_one_more) - # => [#] + # => [#] # - # Any attributes that do not have fields retreived by a select + # Any attributes that do not have fields retrieved by a select # will return `nil` when the getter method for that attribute is used: # # >> Model.select(:field).first.other_field -- cgit v1.2.3 From b838059817aca490f78e3bb74a070729270300db Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 26 Sep 2011 10:41:11 +0100 Subject: CollectionProxy#replace should change the DB records rather than just mutating the array. Fixes #3020. --- activerecord/lib/active_record/associations/collection_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 6ba3d45aff..3181ca9a32 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -46,7 +46,7 @@ module ActiveRecord delegate :select, :find, :first, :last, :build, :create, :create!, - :concat, :delete_all, :destroy_all, :delete, :destroy, :uniq, + :concat, :replace, :delete_all, :destroy_all, :delete, :destroy, :uniq, :sum, :count, :size, :length, :empty?, :any?, :many?, :include?, :to => :@association -- cgit v1.2.3 From 3b87c38d029c1626161a3e7699d40da3e789d7cb Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 26 Sep 2011 15:41:31 +0100 Subject: Fix belongs_to polymorphic with custom primary key on target. Closes #3104. --- .../associations/association_scope.rb | 7 +++++- .../associations/belongs_to_association.rb | 6 ++++- activerecord/lib/active_record/reflection.rb | 26 +++++++++------------- 3 files changed, 22 insertions(+), 17 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 9e6d9e73c5..a0efdbc2a4 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -68,7 +68,12 @@ module ActiveRecord end if reflection.source_macro == :belongs_to - key = reflection.association_primary_key + if reflection.options[:polymorphic] + key = reflection.association_primary_key(klass) + else + key = reflection.association_primary_key + end + foreign_key = reflection.foreign_key else key = reflection.foreign_key diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 58c9648ce8..97f531d064 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -45,7 +45,11 @@ module ActiveRecord end def replace_keys(record) - owner[reflection.foreign_key] = record && record[reflection.association_primary_key] + if record + owner[reflection.foreign_key] = record[reflection.association_primary_key(record.class)] + else + owner[reflection.foreign_key] = nil + end end def foreign_key_present? diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 89179779e3..1929a808ed 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -211,11 +211,9 @@ module ActiveRecord @association_foreign_key ||= options[:association_foreign_key] || class_name.foreign_key end - def association_primary_key - @association_primary_key ||= - options[:primary_key] || - !options[:polymorphic] && klass.primary_key || - 'id' + # klass option is necessary to support loading polymorphic associations + def association_primary_key(klass = self.klass) + options[:primary_key] || klass.primary_key end def active_record_primary_key @@ -463,17 +461,15 @@ module ActiveRecord # We want to use the klass from this reflection, rather than just delegate straight to # the source_reflection, because the source_reflection may be polymorphic. We still # need to respect the source_reflection's :primary_key option, though. - def association_primary_key - @association_primary_key ||= begin - # Get the "actual" source reflection if the immediate source reflection has a - # source reflection itself - source_reflection = self.source_reflection - while source_reflection.source_reflection - source_reflection = source_reflection.source_reflection - end - - source_reflection.options[:primary_key] || klass.primary_key + def association_primary_key(klass = self.klass) + # Get the "actual" source reflection if the immediate source reflection has a + # source reflection itself + source_reflection = self.source_reflection + while source_reflection.source_reflection + source_reflection = source_reflection.source_reflection end + + source_reflection.options[:primary_key] || klass.primary_key end # Gets an array of possible :through source reflection names: -- cgit v1.2.3 From 89e98e278abe8564b80953855fcb4bcb9871c51c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 26 Sep 2011 08:45:53 -0700 Subject: Merge pull request #3030 from htanata/fix_habtm_select_query_method Fix: habtm doesn't respect select query method --- activerecord/lib/active_record/associations/association_scope.rb | 4 ---- 1 file changed, 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index a0efdbc2a4..6cc401e6cc 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -42,10 +42,6 @@ module ActiveRecord select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*" end - if reflection.macro == :has_and_belongs_to_many - select_value ||= reflection.klass.arel_table[Arel.star] - end - select_value end -- cgit v1.2.3 From b3407c86cfccd2cc6258b8879e91600fe25c6e48 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 26 Sep 2011 18:14:16 +0100 Subject: Don't require a DB connection when setting primary key. Closes #2807. --- activerecord/lib/active_record/attribute_methods/primary_key.rb | 1 - activerecord/lib/active_record/base.rb | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index ed71b5e7d4..a404a5edd7 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -66,7 +66,6 @@ module ActiveRecord @primary_key ||= '' self.original_primary_key = @primary_key value &&= value.to_s - connection_pool.primary_keys[table_name] = value self.primary_key = block_given? ? instance_eval(&block) : value end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 558b341c06..78159d13d4 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -707,6 +707,10 @@ module ActiveRecord #:nodoc: # Returns an array of column objects for the table associated with this class. def columns + if defined?(@primary_key) + connection_pool.primary_keys[table_name] ||= primary_key + end + connection_pool.columns[table_name] end -- cgit v1.2.3 From aafd4c0452170c2fd3c86b94b86026df7ecb7f7d Mon Sep 17 00:00:00 2001 From: Diego Plentz Date: Mon, 26 Sep 2011 20:06:17 -0300 Subject: fixing docs for delete_sql where quotes should be used in this example. --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 9e7d609d19..0952ea2829 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1575,7 +1575,7 @@ module ActiveRecord # has_and_belongs_to_many :categories, :join_table => "prods_cats" # has_and_belongs_to_many :categories, :readonly => true # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => - # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' + # "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" def has_and_belongs_to_many(name, options = {}, &extension) Builder::HasAndBelongsToMany.build(self, name, options, &extension) end -- cgit v1.2.3 From 78de4fcd050b5f2f67a1bf974ba6960b4b017443 Mon Sep 17 00:00:00 2001 From: Dmitriy Kiriyenko Date: Thu, 4 Aug 2011 15:44:08 +0300 Subject: When running "rake db:drop" also drop test database in development environment. --- .../lib/active_record/railties/databases.rake | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b3316fd1a2..1009af850c 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -37,11 +37,7 @@ db_namespace = namespace :db do desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)' task :create => :load_config do - # Make the test database at the same time as the development one, if it exists - if Rails.env.development? && ActiveRecord::Base.configurations['test'] - create_database(ActiveRecord::Base.configurations['test']) - end - create_database(ActiveRecord::Base.configurations[Rails.env]) + configs_for_environment.each { |config| create_database(config) } end def mysql_creation_options(config) @@ -138,12 +134,7 @@ db_namespace = namespace :db do desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)' task :drop => :load_config do - config = ActiveRecord::Base.configurations[Rails.env || 'development'] - begin - drop_database(config) - rescue Exception => e - $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}" - end + configs_for_environment.each { |config| drop_database_and_rescue(config) } end def local_database?(config, &block) @@ -548,6 +539,19 @@ def drop_database(config) end end +def drop_database_and_rescue(config) + begin + drop_database(config) + rescue Exception => e + $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}" + end +end + +def configs_for_environment + environments = [Rails.env, ("test" if Rails.env.development?)] + ActiveRecord::Base.configurations.values_at(*environments).compact +end + def session_table_name ActiveRecord::SessionStore::Session.table_name end -- cgit v1.2.3 From adb8ac153f8e9e497eaecf62165c0bd53c18149a Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 29 Sep 2011 17:59:40 +0100 Subject: Don't call self.class unless necessary. Closes #3171. --- activerecord/lib/active_record/reflection.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 1929a808ed..120ff0cac6 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -212,8 +212,8 @@ module ActiveRecord end # klass option is necessary to support loading polymorphic associations - def association_primary_key(klass = self.klass) - options[:primary_key] || klass.primary_key + def association_primary_key(klass = nil) + options[:primary_key] || (klass || self.klass).primary_key end def active_record_primary_key -- cgit v1.2.3 From 80e0cd32905d74b7f0779807d9b2428d5d0ce64b Mon Sep 17 00:00:00 2001 From: Jared Mehle Date: Mon, 3 Oct 2011 14:50:40 -0500 Subject: Quoted path to _structure.sql file in db:test:clone_structure task. Leaving the path unquoted causes errors in paths containing spaces or dashes. --- activerecord/lib/active_record/railties/databases.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b3316fd1a2..6145754b45 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -424,10 +424,10 @@ db_namespace = namespace :db do 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']}` + `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` + `sqlite3 #{dbfile} < "#{Rails.root}/db/#{Rails.env}_structure.sql"` when 'sqlserver' `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` when 'oci', 'oracle' -- cgit v1.2.3 From 88f60fd1a744d93528dc36fa1461a59df3efc6a3 Mon Sep 17 00:00:00 2001 From: Ben Woosley Date: Mon, 3 Oct 2011 22:24:45 -0700 Subject: Changing rake db:schema:dump to run :environment as well as :load_config, as running :load_config alone will lead to the dumper being run without including extensions such as those included in foreigner and spatial_adapter. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverses a change made here: https://github.com/rails/rails/commit/5df72a238e9fcb18daf6ab6e6dc9051c9106d7bb#L0L324 I'm assuming here that :load_config needs to be invoked separately from :environment, as it is elsewhere in the file for db operations, if not the alternative is to go back to "task :dump => :environment do". Signed-off-by: José Valim --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index b3316fd1a2..f4a813d704 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -341,7 +341,7 @@ db_namespace = namespace :db do namespace :schema do desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR' - task :dump => :load_config do + task :dump => [:environment, :load_config] do require 'active_record/schema_dumper' filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb" File.open(filename, "w:utf-8") do |file| -- cgit v1.2.3 From ee2be435b1e5c0e94a4ee93a1a310e0471a77d07 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 5 Oct 2011 01:09:43 +0100 Subject: Raise error on unknown primary key. If we don't have a primary key when we ask for it, it's better to fail fast. Fixes GH #2307. --- .../lib/active_record/attribute_methods/primary_key.rb | 7 +++++++ activerecord/lib/active_record/attribute_methods/read.rb | 6 +++--- activerecord/lib/active_record/attribute_methods/write.rb | 2 +- activerecord/lib/active_record/base.rb | 9 +++++---- activerecord/lib/active_record/errors.rb | 14 ++++++++++++++ activerecord/lib/active_record/fixtures.rb | 2 +- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/relation.rb | 6 +++--- activerecord/lib/active_record/transactions.rb | 2 +- 9 files changed, 36 insertions(+), 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index a404a5edd7..36d7f4ad11 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -14,6 +14,8 @@ module ActiveRecord # primary_key_prefix_type setting, though. def primary_key @primary_key ||= reset_primary_key + raise ActiveRecord::UnknownPrimaryKey.new(self) unless @primary_key + @primary_key end # Returns a quoted version of the primary key name, used to construct SQL statements. @@ -29,6 +31,11 @@ module ActiveRecord key end + def primary_key? #:nodoc: + @primary_key ||= reset_primary_key + !@primary_key.nil? + end + def get_primary_key(base_name) #:nodoc: return 'id' unless base_name && !base_name.blank? diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 4174e4da09..8566ecad14 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -40,7 +40,7 @@ module ActiveRecord define_read_method(attr_name, attr_name, columns_hash[attr_name]) end - if attr_name == primary_key && attr_name != "id" + if primary_key? && attr_name == primary_key && attr_name != "id" define_read_method('id', attr_name, columns_hash[attr_name]) end end @@ -63,7 +63,7 @@ module ActiveRecord cast_code = column.type_cast_code('v') access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}" - unless attr_name.to_s == self.primary_key.to_s + unless primary_key? && attr_name.to_s == primary_key.to_s access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") end @@ -107,7 +107,7 @@ module ActiveRecord def _read_attribute(attr_name) attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == 'id' + attr_name = self.class.primary_key? && self.class.primary_key if attr_name == 'id' value = @attributes[attr_name] unless value.nil? if column = column_for_attribute(attr_name) diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index e9cdb130db..4db6d71ba6 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -18,7 +18,7 @@ module ActiveRecord end end - if attr_name == primary_key && attr_name != "id" + if primary_key? && attr_name == primary_key && attr_name != "id" generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='") end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78159d13d4..137b4c6534 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -708,7 +708,7 @@ module ActiveRecord #:nodoc: # Returns an array of column objects for the table associated with this class. def columns if defined?(@primary_key) - connection_pool.primary_keys[table_name] ||= primary_key + connection_pool.primary_keys[table_name] ||= @primary_key end connection_pool.columns[table_name] @@ -953,7 +953,7 @@ module ActiveRecord #:nodoc: # objects of different types from the same table. def instantiate(record) sti_class = find_sti_class(record[inheritance_column]) - record_id = sti_class.primary_key && record[sti_class.primary_key] + record_id = sti_class.primary_key? && record[sti_class.primary_key] if ActiveRecord::IdentityMap.enabled? && record_id if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number? @@ -1941,8 +1941,9 @@ MSG # The primary key and inheritance column can never be set by mass-assignment for security reasons. def self.attributes_protected_by_default - default = [ primary_key, inheritance_column ] - default << 'id' unless primary_key.eql? 'id' + default = [ inheritance_column ] + default << primary_key if primary_key? + default << 'id' unless primary_key? && primary_key == 'id' default end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index ad7d8cd63c..8262b60f6e 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -169,4 +169,18 @@ module ActiveRecord @errors = errors end end + + # Raised when a model attempts to fetch its primary key from the database, but the table + # has no primary key declared. + class UnknownPrimaryKey < ActiveRecordError + attr_reader :model + + def initialize(model) + @model = model + end + + def message + "Unknown primary key for table #{model.table_name} in model #{model}." + end + end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6f1ec7f9b3..97af15c9e8 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -622,7 +622,7 @@ module ActiveRecord private def primary_key_name - @primary_key_name ||= model_class && model_class.primary_key + @primary_key_name ||= model_class && model_class.primary_key? && model_class.primary_key end def has_primary_key_column? diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 5e65e46a7d..b5dadb929d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -314,7 +314,7 @@ module ActiveRecord new_id = self.class.unscoped.insert attributes_values - self.id ||= new_id if self.class.primary_key + self.id ||= new_id if self.class.primary_key? IdentityMap.add(self) if IdentityMap.enabled? @new_record = false diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ecefaa633c..bf61d79a2c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -13,7 +13,7 @@ module ActiveRecord # These are explicitly delegated to improve performance (avoids method_missing) delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a - delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :column_hash,:to => :klass + delegate :table_name, :quoted_table_name, :primary_key, :primary_key?, :quoted_primary_key, :connection, :column_hash,:to => :klass attr_reader :table, :klass, :loaded attr_accessor :extensions, :default_scoped @@ -36,7 +36,7 @@ module ActiveRecord def insert(values) primary_key_value = nil - if primary_key && Hash === values + if primary_key? && Hash === values primary_key_value = values[values.keys.find { |k| k.name == primary_key }] @@ -70,7 +70,7 @@ module ActiveRecord conn.insert( im, 'SQL', - primary_key, + primary_key? && primary_key, primary_key_value, nil, binds) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index ae97a3f3ca..d4870dd3f2 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -303,7 +303,7 @@ module ActiveRecord # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc @_start_transaction_state ||= {} - @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key) + @_start_transaction_state[:id] = id if self.class.primary_key? unless @_start_transaction_state.include?(:new_record) @_start_transaction_state[:new_record] = @new_record end -- cgit v1.2.3 From cfc95d89aeffba5a026afcf272bdf3ff231a8983 Mon Sep 17 00:00:00 2001 From: "Juan M. Cuello" Date: Wed, 5 Oct 2011 11:43:03 -0300 Subject: Use the schema_search_path in prepared statements. To allow the use of prepared statements when changing schemas in postgres, the schema search path is added to the sql key. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5402918b1d..d859843260 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1035,13 +1035,14 @@ module ActiveRecord end def exec_cache(sql, binds) - unless @statements.key? sql + sql_key = "#{schema_search_path}-#{sql}" + unless @statements.key? sql_key nextkey = @statements.next_key @connection.prepare nextkey, sql - @statements[sql] = nextkey + @statements[sql_key] = nextkey end - key = @statements[sql] + key = @statements[sql_key] # Clear the queue @connection.get_last_result -- cgit v1.2.3 From 64747654ca661d695622c0ad9e33b8d9e6df8048 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 5 Oct 2011 18:11:25 +0100 Subject: Revert "Raise error on unknown primary key." This reverts commit ee2be435b1e5c0e94a4ee93a1a310e0471a77d07. --- .../lib/active_record/attribute_methods/primary_key.rb | 7 ------- activerecord/lib/active_record/attribute_methods/read.rb | 6 +++--- activerecord/lib/active_record/attribute_methods/write.rb | 2 +- activerecord/lib/active_record/base.rb | 9 ++++----- activerecord/lib/active_record/errors.rb | 14 -------------- activerecord/lib/active_record/fixtures.rb | 2 +- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/relation.rb | 6 +++--- activerecord/lib/active_record/transactions.rb | 2 +- 9 files changed, 14 insertions(+), 36 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 36d7f4ad11..a404a5edd7 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -14,8 +14,6 @@ module ActiveRecord # primary_key_prefix_type setting, though. def primary_key @primary_key ||= reset_primary_key - raise ActiveRecord::UnknownPrimaryKey.new(self) unless @primary_key - @primary_key end # Returns a quoted version of the primary key name, used to construct SQL statements. @@ -31,11 +29,6 @@ module ActiveRecord key end - def primary_key? #:nodoc: - @primary_key ||= reset_primary_key - !@primary_key.nil? - end - def get_primary_key(base_name) #:nodoc: return 'id' unless base_name && !base_name.blank? diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 8566ecad14..4174e4da09 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -40,7 +40,7 @@ module ActiveRecord define_read_method(attr_name, attr_name, columns_hash[attr_name]) end - if primary_key? && attr_name == primary_key && attr_name != "id" + if attr_name == primary_key && attr_name != "id" define_read_method('id', attr_name, columns_hash[attr_name]) end end @@ -63,7 +63,7 @@ module ActiveRecord cast_code = column.type_cast_code('v') access_code = "(v=@attributes['#{attr_name}']) && #{cast_code}" - unless primary_key? && attr_name.to_s == primary_key.to_s + unless attr_name.to_s == self.primary_key.to_s access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") end @@ -107,7 +107,7 @@ module ActiveRecord def _read_attribute(attr_name) attr_name = attr_name.to_s - attr_name = self.class.primary_key? && self.class.primary_key if attr_name == 'id' + attr_name = self.class.primary_key if attr_name == 'id' value = @attributes[attr_name] unless value.nil? if column = column_for_attribute(attr_name) diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 4db6d71ba6..e9cdb130db 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -18,7 +18,7 @@ module ActiveRecord end end - if primary_key? && attr_name == primary_key && attr_name != "id" + if attr_name == primary_key && attr_name != "id" generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='") end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 137b4c6534..78159d13d4 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -708,7 +708,7 @@ module ActiveRecord #:nodoc: # Returns an array of column objects for the table associated with this class. def columns if defined?(@primary_key) - connection_pool.primary_keys[table_name] ||= @primary_key + connection_pool.primary_keys[table_name] ||= primary_key end connection_pool.columns[table_name] @@ -953,7 +953,7 @@ module ActiveRecord #:nodoc: # objects of different types from the same table. def instantiate(record) sti_class = find_sti_class(record[inheritance_column]) - record_id = sti_class.primary_key? && record[sti_class.primary_key] + record_id = sti_class.primary_key && record[sti_class.primary_key] if ActiveRecord::IdentityMap.enabled? && record_id if (column = sti_class.columns_hash[sti_class.primary_key]) && column.number? @@ -1941,9 +1941,8 @@ MSG # The primary key and inheritance column can never be set by mass-assignment for security reasons. def self.attributes_protected_by_default - default = [ inheritance_column ] - default << primary_key if primary_key? - default << 'id' unless primary_key? && primary_key == 'id' + default = [ primary_key, inheritance_column ] + default << 'id' unless primary_key.eql? 'id' default end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 8262b60f6e..ad7d8cd63c 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -169,18 +169,4 @@ module ActiveRecord @errors = errors end end - - # Raised when a model attempts to fetch its primary key from the database, but the table - # has no primary key declared. - class UnknownPrimaryKey < ActiveRecordError - attr_reader :model - - def initialize(model) - @model = model - end - - def message - "Unknown primary key for table #{model.table_name} in model #{model}." - end - end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 97af15c9e8..6f1ec7f9b3 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -622,7 +622,7 @@ module ActiveRecord private def primary_key_name - @primary_key_name ||= model_class && model_class.primary_key? && model_class.primary_key + @primary_key_name ||= model_class && model_class.primary_key end def has_primary_key_column? diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index b5dadb929d..5e65e46a7d 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -314,7 +314,7 @@ module ActiveRecord new_id = self.class.unscoped.insert attributes_values - self.id ||= new_id if self.class.primary_key? + self.id ||= new_id if self.class.primary_key IdentityMap.add(self) if IdentityMap.enabled? @new_record = false diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index bf61d79a2c..ecefaa633c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -13,7 +13,7 @@ module ActiveRecord # These are explicitly delegated to improve performance (avoids method_missing) delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a - delegate :table_name, :quoted_table_name, :primary_key, :primary_key?, :quoted_primary_key, :connection, :column_hash,:to => :klass + delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :column_hash,:to => :klass attr_reader :table, :klass, :loaded attr_accessor :extensions, :default_scoped @@ -36,7 +36,7 @@ module ActiveRecord def insert(values) primary_key_value = nil - if primary_key? && Hash === values + if primary_key && Hash === values primary_key_value = values[values.keys.find { |k| k.name == primary_key }] @@ -70,7 +70,7 @@ module ActiveRecord conn.insert( im, 'SQL', - primary_key? && primary_key, + primary_key, primary_key_value, nil, binds) diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index d4870dd3f2..ae97a3f3ca 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -303,7 +303,7 @@ module ActiveRecord # Save the new record state and id of a record so it can be restored later if a transaction fails. def remember_transaction_record_state #:nodoc @_start_transaction_state ||= {} - @_start_transaction_state[:id] = id if self.class.primary_key? + @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key) unless @_start_transaction_state.include?(:new_record) @_start_transaction_state[:new_record] = @new_record end -- cgit v1.2.3 From 2e9e647fee59d975c9564d96c924d29ffe57f2a2 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 5 Oct 2011 20:20:04 +0100 Subject: Raise an exception on unknown primary key inside AssociationReflection. An association between two models cannot be made if a relevant key is unknown, so fail fast rather than generating invalid SQL. Fixes #3207. --- activerecord/lib/active_record/errors.rb | 13 +++++++++++++ activerecord/lib/active_record/reflection.rb | 12 ++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index ad7d8cd63c..96870cb338 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -169,4 +169,17 @@ module ActiveRecord @errors = errors end end + + # Raised when a primary key is needed, but there is not one specified in the schema or model. + class UnknownPrimaryKey < ActiveRecordError + attr_reader :model + + def initialize(model) + @model = model + end + + def message + "Unknown primary key for table #{model.table_name} in model #{model}." + end + end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 120ff0cac6..5285060288 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -213,11 +213,11 @@ module ActiveRecord # klass option is necessary to support loading polymorphic associations def association_primary_key(klass = nil) - options[:primary_key] || (klass || self.klass).primary_key + options[:primary_key] || primary_key(klass || self.klass) end def active_record_primary_key - @active_record_primary_key ||= options[:primary_key] || active_record.primary_key + @active_record_primary_key ||= options[:primary_key] || primary_key(active_record) end def counter_cache_column @@ -357,6 +357,10 @@ module ActiveRecord active_record.name.foreign_key end end + + def primary_key(klass) + klass.primary_key || raise(UnknownPrimaryKey.new(klass)) + end end # Holds all the meta-data about a :through association as it was specified @@ -461,7 +465,7 @@ module ActiveRecord # We want to use the klass from this reflection, rather than just delegate straight to # the source_reflection, because the source_reflection may be polymorphic. We still # need to respect the source_reflection's :primary_key option, though. - def association_primary_key(klass = self.klass) + def association_primary_key(klass = nil) # Get the "actual" source reflection if the immediate source reflection has a # source reflection itself source_reflection = self.source_reflection @@ -469,7 +473,7 @@ module ActiveRecord source_reflection = source_reflection.source_reflection end - source_reflection.options[:primary_key] || klass.primary_key + source_reflection.options[:primary_key] || primary_key(klass || self.klass) end # Gets an array of possible :through source reflection names: -- cgit v1.2.3 From 999b7ed94d7d7f993cded7376fc4014128d43294 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Wed, 5 Oct 2011 17:21:43 -0700 Subject: Transactional fixtures enlist all active database connections. You can use multiple databases in your tests without disabling transactional fixtures. --- activerecord/lib/active_record/fixtures.rb | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 6f1ec7f9b3..cad9417216 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -842,9 +842,12 @@ module ActiveRecord @loaded_fixtures = load_fixtures @@already_loaded_fixtures[self.class] = @loaded_fixtures end - ActiveRecord::Base.connection.increment_open_transactions - ActiveRecord::Base.connection.transaction_joinable = false - ActiveRecord::Base.connection.begin_db_transaction + @fixture_connections = enlist_fixture_connections + @fixture_connections.each do |connection| + connection.increment_open_transactions + connection.transaction_joinable = false + connection.begin_db_transaction + end # Load fixtures for every test. else ActiveRecord::Fixtures.reset_cache @@ -864,13 +867,22 @@ module ActiveRecord end # Rollback changes if a transaction is active. - if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0 - ActiveRecord::Base.connection.rollback_db_transaction - ActiveRecord::Base.connection.decrement_open_transactions + if run_in_transaction? + @fixture_connections.each do |connection| + if connection.open_transactions != 0 + connection.rollback_db_transaction + connection.decrement_open_transactions + end + end + @fixture_connections.clear end ActiveRecord::Base.clear_active_connections! end + def enlist_fixture_connections + ActiveRecord::Base.connection_handler.connection_pools.values.map(&:connection) + end + private def load_fixtures fixtures = ActiveRecord::Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names) -- cgit v1.2.3 From f41b58d3b25eb57ba5c890b713301ba2e3fcb9b7 Mon Sep 17 00:00:00 2001 From: "Mark J. Titorenko" Date: Mon, 13 Jun 2011 02:14:18 +0100 Subject: use thread locals and an instance variable within QueryCache#BodyProxy to maintain appropriate linkage with AR database connection across threads --- .../active_record/connection_adapters/abstract/connection_pool.rb | 2 +- .../connection_adapters/abstract/connection_specification.rb | 8 ++++++++ activerecord/lib/active_record/query_cache.rb | 6 ++++-- 3 files changed, 13 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 20863e73aa..77a5fe1efb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -314,7 +314,7 @@ module ActiveRecord end def current_connection_id #:nodoc: - Thread.current.object_id + ActiveRecord::Base.connection_id ||= Thread.current.object_id end def checkout_new_connection diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index c08c0263b9..3d0f146fed 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -115,6 +115,14 @@ module ActiveRecord retrieve_connection end + def connection_id + Thread.current['ActiveRecord::Base.connection_id'] + end + + def connection_id=(connection_id) + Thread.current['ActiveRecord::Base.connection_id'] = connection_id + end + # Returns the configuration of the associated connection as a hash: # # ActiveRecord::Base.connection_config diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 10c0dc6f2a..466d148901 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -28,9 +28,10 @@ module ActiveRecord end class BodyProxy # :nodoc: - def initialize(original_cache_value, target) + def initialize(original_cache_value, target, connection_id) @original_cache_value = original_cache_value @target = target + @connection_id = connection_id end def method_missing(method_sym, *arguments, &block) @@ -48,6 +49,7 @@ module ActiveRecord def close @target.close if @target.respond_to?(:close) ensure + ActiveRecord::Base.connection_id = @connection_id ActiveRecord::Base.connection.clear_query_cache unless @original_cache_value ActiveRecord::Base.connection.disable_query_cache! @@ -60,7 +62,7 @@ module ActiveRecord ActiveRecord::Base.connection.enable_query_cache! status, headers, body = @app.call(env) - [status, headers, BodyProxy.new(old, body)] + [status, headers, BodyProxy.new(old, body, ActiveRecord::Base.connection_id)] rescue Exception => e ActiveRecord::Base.connection.clear_query_cache unless old -- cgit v1.2.3 From c90e5ce779dbf9bd0ee53b68aee9fde2997be123 Mon Sep 17 00:00:00 2001 From: Joseph Palermo Date: Sun, 9 Oct 2011 04:38:36 -0700 Subject: Only use LOWER for mysql case insensitive uniqueness check when column has a case sensitive collation. --- .../connection_adapters/abstract_adapter.rb | 4 ++++ .../connection_adapters/abstract_mysql_adapter.rb | 27 ++++++++++++++++++---- .../connection_adapters/mysql2_adapter.rb | 4 ++-- .../connection_adapters/mysql_adapter.rb | 4 ++-- .../lib/active_record/validations/uniqueness.rb | 4 ++-- 5 files changed, 33 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 443e61b527..4c3a8f7233 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -238,6 +238,10 @@ module ActiveRecord node end + def case_insensitive_comparison(table, attribute, column, value) + table[attribute].lower.eq(table.lower(value)) + end + def current_savepoint_name "active_record_#{open_transactions}" end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 4b7c74e0b8..dd573ba569 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -4,6 +4,13 @@ module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter class Column < ConnectionAdapters::Column # :nodoc: + attr_reader :collation + + def initialize(name, default, sql_type = nil, null = true, collation = nil) + super(name, default, sql_type, null) + @collation = collation + end + def extract_default(default) if sql_type =~ /blob/i || type == :text if default.blank? @@ -28,6 +35,10 @@ module ActiveRecord raise NotImplementedError end + def case_sensitive? + collation && !collation.match(/_ci$/) + end + private def simplified_type(field_type) @@ -157,8 +168,8 @@ module ActiveRecord end # Overridden by the adapters to instantiate their specific Column type. - def new_column(field, default, type, null) # :nodoc: - Column.new(field, default, type, null) + def new_column(field, default, type, null, collation) # :nodoc: + Column.new(field, default, type, null, collation) end # Must return the Mysql error number from the exception, if the exception has an @@ -393,10 +404,10 @@ module ActiveRecord # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name, name = nil)#:nodoc: - sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}" + sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}" execute_and_free(sql, 'SCHEMA') do |result| each_hash(result).map do |field| - new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES") + new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES", field[:Collation]) end end end @@ -501,6 +512,14 @@ module ActiveRecord Arel::Nodes::Bin.new(node) end + def case_insensitive_comparison(table, attribute, column, value) + if column.case_sensitive? + super + else + table[attribute].eq(value) + end + end + def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key) where_sql end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 8b574518e5..971f3c35f3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -47,8 +47,8 @@ module ActiveRecord end end - def new_column(field, default, type, null) # :nodoc: - Column.new(field, default, type, null) + def new_column(field, default, type, null, collation) # :nodoc: + Column.new(field, default, type, null, collation) end def error_number(exception) diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index a1824fe396..f092edecda 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -150,8 +150,8 @@ module ActiveRecord end end - def new_column(field, default, type, null) # :nodoc: - Column.new(field, default, type, null) + def new_column(field, default, type, null, collation) # :nodoc: + Column.new(field, default, type, null, collation) end def error_number(exception) # :nodoc: diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 484b1d369b..2e2ea8c42b 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -57,8 +57,8 @@ module ActiveRecord value = column.limit ? value.to_s.mb_chars[0, column.limit] : value.to_s if column.text? if !options[:case_sensitive] && value && column.text? - # will use SQL LOWER function before comparison - relation = table[attribute].lower.eq(table.lower(value)) + # will use SQL LOWER function before comparison, unless it detects a case insensitive collation + relation = klass.connection.case_insensitive_comparison(table, attribute, column, value) else value = klass.connection.case_sensitive_modifier(value) relation = table[attribute].eq(value) -- cgit v1.2.3 From e552fe16d43d89c37b85cf3d5e8c8e5afe150529 Mon Sep 17 00:00:00 2001 From: Steve Bourne Date: Wed, 12 Oct 2011 10:46:08 -0700 Subject: change activerecord query conditions example to avoid 'type' as column name 'Type' is a reserved column for STI. Changed conditions example to avoid using that column name as an example. The example isn't STI-related (and mentioning STI here is needless clutter), so changing to avoid accidentally encouraging users to use 'type' as a column name for other purposes. --- activerecord/lib/active_record/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78159d13d4..7a9654ffd8 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -115,8 +115,8 @@ module ActiveRecord #:nodoc: # When joining tables, nested hashes or keys written in the form 'table_name.column_name' # can be used to qualify the table name of a particular condition. For instance: # - # Student.joins(:schools).where(:schools => { :type => 'public' }) - # Student.joins(:schools).where('schools.type' => 'public' ) + # Student.joins(:schools).where(:schools => { :category => 'public' }) + # Student.joins(:schools).where('schools.category' => 'public' ) # # == Overwriting default accessors # -- cgit v1.2.3 From 85b64f98d100d37b3a232c315daa10fad37dccdc Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 13 Oct 2011 16:23:48 -0500 Subject: Added ActiveRecord::Base.store for declaring simple single-column key/value stores [DHH] --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/store.rb | 49 +++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 activerecord/lib/active_record/store.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 132dc12680..3572c640eb 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -69,6 +69,7 @@ module ActiveRecord autoload :Schema autoload :SchemaDumper autoload :Serialization + autoload :Store autoload :SessionStore autoload :Timestamp autoload :Transactions diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 78159d13d4..6dae90266e 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2145,7 +2145,7 @@ MSG # AutosaveAssociation needs to be included before Transactions, because we want # #save_with_autosave_associations to be wrapped inside a transaction. include AutosaveAssociation, NestedAttributes - include Aggregations, Transactions, Reflection, Serialization + include Aggregations, Transactions, Reflection, Serialization, Store NilClass.add_whiner(self) if NilClass.respond_to?(:add_whiner) diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb new file mode 100644 index 0000000000..d5910df891 --- /dev/null +++ b/activerecord/lib/active_record/store.rb @@ -0,0 +1,49 @@ +module ActiveRecord + # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. + # It's like a simple key/value store backed into your record when you don't care about being able to + # query that store outside the context of a single record. + # + # You can then declare accessors to this store that are then accessible just like any other attribute + # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's + # already built around just accessing attributes on the model. + # + # Make sure that you declare the database column used for the serialized store as a text, so there's + # plenty of room. + # + # Examples: + # + # class User < ActiveRecord::Base + # store :settings, accessors: [ :color, :homepage ] + # end + # + # u = User.new(color: 'black', homepage: '37signals.com') + # u.color # Accessor stored attribute + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # Add additional accessors to an existing store through store_accessor + # class SuperUser < User + # store_accessor :settings, :privileges, :servants + # end + module Store + extend ActiveSupport::Concern + + module ClassMethods + def store(store_attribute, options = {}) + serialize store_attribute, Hash + store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors + end + + def store_accessor(store_attribute, *keys) + Array(keys).flatten.each do |key| + define_method("#{key}=") do |value| + send(store_attribute)[key] = value + end + + define_method(key) do + send(store_attribute)[key] + end + end + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From 410fa4cf7c710ff062c59c1c90357729c418be65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ba=CC=88uerlein?= Date: Fri, 14 Oct 2011 16:28:02 +0200 Subject: Includes stale record in StaleObjectError --- activerecord/lib/active_record/errors.rb | 9 +++++++++ activerecord/lib/active_record/locking/optimistic.rb | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 96870cb338..950fc7356c 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -99,6 +99,15 @@ module ActiveRecord # # Read more about optimistic locking in ActiveRecord::Locking module RDoc. class StaleObjectError < ActiveRecordError + attr_reader :record + + def initialize(record) + @record = record + end + + def message + "Attempted to update a stale object: #{record.class.name}" + end end # Raised when association is being configured improperly or diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index d9ad7e4132..cafe48cff6 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -103,7 +103,7 @@ module ActiveRecord affected_rows = connection.update stmt unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to update a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError, self end affected_rows @@ -127,7 +127,7 @@ module ActiveRecord affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object: #{self.class.name}" + raise ActiveRecord::StaleObjectError, self end end -- cgit v1.2.3 From c6f0461e898601578fa8160608d19fec319067fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Ba=CC=88uerlein?= Date: Fri, 14 Oct 2011 18:20:41 +0200 Subject: Consider attempted action in exception message of ActiveRecord::StaleObjectError --- activerecord/lib/active_record/errors.rb | 9 +++++---- activerecord/lib/active_record/locking/optimistic.rb | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 950fc7356c..fc80f3081e 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -99,15 +99,16 @@ module ActiveRecord # # Read more about optimistic locking in ActiveRecord::Locking module RDoc. class StaleObjectError < ActiveRecordError - attr_reader :record + attr_reader :record, :attempted_action - def initialize(record) + def initialize(record, attempted_action) @record = record + @attempted_action = attempted_action end def message - "Attempted to update a stale object: #{record.class.name}" - end + "Attempted to #{attempted_action} a stale object: #{record.class.name}" + end end # Raised when association is being configured improperly or diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index cafe48cff6..2df3309648 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -103,7 +103,7 @@ module ActiveRecord affected_rows = connection.update stmt unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, self + raise ActiveRecord::StaleObjectError.new(self, "update") end affected_rows @@ -127,7 +127,7 @@ module ActiveRecord affected_rows = self.class.unscoped.where(predicate).delete_all unless affected_rows == 1 - raise ActiveRecord::StaleObjectError, self + raise ActiveRecord::StaleObjectError.new(self, "destroy") end end -- cgit v1.2.3 From 3dbedd2823ff6c8a1f2f078ae9df9c6ceb275e1b Mon Sep 17 00:00:00 2001 From: Mike Perham Date: Fri, 14 Oct 2011 21:09:53 -0700 Subject: Default timestamps to non-null --- .../active_record/connection_adapters/abstract/schema_definitions.rb | 2 +- .../active_record/connection_adapters/abstract/schema_statements.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 82f564e41d..989a4fcbca 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -252,7 +252,7 @@ module ActiveRecord # Appends :datetime columns :created_at and # :updated_at to the table. def timestamps(*args) - options = args.extract_options! + options = { :null => false }.merge(args.extract_options!) column(:created_at, :datetime, options) column(:updated_at, :datetime, options) end 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 8e3ba1297e..b4a9e29ef1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -507,8 +507,8 @@ module ActiveRecord # ===== Examples # add_timestamps(:suppliers) def add_timestamps(table_name) - add_column table_name, :created_at, :datetime - add_column table_name, :updated_at, :datetime + add_column table_name, :created_at, :datetime, :null => false + add_column table_name, :updated_at, :datetime, :null => false end # Removes the timestamp columns (created_at and updated_at) from the table definition. -- cgit v1.2.3 From 8510a0bb5a8a65e4bc067ee5a7d98aae965b47a5 Mon Sep 17 00:00:00 2001 From: Aaron Christy Date: Sat, 15 Oct 2011 19:53:36 -0400 Subject: Exclude _destroy parameter in :all_blank check (issue #2937) --- activerecord/lib/active_record/nested_attributes.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 2dbebfcaf8..d2065d701f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -220,7 +220,7 @@ module ActiveRecord # validates_presence_of :member # end module ClassMethods - REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |_, value| value.blank? } } + REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } } # Defines an attributes writer for the specified association(s). If you # are using attr_protected or attr_accessible, then you @@ -239,7 +239,8 @@ module ActiveRecord # is specified, a record will be built for all attribute hashes that # do not have a _destroy value that evaluates to true. # Passing :all_blank instead of a Proc will create a proc - # that will reject a record where all the attributes are blank. + # that will reject a record where all the attributes are blank excluding + # any value for _destroy. # [:limit] # Allows you to specify the maximum number of the associated records that # can be processed with the nested attributes. If the size of the -- cgit v1.2.3 From 6a28c512e358a95bbabd24af9d2b0608df787158 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 18 Oct 2011 11:12:18 -0700 Subject: reset prepared statement when schema changes imapact statement results. fixes #3335 --- .../connection_adapters/postgresql_adapter.rb | 54 +++++++++++++++++----- 1 file changed, 43 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d859843260..b7f346e050 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -278,6 +278,11 @@ module ActiveRecord cache.clear end + def delete(sql_key) + dealloc cache[sql_key] + cache.delete sql_key + end + private def cache @cache[$$] @@ -1030,27 +1035,54 @@ module ActiveRecord end private + FEATURE_NOT_SUPPORTED = "0A000" # :nodoc: + def exec_no_cache(sql, binds) @connection.async_exec(sql) end def exec_cache(sql, binds) - sql_key = "#{schema_search_path}-#{sql}" + begin + stmt_key = prepare_statement sql + + # Clear the queue + @connection.get_last_result + @connection.send_query_prepared(stmt_key, binds.map { |col, val| + type_cast(val, col) + }) + @connection.block + @connection.get_last_result + rescue PGError => e + # Get the PG code for the failure. Annoyingly, the code for + # prepared statements whose return value may have changed is + # FEATURE_NOT_SUPPORTED. Check here for more details: + # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573 + code = e.result.result_error_field(PGresult::PG_DIAG_SQLSTATE) + if FEATURE_NOT_SUPPORTED == code + @statements.delete sql_key(sql) + retry + else + raise e + end + end + end + + # Returns the statement identifier for the client side cache + # of statements + def sql_key(sql) + "#{schema_search_path}-#{sql}" + end + + # Prepare the statement if it hasn't been prepared, return + # the statement key. + def prepare_statement(sql) + sql_key = sql_key(sql) unless @statements.key? sql_key nextkey = @statements.next_key @connection.prepare nextkey, sql @statements[sql_key] = nextkey end - - key = @statements[sql_key] - - # 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 + @statements[sql_key] end # The internal PostgreSQL identifier of the money data type. -- cgit v1.2.3 From ee9d9fb5fab3cfaa5055e5fb4225b720d3818c94 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 20 Oct 2011 08:44:55 -0700 Subject: Merge pull request #3258 from ileitch/3-1-stable Postgres: Do not attempt to deallocate a statement if the connection is no longer active. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b7f346e050..3d084bb178 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -289,7 +289,13 @@ module ActiveRecord end def dealloc(key) - @connection.query "DEALLOCATE #{key}" + @connection.query "DEALLOCATE #{key}" if connection_active? + end + + def connection_active? + @connection.status == PGconn::CONNECTION_OK + rescue PGError + false end end -- cgit v1.2.3 From d64e0955d04a40355e312ca4ee57b2c9a8e248cc Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 20 Oct 2011 17:13:01 -0400 Subject: Use new migration style in HABTM join table Yes, we're on Rails 3.1 now. --- activerecord/lib/active_record/associations.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 0952ea2829..2449df079a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1424,16 +1424,12 @@ module ActiveRecord # join table with a migration such as this: # # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration - # def self.up + # def change # create_table :developers_projects, :id => false do |t| # t.integer :developer_id # t.integer :project_id # end # end - # - # def self.down - # drop_table :developers_projects - # end # end # # Adds the following methods for retrieval and query: -- cgit v1.2.3 From f0be2cb19274bb6029f84ca274a72c4d7fbee2d3 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Thu, 20 Oct 2011 17:15:28 -0400 Subject: Add some note on adding index to HABTM table --- activerecord/lib/active_record/associations.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2449df079a..34684ad2f5 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1432,6 +1432,10 @@ module ActiveRecord # end # end # + # It's also a good idea to add indexes to each of those columns to speed up the joins process. + # However, in MySQL it is advised to add a compound index for both of the columns as MySQL only + # uses one index per table during the lookup. + # # Adds the following methods for retrieval and query: # # [collection(force_reload = false)] -- cgit v1.2.3 From f092be821db4a2e8f142e8f0b9d08e497ccf2eb2 Mon Sep 17 00:00:00 2001 From: Greg Reinacker Date: Fri, 21 Oct 2011 17:30:39 -0600 Subject: preserve decimal column attributes after migration --- activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 1932a849ee..e0e957a12c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -413,6 +413,8 @@ module ActiveRecord self.limit = options[:limit] if options.include?(:limit) self.default = options[:default] if include_default self.null = options[:null] if options.include?(:null) + self.precision = options[:precision] if options.include?(:precision) + self.scale = options[:scale] if options.include?(:scale) end end end @@ -467,6 +469,7 @@ module ActiveRecord @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, + :precision => column.precision, :scale => column.scale, :null => column.null) end @definition.primary_key(primary_key(from)) if primary_key(from) -- cgit v1.2.3 From 1c1c3fc2c06fc02e67a8272adf2c2d2381e005b4 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Sat, 22 Oct 2011 23:53:52 +0530 Subject: minor fixes in the composed_of doc --- activerecord/lib/active_record/aggregations.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 81ddbba51e..5a8addc4e4 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -172,8 +172,8 @@ module ActiveRecord # with this option. # * :mapping - Specifies the mapping of entity attributes to attributes of the value # object. Each mapping is represented as an array where the first item is the name of the - # entity attribute and the second item is the name the attribute in the value object. The - # order in which mappings are defined determine the order in which attributes are sent to the + # entity attribute and the second item is the name of the attribute in the value object. The + # order in which mappings are defined determines the order in which attributes are sent to the # value class constructor. # * :allow_nil - Specifies that the value object will not be instantiated when all mapped # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all @@ -191,7 +191,8 @@ module ActiveRecord # # Option examples: # composed_of :temperature, :mapping => %w(reading celsius) - # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), :converter => Proc.new { |balance| balance.to_money } + # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount), + # :converter => Proc.new { |balance| balance.to_money } # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] # composed_of :gps_location # composed_of :gps_location, :allow_nil => true -- cgit v1.2.3 From 9dd168c40262e850885fcb3dd0028b9067bb627f Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 24 Oct 2011 14:46:28 +0200 Subject: minor revision to some new code in databases.rake --- activerecord/lib/active_record/railties/databases.rake | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 705ffe9dd7..f8fa65ea87 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -548,8 +548,9 @@ def drop_database_and_rescue(config) end def configs_for_environment - environments = [Rails.env, ("test" if Rails.env.development?)] - ActiveRecord::Base.configurations.values_at(*environments).compact + environments = [Rails.env] + environments << 'test' if Rails.env.development? + ActiveRecord::Base.configurations.values_at(*environments) end def session_table_name -- cgit v1.2.3 From 80bcfb00f71dbacd6af0a76429faa5d104ae3a36 Mon Sep 17 00:00:00 2001 From: Joost Baaij Date: Tue, 25 Oct 2011 13:57:24 +0300 Subject: Added a note that optimistic locking also needs a hidden field to function across web workers. --- activerecord/lib/active_record/locking/optimistic.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 2df3309648..1a29ded787 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -37,6 +37,9 @@ module ActiveRecord # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging, # or otherwise apply the business logic needed to resolve the conflict. # + # This locking mechanism will function inside a single Ruby process. To make it work across all + # web requests, the recommended approach is to add +lock_version+ as a hidden field to your form. + # # You must ensure that your database schema defaults the +lock_version+ column to 0. # # This behavior can be turned off by setting ActiveRecord::Base.lock_optimistically = false. -- cgit v1.2.3 From ddce29bfa12462fde2342a0c2bd0eefd420c0eab Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Wed, 26 Oct 2011 00:41:41 +0530 Subject: safeguard against configs missing environment or the database key --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f8fa65ea87..44848b3391 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -550,7 +550,7 @@ end def configs_for_environment environments = [Rails.env] environments << 'test' if Rails.env.development? - ActiveRecord::Base.configurations.values_at(*environments) + ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? } end def session_table_name -- cgit v1.2.3 From 5daf07704ad21d885661216281ffc48b6ea6adfb Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Tue, 25 Oct 2011 17:22:52 -0500 Subject: Fix that changing a store should mark the store attribute as changed --- activerecord/lib/active_record/store.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index d5910df891..8cc84f81d0 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -37,6 +37,7 @@ module ActiveRecord Array(keys).flatten.each do |key| define_method("#{key}=") do |value| send(store_attribute)[key] = value + send("#{store_attribute}_will_change!") end define_method(key) do -- cgit v1.2.3 From 0d0176c4ffe5c58fd1002efbd8f7bd45a8872e33 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Wed, 26 Oct 2011 18:20:12 +0100 Subject: Allow instances to disable record_timestamps --- activerecord/lib/active_record/timestamp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 4d5e469a7f..0c760e9850 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -33,7 +33,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - class_attribute :record_timestamps, :instance_writer => false + class_attribute :record_timestamps self.record_timestamps = true end -- cgit v1.2.3 From 4a065d56edcafacc99196522ea35fe28c5f45937 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 27 Oct 2011 10:55:52 -0700 Subject: clear statement cache when tables are altered --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 3d084bb178..e8a43e7bce 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -922,12 +922,14 @@ module ActiveRecord # Example: # rename_table('octopuses', 'octopi') def rename_table(name, new_name) + clear_cache! execute "ALTER TABLE #{quote_table_name(name)} RENAME TO #{quote_table_name(new_name)}" end # Adds a new column to the named table. # See TableDefinition#column for details of the options you can use. def add_column(table_name, column_name, type, options = {}) + clear_cache! add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" add_column_options!(add_column_sql, options) @@ -936,6 +938,7 @@ module ActiveRecord # Changes the column of a table. def change_column(table_name, column_name, type, options = {}) + clear_cache! quoted_table_name = quote_table_name(table_name) execute "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(column_name)} TYPE #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}" @@ -946,10 +949,12 @@ module ActiveRecord # Changes the default value of a table column. def change_column_default(table_name, column_name, default) + clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}" end def change_column_null(table_name, column_name, null, default = nil) + clear_cache! unless null || default.nil? execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end @@ -958,6 +963,7 @@ module ActiveRecord # Renames a column in a table. def rename_column(table_name, column_name, new_column_name) + clear_cache! execute "ALTER TABLE #{quote_table_name(table_name)} RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}" end -- cgit v1.2.3 From ff9c2799c1a065fb132df64da6d19683c647b5b4 Mon Sep 17 00:00:00 2001 From: Laust Rud Jacobsen Date: Tue, 1 Nov 2011 07:29:48 +0100 Subject: dump_schema_information: explicitly order inserts into schema_migrations table This change reduces churn in the db/development_structure.sql file when using :sql as active_record.schema_format, and makes comparing diffs much easier. Test ensures the output SQL-statements are lexically ordered by version. --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') 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 b4a9e29ef1..910ef3efce 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -405,7 +405,7 @@ module ActiveRecord def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name - migrated = select_values("SELECT version FROM #{sm_table}") + migrated = select_values("SELECT version FROM #{sm_table} ORDER BY version") migrated.map { |v| "INSERT INTO #{sm_table} (version) VALUES ('#{v}');" }.join("\n\n") end -- cgit v1.2.3 From b4b178f7e9a00a0235574a773cdbc06fe856acaf Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 3 Nov 2011 10:23:42 +0000 Subject: Fix #3247. Fixes creating records in a through association with a polymorphic source type. --- activerecord/lib/active_record/associations/through_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index b347a94978..f95e5337c2 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -44,7 +44,7 @@ module ActiveRecord join_attributes = { source_reflection.foreign_key => records.map { |record| - record.send(source_reflection.association_primary_key) + record.send(source_reflection.association_primary_key(reflection.klass)) } } -- cgit v1.2.3 From 71bc921ec8ac89840077bb54752282a3d89429f6 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 3 Nov 2011 12:37:37 +0000 Subject: Fix adding multiple instances of the same record to a has_many :through. Fixes #3425. --- .../associations/has_many_through_association.rb | 64 +++++++++++++++------- 1 file changed, 43 insertions(+), 21 deletions(-) (limited to 'activerecord/lib') 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 2e818dca5d..d82d041b69 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -6,6 +6,11 @@ module ActiveRecord class HasManyThroughAssociation < HasManyAssociation #:nodoc: include ThroughAssociation + def initialize(owner, reflection) + super + @through_records = {} + end + # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been # loaded and calling collection.size if it has. If it's more likely than not that the collection does # have a size larger than zero, and you need to fetch that collection afterwards, it'll take one fewer @@ -42,27 +47,42 @@ module ActiveRecord end end - through_record(record).save! + save_through_record(record) update_counter(1) record end private - def through_record(record) - through_association = owner.association(through_reflection.name) - attributes = construct_join_attributes(record) - - through_record = Array.wrap(through_association.target).find { |candidate| - candidate.attributes.slice(*attributes.keys) == attributes - } + def through_association + owner.association(through_reflection.name) + end - unless through_record - through_record = through_association.build(attributes) + # We temporarily cache through record that has been build, because if we build a + # through record in build_record and then subsequently call insert_record, then we + # want to use the exact same object. + # + # However, after insert_record has been called, we clear the cache entry because + # we want it to be possible to have multiple instances of the same record in an + # association + def build_through_record(record) + @through_records[record.object_id] ||= begin + through_record = through_association.build(construct_join_attributes(record)) through_record.send("#{source_reflection.name}=", record) + through_record end + end - through_record + def save_through_record(record) + build_through_record(record).save! + ensure + @through_records.delete(record.object_id) + end + + def through_record(record) + attributes = construct_join_attributes(record) + candidates = Array.wrap(through_association.target) + candidates.find { |c| c.attributes.slice(*attributes.keys) == attributes } end def build_record(attributes, options = {}) @@ -73,9 +93,9 @@ module ActiveRecord inverse = source_reflection.inverse_of if inverse if inverse.macro == :has_many - record.send(inverse.name) << through_record(record) + record.send(inverse.name) << build_through_record(record) elsif inverse.macro == :has_one - record.send("#{inverse.name}=", through_record(record)) + record.send("#{inverse.name}=", build_through_record(record)) end end @@ -104,7 +124,7 @@ module ActiveRecord def delete_records(records, method) ensure_not_nested - through = owner.association(through_reflection.name) + through = through_association scope = through.scoped.where(construct_join_attributes(*records)) case method @@ -126,14 +146,16 @@ module ActiveRecord end def delete_through_records(through, records) - if through_reflection.macro == :has_many - records.each do |record| - through.target.delete(through_record(record)) - end - else - records.each do |record| - through.target = nil if through.target == through_record(record) + records.each do |record| + through_record = through_record(record) + + if through_reflection.macro == :has_many + through.target.delete(through_record) + else + through.target = nil if through.target == through_record end + + @through_records.delete(record.object_id) end end -- cgit v1.2.3 From 19b2a5f2bdd5bf6404bfc3e574b7477038e9b2bf Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 3 Nov 2011 13:12:04 +0000 Subject: Remove all revelant through records. If a record is removed from a has_many :through, all of the join records relating to that record should also be removed from the through association's target. (Previously the records were removed in the database, but only one was removed from the in-memory target array.) --- .../associations/has_many_through_association.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'activerecord/lib') 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 d82d041b69..6ba405ba4c 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -79,12 +79,6 @@ module ActiveRecord @through_records.delete(record.object_id) end - def through_record(record) - attributes = construct_join_attributes(record) - candidates = Array.wrap(through_association.target) - candidates.find { |c| c.attributes.slice(*attributes.keys) == attributes } - end - def build_record(attributes, options = {}) ensure_not_nested @@ -145,14 +139,20 @@ module ActiveRecord update_counter(-count) end + def through_records_for(record) + attributes = construct_join_attributes(record) + candidates = Array.wrap(through_association.target) + candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes } + end + def delete_through_records(through, records) records.each do |record| - through_record = through_record(record) + through_records = through_records_for(record) if through_reflection.macro == :has_many - through.target.delete(through_record) + through_records.each { |r| through.target.delete(r) } else - through.target = nil if through.target == through_record + through.target = nil if through_records.include?(through.target) end @through_records.delete(record.object_id) -- cgit v1.2.3 From 567d454d99bd472ef342c047c0038504fa4f960c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 3 Nov 2011 13:24:39 +0000 Subject: Memoize through association --- .../associations/has_many_through_association.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') 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 6ba405ba4c..7e6e3be382 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -8,7 +8,9 @@ module ActiveRecord def initialize(owner, reflection) super - @through_records = {} + + @through_records = {} + @through_association = nil end # Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been @@ -55,7 +57,7 @@ module ActiveRecord private def through_association - owner.association(through_reflection.name) + @through_association ||= owner.association(through_reflection.name) end # We temporarily cache through record that has been build, because if we build a @@ -118,8 +120,7 @@ module ActiveRecord def delete_records(records, method) ensure_not_nested - through = through_association - scope = through.scoped.where(construct_join_attributes(*records)) + scope = through_association.scoped.where(construct_join_attributes(*records)) case method when :destroy @@ -130,7 +131,7 @@ module ActiveRecord count = scope.delete_all end - delete_through_records(through, records) + delete_through_records(records) if through_reflection.macro == :has_many && update_through_counter?(method) update_counter(-count, through_reflection) @@ -145,14 +146,16 @@ module ActiveRecord candidates.find_all { |c| c.attributes.slice(*attributes.keys) == attributes } end - def delete_through_records(through, records) + def delete_through_records(records) records.each do |record| through_records = through_records_for(record) if through_reflection.macro == :has_many - through_records.each { |r| through.target.delete(r) } + through_records.each { |r| through_association.target.delete(r) } else - through.target = nil if through_records.include?(through.target) + if through_records.include?(through_association.target) + through_association.target = nil + end end @through_records.delete(record.object_id) -- cgit v1.2.3 From d486103570bf680307ba474915efc01862e99403 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 3 Nov 2011 15:07:24 +0000 Subject: Fix #3271. Building the conditions of a nested through association could potentially modify the conditions of the through and/or source association. This is a Bad Thing. --- activerecord/lib/active_record/reflection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 5285060288..98f0418d3f 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -433,7 +433,7 @@ module ActiveRecord # of relevant reflections, plus any :source_type or polymorphic :as constraints. def conditions @conditions ||= begin - conditions = source_reflection.conditions + conditions = source_reflection.conditions.map { |c| c.dup } # Add to it the conditions from this reflection if necessary. conditions.first << options[:conditions] if options[:conditions] -- cgit v1.2.3 From 533a9f84b035756eedf9fdccf0c494dc9701ba72 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Thu, 3 Nov 2011 21:14:51 -0700 Subject: Merge pull request #3507 from jmazzi/issue-3503 Preserve SELECT columns on the COUNT for finder_sql when possible --- .../lib/active_record/associations/collection_association.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index cec876149c..362f1053cd 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -344,8 +344,12 @@ module ActiveRecord if options[:counter_sql] interpolate(options[:counter_sql]) else - # replace the SELECT clause with COUNT(*), preserving any hints within /* ... */ - interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" } + # replace the SELECT clause with COUNT(SELECTS), preserving any hints within /* ... */ + interpolate(options[:finder_sql]).sub(/SELECT\b(\/\*.*?\*\/ )?(.*)\bFROM\b/im) do + count_with = $2.to_s + count_with = '*' if count_with.blank? || count_with =~ /,/ + "SELECT #{$1}COUNT(#{count_with}) FROM" + end end end -- cgit v1.2.3 From e6cc2ea201857a5f3700f589336df2880564cfea Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 4 Nov 2011 11:16:55 +0000 Subject: Allow the :class_name option for associations to take a symbol. This is to avoid confusing newbies, and to be consistent with the fact that other options like :foreign_key already allow a symbol or a string. --- activerecord/lib/active_record/reflection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 98f0418d3f..52968070cb 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -124,7 +124,7 @@ module ActiveRecord # composed_of :balance, :class_name => 'Money' returns 'Money' # has_many :clients returns 'Client' def class_name - @class_name ||= options[:class_name] || derive_class_name + @class_name ||= (options[:class_name] || derive_class_name).to_s end # Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute, -- cgit v1.2.3 From 69dcd45aea770ca90bb3e1f8ce4e944dfb37e766 Mon Sep 17 00:00:00 2001 From: Vlad Jebelev Date: Fri, 4 Nov 2011 15:45:24 -0500 Subject: AR changes to support creating ordered (asc, desc) indexes --- .../abstract/schema_definitions.rb | 2 +- .../abstract/schema_statements.rb | 30 +++++++++++++++++- .../connection_adapters/abstract_adapter.rb | 5 +++ .../connection_adapters/abstract_mysql_adapter.rb | 36 ++++++++++++++++------ .../connection_adapters/postgresql_adapter.rb | 16 ++++++++-- .../connection_adapters/sqlite_adapter.rb | 4 +++ activerecord/lib/active_record/migration.rb | 5 +-- activerecord/lib/active_record/schema_dumper.rb | 3 ++ 8 files changed, 85 insertions(+), 16 deletions(-) (limited to 'activerecord/lib') 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 989a4fcbca..3f69f75565 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -6,7 +6,7 @@ require 'bigdecimal/util' module ActiveRecord module ConnectionAdapters #:nodoc: - class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc: + class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders) #:nodoc: end # Abstract representation of a column definition. Instances of this type 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 7226069ebf..0e5e33fa02 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -339,6 +339,14 @@ module ActiveRecord # CREATE INDEX by_name_surname ON accounts(name(10), surname(15)) # # Note: SQLite doesn't support index length + # + # ====== Creating an index with a sort order (desc or asc, asc is the default) + # add_index(:accounts, [:branch_id, :party_id, :surname], :order => {:branch_id => :desc, :part_id => :asc}) + # generates + # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname) + # + # Note: mysql doesn't yet support index order (it accepts the syntax but ignores it) + # def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})" @@ -520,9 +528,29 @@ module ActiveRecord end protected + def add_index_sort_order(option_strings, column_names, options = {}) + if options.is_a?(Hash) && order = options[:order] + case order + when Hash + column_names.each {|name| option_strings[name] += " #{order[name].to_s.upcase}" if order.has_key?(name)} + when String + column_names.each {|name| option_strings[name] += " #{order.upcase}"} + end + end + + return option_strings + end + # Overridden by the mysql adapter for supporting index lengths def quoted_columns_for_index(column_names, options = {}) - column_names.map {|name| quote_column_name(name) } + option_strings = Hash[column_names.map {|name| [name, '']}] + + # add index sort order if supported + if supports_index_sort_order? + option_strings = add_index_sort_order(option_strings, column_names, options) + end + + column_names.map {|name| quote_column_name(name) + option_strings[name]} end def options_include_default?(options) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 4c3a8f7233..c47bcfc406 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -130,6 +130,11 @@ module ActiveRecord false end + # Does this adapter support index sort order? + def supports_index_sort_order? + false + end + # QUOTING ================================================== # Override to return the quoted table name. Defaults to column quoting. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index dd573ba569..35c2118190 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -155,6 +155,12 @@ module ActiveRecord true end + # Technically MySQL allows to create indexes with the sort order syntax + # but at the moment (5.5) it doesn't yet implement them + def supports_index_sort_order? + true + end + def native_database_types NATIVE_DATABASE_TYPES end @@ -526,17 +532,29 @@ module ActiveRecord protected + def add_index_length(option_strings, column_names, options = {}) + if options.is_a?(Hash) && length = options[:length] + case length + when Hash + column_names.each {|name| option_strings[name] += "(#{length[name]})" if length.has_key?(name)} + when Fixnum + column_names.each {|name| option_strings[name] += "(#{length})"} + end + end + + return option_strings + end + def quoted_columns_for_index(column_names, options = {}) - length = options[:length] if options.is_a?(Hash) + option_strings = Hash[column_names.map {|name| [name, '']}] - case length - when Hash - column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) } - when Fixnum - column_names.map {|name| "#{quote_column_name(name)}(#{length})"} - else - column_names.map {|name| quote_column_name(name) } - end + # add index length + option_strings = add_index_length(option_strings, column_names, options) + + # add index sort order + option_strings = add_index_sort_order(option_strings, column_names, options) + + column_names.map {|name| quote_column_name(name) + option_strings[name]} end def translate_exception(exception, message) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index e8a43e7bce..15e329a1c8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -247,6 +247,10 @@ module ActiveRecord true end + def supports_index_sort_order? + true + end + class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) super @@ -756,7 +760,7 @@ module ActiveRecord def indexes(table_name, name = nil) schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',') result = query(<<-SQL, name) - SELECT distinct i.relname, d.indisunique, d.indkey, t.oid + SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid @@ -772,7 +776,8 @@ module ActiveRecord index_name = row[0] unique = row[1] == 't' indkey = row[2].split(" ") - oid = row[3] + inddef = row[3] + oid = row[4] columns = Hash[query(<<-SQL, "Columns for index #{row[0]} on #{table_name}")] SELECT a.attnum, a.attname @@ -782,7 +787,12 @@ module ActiveRecord SQL column_names = columns.values_at(*indkey).compact - column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names) + + # add info on sort order for columns (only desc order is explicitly specified, asc is the default) + desc_order_columns = inddef.scan(/(\w+) DESC/).flatten + orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {} + + column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders) end.compact end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index f74f3e6ec8..caecbc9b3a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -157,6 +157,10 @@ module ActiveRecord sqlite_version >= '3.1.0' end + def supports_index_sort_order? + sqlite_version >= '3.3.0' + end + def native_database_types #:nodoc: { :primary_key => default_primary_key_type, diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 7166f1b82a..2fb1b8f7a3 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -116,8 +116,9 @@ module ActiveRecord # +column_name+ from the table called +table_name+. # * add_index(table_name, column_names, options): Adds a new index # with the name of the column. Other options include - # :name and :unique (e.g. - # { :name => "users_name_index", :unique => true }). + # :name, :unique (e.g. + # { :name => "users_name_index", :unique => true }) and :order + # (e.g. { :order => {:name => :desc} }). # * remove_index(table_name, :column => column_name): Removes the index # specified by +column_name+. # * remove_index(table_name, :name => index_name): Removes the index diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 6fe305f843..cdde5cf3b9 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -190,6 +190,9 @@ HEADER index_lengths = (index.lengths || []).compact statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty? + index_orders = (index.orders || {}) + statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty? + ' ' + statement_parts.join(', ') end -- cgit v1.2.3 From 15fb4302b6ff16e641b6279a3530eb8ed97f2899 Mon Sep 17 00:00:00 2001 From: Alex Tambellini Date: Thu, 8 Sep 2011 16:07:04 -0400 Subject: schema_format :sql should behave like schema_format :ruby This commit adds a db:structure:load task that is run instead of db:schema:load when schema_format is set to :sql. This patch also removes the prefixing of the structure.sql files to mimic the use of a single schema.rb file. The patch originates from github issue #715. --- .../lib/active_record/railties/databases.rake | 104 +++++++++++++-------- 1 file changed, 66 insertions(+), 38 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 44848b3391..aea928b443 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -151,6 +151,7 @@ db_namespace = namespace :db do ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace["structure:dump"].invoke if ActiveRecord::Base.schema_format == :sql end namespace :migrate do @@ -174,6 +175,7 @@ db_namespace = namespace :db do 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql end # desc 'Runs the "down" for a given migration VERSION.' @@ -182,6 +184,7 @@ db_namespace = namespace :db do 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql end desc 'Display status of migrations' @@ -222,6 +225,7 @@ db_namespace = namespace :db 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' @@ -229,10 +233,14 @@ db_namespace = namespace :db 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql end # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' - task :reset => [ 'db:drop', 'db:setup' ] + task :reset => :environment do + db_namespace["drop"].invoke + db_namespace["setup"].invoke + end # desc "Retrieves the charset for the current environment's database" task :charset => :environment do @@ -285,7 +293,12 @@ db_namespace = namespace :db do end desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)' - task :setup => [ 'db:create', 'db:schema:load', 'db:seed' ] + task :setup => :environment do + db_namespace["create"].invoke + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace["seed"].invoke + end desc 'Load the seed data from db/seeds.rb' task :seed => 'db:abort_if_pending_migrations' do @@ -360,81 +373,96 @@ db_namespace = namespace :db do 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 } + File.open("#{Rails.root}/db/structure.sql", "w:utf-8") { |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['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'] + ENV['PGUSER'] = abcs[Rails.env]['username'].to_s if abcs[Rails.env]['username'] search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.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']}` + `pg_dump -i -s -x -O -f db/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` + `sqlite3 #{dbfile} .schema > db/structure.sql` when 'sqlserver' - `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\#{Rails.env}_structure.sql -A -U` + `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\structure.sql -A -U` when "firebird" set_firebird_env(abcs[Rails.env]) db_string = firebird_db_string(abcs[Rails.env]) - sh "isql -a #{db_string} > #{Rails.root}/db/#{Rails.env}_structure.sql" + sh "isql -a #{db_string} > #{Rails.root}/db/structure.sql" else raise "Task not supported by '#{abcs[Rails.env]["adapter"]}'" end if ActiveRecord::Base.connection.supports_migrations? - File.open("#{Rails.root}/db/#{Rails.env}_structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } + File.open("#{Rails.root}/db/structure.sql", "a") { |f| f << ActiveRecord::Base.connection.dump_schema_information } end end - end - - namespace :test do - # desc "Recreate the test database from the current schema.rb" - task :load => 'db:test:purge' do - ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) - ActiveRecord::Schema.verbose = false - 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 databases from the structure.sql file" + task :load => [:environment, :load_config] do + env = ENV['RAILS_ENV'] || 'test' - # desc "Recreate the test databases from the development structure" - task :clone_structure => [ 'db:structure:dump', 'db:test:purge' ] do abcs = ActiveRecord::Base.configurations - case abcs['test']['adapter'] + case abcs[env]['adapter'] when /mysql/ - ActiveRecord::Base.establish_connection(:test) + ActiveRecord::Base.establish_connection(abcs[env]) 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| + IO.readlines("#{Rails.root}/db/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']}` + ENV['PGHOST'] = abcs[env]['host'] if abcs[env]['host'] + ENV['PGPORT'] = abcs[env]['port'].to_s if abcs[env]['port'] + ENV['PGPASSWORD'] = abcs[env]['password'].to_s if abcs[env]['password'] + ENV['PGUSER'] = abcs[env]['username'].to_s if abcs[env]['username'] + `psql -f "#{Rails.root}/db/structure.sql" #{abcs[env]['database']} #{abcs[env]['template']}` when /sqlite/ - dbfile = abcs['test']['database'] || abcs['test']['dbfile'] - `sqlite3 #{dbfile} < "#{Rails.root}/db/#{Rails.env}_structure.sql"` + dbfile = abcs[env]['database'] || abcs[env]['dbfile'] + `sqlite3 #{dbfile} < "#{Rails.root}/db/structure.sql"` when 'sqlserver' - `sqlcmd -S #{abcs['test']['host']} -d #{abcs['test']['database']} -U #{abcs['test']['username']} -P #{abcs['test']['password']} -i db\\#{Rails.env}_structure.sql` + `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i db\\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.establish_connection(abcs[env]) + IO.readlines("#{Rails.root}/db/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']) - sh "isql -i #{Rails.root}/db/#{Rails.env}_structure.sql #{db_string}" + set_firebird_env(abcs[env]) + db_string = firebird_db_string(abcs[env]) + sh "isql -i #{Rails.root}/db/structure.sql #{db_string}" else - raise "Task not supported by '#{abcs['test']['adapter']}'" + raise "Task not supported by '#{abcs[env]['adapter']}'" + end + end + end + + namespace :test do + # desc "Recreate the test database from the current schema.rb" + task :load => 'db:test:purge' do + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test']) + ActiveRecord::Schema.verbose = false + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + + begin + old_env, ENV['RAILS_ENV'] = ENV['RAILS_ENV'], 'test' + db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql + ensure + ENV['RAILS_ENV'] = old_env end + 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 structure.sql file" + task :clone_structure => [ "db:structure:dump", "db:test:load" ] + # desc "Empty the test database" task :purge => :environment do abcs = ActiveRecord::Base.configurations -- cgit v1.2.3 From 562583c7667f508493ab8c5b1a4215087fafd22d Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 4 Nov 2011 16:10:18 +0000 Subject: Add ActiveRecord::Relation#uniq for toggling DISTINCT in the SQL query --- activerecord/lib/active_record/base.rb | 4 ++- activerecord/lib/active_record/relation.rb | 2 +- .../lib/active_record/relation/query_methods.rb | 30 +++++++++++++++++----- 3 files changed, 28 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 360e494af1..455b299270 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -445,7 +445,9 @@ module ActiveRecord #:nodoc: delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :scoped delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :scoped delegate :find_each, :find_in_batches, :to => :scoped - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, + :where, :preload, :eager_load, :includes, :from, :lock, :readonly, + :having, :create_with, :uniq, :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 diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ecefaa633c..3baf9b3f49 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -7,7 +7,7 @@ module ActiveRecord JoinOperation = Struct.new(:relation, :join_class, :on) ASSOCIATION_METHODS = [:includes, :eager_load, :preload] MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order] + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reorder, :reverse_order, :uniq] include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 670ba0987d..c281bead0d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -9,7 +9,8 @@ module ActiveRecord :select_values, :group_values, :order_values, :joins_values, :where_values, :having_values, :bind_values, :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value, - :from_value, :reorder_value, :reverse_order_value + :from_value, :reorder_value, :reverse_order_value, + :uniq_value def includes(*args) args.reject! {|a| a.blank? } @@ -38,7 +39,7 @@ module ActiveRecord end # Works in two unique ways. - # + # # First: takes a block so it can be used just like Array#select. # # Model.scoped.select { |m| m.field == value } @@ -176,9 +177,25 @@ module ActiveRecord relation end + # Specifies whether the records should be unique or not. For example: + # + # User.select(:name) + # # => Might return two records with the same name + # + # User.select(:name).uniq + # # => Returns 1 record per unique name + # + # User.select(:name).uniq.uniq(false) + # # => You can also remove the uniqueness + def uniq(value = true) + relation = clone + relation.uniq_value = value + relation + end + # Used to extend a scope with additional methods, either through - # a module or through a block provided. - # + # a module or through a block provided. + # # The object returned is a relation, which can be further extended. # # === Using a module @@ -200,7 +217,7 @@ module ActiveRecord # # scope = Model.scoped.extending do # def page(number) - # # pagination code goes here + # # pagination code goes here # end # end # scope.page(params[:page]) @@ -209,7 +226,7 @@ module ActiveRecord # # scope = Model.scoped.extending(Pagination) do # def per_page(number) - # # pagination code goes here + # # pagination code goes here # end # end def extending(*modules) @@ -252,6 +269,7 @@ module ActiveRecord build_select(arel, @select_values.uniq) + arel.distinct(@uniq_value) arel.from(@from_value) if @from_value arel.lock(@lock_value) if @lock_value -- cgit v1.2.3 From 9a111c7cf94d9e4614a0687345084cfe60a69187 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Fri, 4 Nov 2011 16:34:42 +0000 Subject: Use uniq instead of manually putting a DISTINCT in the query --- .../lib/active_record/associations/association_scope.rb | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 6cc401e6cc..6f8b76abda 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -20,31 +20,19 @@ module ActiveRecord # It's okay to just apply all these like this. The options will only be present if the # association supports that option; this is enforced by the association builder. scope = scope.apply_finder_options(options.slice( - :readonly, :include, :order, :limit, :joins, :group, :having, :offset)) + :readonly, :include, :order, :limit, :joins, :group, :having, :offset, :select)) if options[:through] && !options[:include] scope = scope.includes(source_options[:include]) end - if select = select_value - scope = scope.select(select) - end + scope = scope.uniq if options[:uniq] add_constraints(scope) end private - def select_value - select_value = options[:select] - - if reflection.collection? - select_value ||= options[:uniq] && "DISTINCT #{reflection.quoted_table_name}.*" - end - - select_value - end - def add_constraints(scope) tables = construct_tables -- cgit v1.2.3 From fb0bf3c984fb088b5e436b25e6e5ae042e99c523 Mon Sep 17 00:00:00 2001 From: kennyj Date: Mon, 31 Oct 2011 22:53:47 +0900 Subject: Fixed an issue id false option is ignored on mysql/mysql2 (fix #3440) --- .../connection_adapters/abstract_mysql_adapter.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index dd573ba569..e2075225f0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -496,8 +496,17 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) - execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result| - keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] } + sql = <<-SQL + SELECT t.constraint_type, k.column_name + FROM information_schema.table_constraints t + JOIN information_schema.key_column_usage k + USING (constraint_name, table_schema, table_name) + WHERE t.table_schema = DATABASE() + AND t.table_name = '#{table}' + SQL + + execute_and_free(sql, 'SCHEMA') do |result| + keys = each_hash(result).select { |row| row[:constraint_type] == 'PRIMARY KEY' }.map { |row| row[:column_name] } keys.length == 1 ? [keys.first, nil] : nil end end -- cgit v1.2.3 From e7b7b4412380e7ce2d8e6ae402cb7fe02d7666b8 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Tue, 20 Sep 2011 10:50:08 -0700 Subject: implements AR::Relation#explain This is a first implementation, EXPLAIN is highly dependent on the database and I have made some compromises. On one hand, the method allows you to run the most common EXPLAIN and that's it. If you want EXPLAIN ANALYZE in PostgreSQL you need to do it by hand. On the other hand, I've tried to construct a string as close as possible to the ones built by the respective shells. The rationale is that IMO the user should feel at home with the output and recognize it at first sight. Per database. I don't know whether this implementation is going to work well. Let's see whether people like it. --- .../connection_adapters/abstract_mysql_adapter.rb | 75 ++++++++++++++++++++++ .../connection_adapters/postgresql_adapter.rb | 44 ++++++++++++- .../connection_adapters/sqlite_adapter.rb | 19 ++++++ activerecord/lib/active_record/relation.rb | 16 +++++ 4 files changed, 153 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 306b185c5e..1146323147 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -228,6 +228,80 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== + def explain(arel) + sql = "EXPLAIN #{to_sql(arel)}" + start = Time.now + result = exec_query(sql, 'EXPLAIN') + elapsed = Time.now - start + + ExplainPrettyPrinter.new.pp(result, elapsed) + end + + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of a EXPLAIN in a way that resembles the output of the + # MySQL shell: + # + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | + # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # 2 rows in set (0.00 sec) + # + # This is an exercise in Ruby hyperrealism :). + def pp(result, elapsed) + widths = compute_column_widths(result) + separator = build_separator(widths) + + pp = [] + + pp << separator + pp << build_cells(result.columns, widths) + pp << separator + + result.rows.each do |row| + pp << build_cells(row, widths) + end + + pp << separator + pp << build_footer(result.rows.length, elapsed) + + pp.join("\n") + "\n" + end + + private + + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} + widths << cells_in_column.map(&:length).max + end + end + end + + def build_separator(widths) + # Each cell has one char of padding at both sides, that's why we add 2. + '+' + widths.map {|w| '-' * (w + 2)}.join('+') + end + + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = 'NULL' if item.nil? + justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' + cells << item.to_s.send(justifier, widths[i]) + end + '| ' + cells.join(' | ') + ' |' + end + + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? 'row' : 'rows' + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end + end + # Executes the SQL statement in the context of this connection. def execute(sql, name = nil) if name == :skip_logging @@ -290,6 +364,7 @@ module ActiveRecord # these, we must use a subquery. However, MySQL is too stupid to create a # temporary table for this automatically, so we have to give it some prompting # in the form of a subsubquery. Ugh! + def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? subsubselect = select.clone diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 15e329a1c8..44c2fa439c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -517,6 +517,48 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== + def explain(arel) + sql = "EXPLAIN #{to_sql(arel)}" + ExplainPrettyPrinter.new.pp(exec_query(sql)) + end + + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of a EXPLAIN in a way that resembles the output of the + # PostgreSQL shell: + # + # QUERY PLAN + # ------------------------------------------------------------------------------ + # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) + # Join Filter: (posts.user_id = users.id) + # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) + # Index Cond: (id = 1) + # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) + # Filter: (posts.user_id = 1) + # (6 rows) + # + def pp(result) + header = result.columns.first + lines = result.rows.map(&:first) + + # We add 2 because there's one char of padding at both sides, note + # the extra hyphens in the example above. + width = [header, *lines].map(&:length).max + 2 + + pp = [] + + pp << header.center(width).rstrip + pp << '-' * width + + pp += lines.map {|line| " #{line}"} + + nrows = result.rows.length + rows_label = nrows == 1 ? 'row' : 'rows' + pp << "(#{nrows} #{rows_label})" + + pp.join("\n") + "\n" + end + end + # Executes a SELECT query and returns an array of rows. Each row is an # array of field values. def select_rows(sql, name = nil) @@ -836,7 +878,7 @@ module ActiveRecord # Returns the active schema search path. def schema_search_path - @schema_search_path ||= query('SHOW search_path')[0][0] + @schema_search_path ||= query('SHOW search_path', 'SCHEMA')[0][0] end # Returns the current client message level. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index caecbc9b3a..35df0a1542 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -222,6 +222,25 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== + def explain(arel) + sql = "EXPLAIN QUERY PLAN #{to_sql(arel)}" + ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN')) + end + + class ExplainPrettyPrinter + # Pretty prints the result of a EXPLAIN QUERY PLAN in a way that resembles + # the output of the SQLite shell: + # + # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) + # 0|1|1|SCAN TABLE posts (~100000 rows) + # + def pp(result) # :nodoc: + result.rows.map do |row| + row.join('|') + end.join("\n") + "\n" + end + end + def exec_query(sql, name = nil, binds = []) log(sql, name, binds) do diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 3baf9b3f49..f0891440a6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -143,6 +143,22 @@ module ActiveRecord super end + def explain + queries = [] + callback = lambda do |*args| + payload = args.last + queries << payload[:sql] unless payload[:exception] || %w(SCHEMA EXPLAIN).include?(payload[:name]) + end + + ActiveSupport::Notifications.subscribed(callback, "sql.active_record") do + to_a + end + + queries.map do |sql| + @klass.connection.explain(sql) + end.join + end + def to_a return @records if loaded? -- cgit v1.2.3 From c7d20785965d5f0877d0c13d0d9929f2fce50a8b Mon Sep 17 00:00:00 2001 From: Justin Mazzi Date: Sat, 5 Nov 2011 22:36:19 -0400 Subject: Update ActiveRecord#attribute_present? to work as documented "Returns true if the specified attribute has been set by the user or by a database load and is neither nil nor empty?" Fixes #1613 --- activerecord/lib/active_record/base.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 455b299270..feeebb7131 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1771,7 +1771,8 @@ MSG # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings). def attribute_present?(attribute) - !_read_attribute(attribute).blank? + value = _read_attribute(attribute) + !value.nil? || (value.respond_to?(:empty?) && !value.empty?) end # Returns the column object for the named attribute. -- cgit v1.2.3 From 9b46613780dcd0aa49abf49877baea274040c0b2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 6 Nov 2011 08:15:34 -0800 Subject: Merge pull request #3521 from nulogy/fix_postgres_adapter_to_handle_spaces_between_schemas Fix postgres adapter to handle spaces between schemas --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 44c2fa439c..b7918c7f07 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -800,7 +800,6 @@ module ActiveRecord # 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) SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid FROM pg_class t @@ -809,7 +808,7 @@ module ActiveRecord WHERE i.relkind = 'i' AND d.indisprimary = 'f' AND t.relname = '#{table_name}' - AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (#{schemas}) ) + AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) ) ORDER BY i.relname SQL -- cgit v1.2.3 From 55b203dac1eb4b3430b313112b8d5a53b1016b4e Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Mon, 7 Nov 2011 11:56:50 +0900 Subject: self.up, self.down => up, down --- activerecord/lib/active_record/base.rb | 4 ++-- .../active_record/connection_adapters/abstract/schema_definitions.rb | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index feeebb7131..3558ae3545 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -760,7 +760,7 @@ module ActiveRecord #:nodoc: # values, eg: # # class CreateJobLevels < ActiveRecord::Migration - # def self.up + # def up # create_table :job_levels do |t| # t.integer :id # t.string :name @@ -774,7 +774,7 @@ module ActiveRecord #:nodoc: # end # end # - # def self.down + # def down # drop_table :job_levels # end # end 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 3f69f75565..6f135b56b5 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -46,13 +46,13 @@ module ActiveRecord # +change_table+ is actually of this type: # # class SomeMigration < ActiveRecord::Migration - # def self.up + # def up # create_table :foo do |t| # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" # end # end # - # def self.down + # def down # ... # end # end @@ -479,4 +479,3 @@ module ActiveRecord end end - -- cgit v1.2.3 From aff9e68cb5996c95bc5c7304d996343ea2382d3b Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Mon, 7 Nov 2011 14:03:33 +0900 Subject: document fix: remove_column takes multiple column_names --- activerecord/lib/active_record/migration.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 2fb1b8f7a3..d70c7d1d34 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -112,8 +112,8 @@ module ActiveRecord # a column but keeps the type and content. # * change_column(table_name, column_name, type, options): Changes # the column to a different type using the same parameters as add_column. - # * remove_column(table_name, column_name): Removes the column named - # +column_name+ from the table called +table_name+. + # * remove_column(table_name, column_names): Removes the column listed in + # +column_names+ from the table called +table_name+. # * add_index(table_name, column_names, options): Adds a new index # with the name of the column. Other options include # :name, :unique (e.g. -- cgit v1.2.3 From 17ecdd388c70f7faf002ef21be6a674b4c0df7ca Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Mon, 7 Nov 2011 02:01:37 -0800 Subject: adds trailing +s to the output of EXPLAIN for MySQL --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 1146323147..baf4c043c4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -282,8 +282,8 @@ module ActiveRecord end def build_separator(widths) - # Each cell has one char of padding at both sides, that's why we add 2. - '+' + widths.map {|w| '-' * (w + 2)}.join('+') + padding = 1 + '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' end def build_cells(items, widths) @@ -364,7 +364,6 @@ module ActiveRecord # these, we must use a subquery. However, MySQL is too stupid to create a # temporary table for this automatically, so we have to give it some prompting # in the form of a subsubquery. Ugh! - def join_to_update(update, select) #:nodoc: if select.limit || select.offset || select.orders.any? subsubselect = select.clone -- cgit v1.2.3 From 74c6e80cabd8e3b7751fc3a32c9ad95c1780054b Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Wed, 9 Nov 2011 04:46:11 +0900 Subject: exclude ORDER BY clause when querying Relation#exists? --- activerecord/lib/active_record/relation/finder_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 7eeb3dde70..3c8e0f2052 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -191,7 +191,7 @@ module ActiveRecord join_dependency = construct_join_dependency_for_association_find relation = construct_relation_for_association_find(join_dependency) - relation = relation.except(:select).select("1").limit(1) + relation = relation.except(:select, :order).select("1").limit(1) case id when Array, Hash -- cgit v1.2.3 From b805c71655ada0e3fcf7ccc1cdf3376e55b2b9ce Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 10 Nov 2011 19:05:37 +0530 Subject: Checking the arity of the block passed to create_table A recent change made to create_table does away with the need for the block argument. Checking the arity will prevent the mixing up of the two syntaxes. --- .../connection_adapters/abstract/schema_statements.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') 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 0e5e33fa02..be9a02682b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -158,7 +158,13 @@ module ActiveRecord td = table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false - td.instance_eval(&blk) if blk + if block_given? + if blk.arity == 1 + yield td + else + td.instance_eval(&blk) + end + end if options[:force] && table_exists?(table_name) drop_table(table_name) -- cgit v1.2.3 From 6e112e42176c6842270f879fd15a0b7b7371ecef Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Fri, 11 Nov 2011 02:47:27 +0530 Subject: Modify change_table to remove the need for the block argument. --- .../connection_adapters/abstract/schema_statements.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') 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 be9a02682b..11da84e245 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -241,14 +241,19 @@ module ActiveRecord # # See also Table for details on # all of the various column transformation - def change_table(table_name, options = {}) - if supports_bulk_alter? && options[:bulk] - recorder = ActiveRecord::Migration::CommandRecorder.new(self) - yield Table.new(table_name, recorder) - bulk_change_table(table_name, recorder.commands) - else - yield Table.new(table_name, self) + def change_table(table_name, options = {}, &blk) + bulk_change = supports_bulk_alter? && options[:bulk] + recorder = bulk_change ? ActiveRecord::Migration::CommandRecorder.new(self) : self + table = Table.new(table_name, recorder) + + if block_given? + if blk.arity == 1 + yield table + else + table.instance_eval(&blk) + end end + bulk_change_table(table_name, recorder.commands) if bulk_change end # Renames a table. -- cgit v1.2.3 From 27c5800c9cf8c19c3deb8bb6ebc1186d0f865d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noe=CC=81=20Froidevaux?= Date: Fri, 11 Nov 2011 12:09:51 +0100 Subject: Prevent multiple SHOW TABLES calls when a table don't exists in database. --- .../active_record/connection_adapters/abstract/connection_pool.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 77a5fe1efb..92dfb844db 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -123,14 +123,14 @@ module ActiveRecord # A cached lookup for table existence. def table_exists?(name) - return true if @tables.key? name + return @tables[name] if @tables.key? name with_connection do |conn| conn.tables.each { |table| @tables[table] = true } - @tables[name] = true if !@tables.key?(name) && conn.table_exists?(name) + @tables[name] = !@tables.key?(name) && conn.table_exists?(name) end - @tables.key? name + @tables[name] end # Clears out internal caches: -- cgit v1.2.3 From bf6efa8d9cd0f7ce2eea7c1a0ff51f1d165cafc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noe=CC=81=20Froidevaux?= Date: Sun, 13 Nov 2011 11:03:22 +0100 Subject: Fix pull request #3609 --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') 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 92dfb844db..0ec0576795 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -127,7 +127,7 @@ module ActiveRecord with_connection do |conn| conn.tables.each { |table| @tables[table] = true } - @tables[name] = !@tables.key?(name) && conn.table_exists?(name) + @tables[name] = conn.table_exists?(name) if !@tables.key?(name) end @tables[name] -- cgit v1.2.3 From 9fa329b7544b15cdf5751d518e380abc82468df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 14 Nov 2011 20:12:17 +0100 Subject: Speed up attribute invocation by checking if both name and calls are compilable. --- activerecord/lib/active_record/attribute_methods/read.rb | 2 +- activerecord/lib/active_record/attribute_methods/write.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 4174e4da09..4a5afcd585 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -77,7 +77,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 =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP + if method_name =~ ActiveModel::AttributeMethods::NAME_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 e9cdb130db..eb585ee906 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 =~ ActiveModel::AttributeMethods::COMPILABLE_REGEXP + if attr_name =~ ActiveModel::AttributeMethods::NAME_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| -- cgit v1.2.3 From 63a22ca6169f06d10cd843ea3a75df6498fdcf7d Mon Sep 17 00:00:00 2001 From: Ryan Naughton Date: Mon, 14 Nov 2011 21:43:27 -0600 Subject: Fixes issue #3483, regarding using a mixture of ranges and discrete values in find conditions. Paired with Joey Schoblaska. --- .../lib/active_record/relation/predicate_builder.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 7e8ddd1b5d..af167dc59b 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -22,21 +22,23 @@ module ActiveRecord value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty? attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy - values = value.to_a.map { |x| - x.is_a?(ActiveRecord::Base) ? x.id : x - } + values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x} + ranges, values = values.partition {|value| value.is_a?(Range) || value.is_a?(Arel::Relation)} + + array_predicates = ranges.map {|range| attribute.in(range)} if values.include?(nil) values = values.compact if values.empty? - attribute.eq nil + array_predicates << attribute.eq(nil) else - attribute.in(values.compact).or attribute.eq(nil) + array_predicates << attribute.in(values.compact).or(attribute.eq(nil)) end else - attribute.in(values) + array_predicates << attribute.in(values) end + array_predicates.inject {|composite, predicate| composite.or(predicate)} when Range, Arel::Relation attribute.in(value) when ActiveRecord::Base -- cgit v1.2.3 From 7cba6a37848ba96b4decec885779fb309d71c339 Mon Sep 17 00:00:00 2001 From: Josh Susser Date: Mon, 14 Nov 2011 22:57:15 -0800 Subject: association methods are now generated in modules Instead of generating association methods directly in the model class, they are generated in an anonymous module which is then included in the model class. There is one such module for each association. The only subtlety is that the generated_attributes_methods module (from ActiveModel) must be forced to be included before association methods are created so that attribute methods will not shadow association methods. --- .../lib/active_record/associations/builder/association.rb | 10 +++++----- .../lib/active_record/associations/builder/belongs_to.rb | 6 +++--- .../associations/builder/collection_association.rb | 4 ++-- .../associations/builder/has_and_belongs_to_many.rb | 12 ++++-------- .../lib/active_record/associations/builder/has_many.rb | 6 +++--- .../lib/active_record/associations/builder/has_one.rb | 11 +++++------ .../associations/builder/singular_association.rb | 6 +++--- activerecord/lib/active_record/attribute_methods.rb | 6 ++++++ 8 files changed, 31 insertions(+), 30 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 96fca97440..686db0725d 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -6,7 +6,7 @@ module ActiveRecord::Associations::Builder # Set by subclasses class_attribute :macro - attr_reader :model, :name, :options, :reflection + attr_reader :model, :name, :options, :reflection, :mixin def self.build(model, name, options) new(model, name, options).build @@ -14,6 +14,8 @@ module ActiveRecord::Associations::Builder def initialize(model, name, options) @model, @name, @options = model, name, options + @mixin = Module.new + @model.__send__(:include, @mixin) end def build @@ -36,16 +38,14 @@ module ActiveRecord::Associations::Builder def define_readers name = self.name - - model.redefine_method(name) do |*params| + mixin.send(:define_method, name) do |*params| association(name).reader(*params) end end def define_writers name = self.name - - model.redefine_method("#{name}=") do |value| + mixin.send(:define_method, "#{name}=") do |value| association(name).writer(value) end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index f6d26840c2..0ca107035f 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -25,14 +25,14 @@ module ActiveRecord::Associations::Builder name = self.name method_name = "belongs_to_counter_cache_after_create_for_#{name}" - model.redefine_method(method_name) do + mixin.send(:define_method, method_name) do record = send(name) record.class.increment_counter(cache_column, record.id) unless record.nil? end model.after_create(method_name) method_name = "belongs_to_counter_cache_before_destroy_for_#{name}" - model.redefine_method(method_name) do + mixin.send(:define_method, method_name) do record = send(name) record.class.decrement_counter(cache_column, record.id) unless record.nil? end @@ -48,7 +48,7 @@ module ActiveRecord::Associations::Builder method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}" touch = options[:touch] - model.redefine_method(method_name) do + mixin.send(:define_method, method_name) do record = send(name) unless record.nil? diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index f62209a226..1805ea2c1e 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder super name = self.name - model.redefine_method("#{name.to_s.singularize}_ids") do + mixin.send(:define_method, "#{name.to_s.singularize}_ids") do association(name).ids_reader end end @@ -67,7 +67,7 @@ module ActiveRecord::Associations::Builder super name = self.name - model.redefine_method("#{name.to_s.singularize}_ids=") do |ids| + mixin.send(:define_method, "#{name.to_s.singularize}_ids=") do |ids| association(name).ids_writer(ids) end end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 30fc44b4c2..f3391eba13 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -15,14 +15,10 @@ module ActiveRecord::Associations::Builder def define_destroy_hook name = self.name - model.send(:include, Module.new { - class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def destroy_associations - association(#{name.to_sym.inspect}).delete_all - super - end - RUBY - }) + mixin.send(:define_method, :destroy_associations) do + association(name).delete_all + super() + end end # TODO: These checks should probably be moved into the Reflection, and we should not be diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index ecbc70888f..8a6f5a87e7 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder def define_destroy_dependency_method name = self.name - model.send(:define_method, dependency_method_name) do + mixin.send(:define_method, dependency_method_name) do send(name).each do |o| # No point in executing the counter update since we're going to destroy the parent anyway counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym @@ -45,7 +45,7 @@ module ActiveRecord::Associations::Builder def define_delete_all_dependency_method name = self.name - model.send(:define_method, dependency_method_name) do + mixin.send(:define_method, dependency_method_name) do send(name).delete_all end end @@ -53,7 +53,7 @@ module ActiveRecord::Associations::Builder def define_restrict_dependency_method name = self.name - model.send(:define_method, dependency_method_name) do + mixin.send(:define_method, dependency_method_name) do raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty? end end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 88c0d3e90f..2cea8b9805 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -44,18 +44,17 @@ module ActiveRecord::Associations::Builder end def define_destroy_dependency_method - model.send(:class_eval, <<-eoruby, __FILE__, __LINE__ + 1) - def #{dependency_method_name} - association(#{name.to_sym.inspect}).delete - end - eoruby + name = self.name + mixin.send(:define_method, dependency_method_name) do + association(name).delete + end end alias :define_delete_dependency_method :define_destroy_dependency_method alias :define_nullify_dependency_method :define_destroy_dependency_method def define_restrict_dependency_method name = self.name - model.redefine_method(dependency_method_name) do + mixin.send(:define_method, dependency_method_name) do raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil? end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 0cbbba041a..020e9157b3 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -16,15 +16,15 @@ module ActiveRecord::Associations::Builder def define_constructors name = self.name - model.redefine_method("build_#{name}") do |*params, &block| + mixin.send(:define_method, "build_#{name}") do |*params, &block| association(name).build(*params, &block) end - model.redefine_method("create_#{name}") do |*params, &block| + mixin.send(:define_method, "create_#{name}") do |*params, &block| association(name).create(*params, &block) end - model.redefine_method("create_#{name}!") do |*params, &block| + mixin.send(:define_method, "create_#{name}!") do |*params, &block| association(name).create!(*params, &block) end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index d7bfaa5655..2d720fe700 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -8,6 +8,12 @@ module ActiveRecord include ActiveModel::AttributeMethods module ClassMethods + def inherited(child_class) + # force creation + include before accessor method modules + child_class.generated_attribute_methods + super + end + # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods -- cgit v1.2.3 From cef1e14e09e5654895f264a2cff1c0898672b1f0 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 14:45:18 -0800 Subject: removing some useless conditionals --- activerecord/lib/active_record/railties/databases.rake | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index aea928b443..f3cb2a971e 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -1,8 +1,8 @@ require 'active_support/core_ext/object/inclusion' +require 'active_record' 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 @@ -279,16 +279,14 @@ db_namespace = namespace :db do # desc "Raises an error if there are pending migrations" task :abort_if_pending_migrations => :environment do - if defined? ActiveRecord - pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations + pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations - if pending_migrations.any? - puts "You have #{pending_migrations.size} pending migrations:" - pending_migrations.each do |pending_migration| - puts ' %4d %s' % [pending_migration.version, pending_migration.name] - end - abort %{Run `rake db:migrate` to update your database then try again.} + if pending_migrations.any? + puts "You have #{pending_migrations.size} pending migrations:" + pending_migrations.each do |pending_migration| + puts ' %4d %s' % [pending_migration.version, pending_migration.name] end + abort %{Run `rake db:migrate` to update your database then try again.} end end @@ -498,7 +496,7 @@ db_namespace = namespace :db do # 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? + unless ActiveRecord::Base.configurations.blank? db_namespace[{ :sql => 'test:clone_structure', :ruby => 'test:load' }[ActiveRecord::Base.schema_format]].invoke end end -- cgit v1.2.3 From 73cb0f98289923c8fa0287bf1cc8857664078d43 Mon Sep 17 00:00:00 2001 From: James Adam Date: Wed, 26 Oct 2011 12:51:38 +0100 Subject: `ActiveRecord::Base#becomes` should retain the errors of the original object. This commit contains a simple failing test that demonstrates the behaviour we expect, and a fix. When using `becomes` to transform the type of an object, it should retain any error information that was present on the original instance. --- activerecord/lib/active_record/persistence.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 5e65e46a7d..f047a1d9fa 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -114,6 +114,7 @@ module ActiveRecord became.instance_variable_set("@attributes_cache", @attributes_cache) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) + became.instance_variable_set("@errors", errors) became.type = klass.name unless self.class.descends_from_active_record? became end -- cgit v1.2.3 From 38d26b0cb56d82093889efa95992a35ba3bb9f29 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 15:01:02 -0800 Subject: Move conditionals to separate tasks so they can be reused. --- activerecord/lib/active_record/railties/databases.rake | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index f3cb2a971e..ee611f6a2a 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -291,15 +291,11 @@ db_namespace = namespace :db do end desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)' - task :setup => :environment do - db_namespace["create"].invoke - db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby - db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql - db_namespace["seed"].invoke - end + task :setup => :seed desc 'Load the seed data from db/seeds.rb' - task :seed => 'db:abort_if_pending_migrations' do + task :seed => ['db:schema:load_if_ruby', 'db:structure:load_if_sql'] do + db_namespace['abort_if_pending_migrations'].invoke Rails.application.load_seed end @@ -362,6 +358,10 @@ db_namespace = namespace :db do abort %{#{file} doesn't exist yet. Run `rake db:migrate` to create it then try again. If you do not intend to use a database, you should instead alter #{Rails.root}/config/application.rb to limit the frameworks that will be loaded} end end + + task :load_if_ruby => 'db:create' do + db_namespace["schema:load"].invoke if ActiveRecord::Base.schema_format == :ruby + end end namespace :structure do @@ -437,6 +437,10 @@ db_namespace = namespace :db do raise "Task not supported by '#{abcs[env]['adapter']}'" end end + + task :load_if_sql => 'db:create' do + db_namespace["structure:load"].invoke if ActiveRecord::Base.schema_format == :sql + end end namespace :test do -- cgit v1.2.3 From ca69408b49156f98a76adfc03088b6b067fabc61 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 15:27:47 -0800 Subject: Reduce schema format tests --- .../lib/active_record/railties/databases.rake | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ee611f6a2a..d19208dd76 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -150,8 +150,16 @@ db_namespace = namespace :db do task :migrate => [:environment, :load_config] do ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) - db_namespace["schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby - db_namespace["structure:dump"].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace['_dump'].invoke + end + + task :_dump do + case ActiveRecord::Base.schema_format + when :ruby then db_namespace["schema:dump"].invoke + when :sql then db_namespace["structure:dump"].invoke + else + raise "unknown schema format #{ActiveRecord::Base.schema_format}" + end end namespace :migrate do @@ -174,8 +182,7 @@ db_namespace = namespace :db do 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace['_dump'].invoke end # desc 'Runs the "down" for a given migration VERSION.' @@ -183,8 +190,7 @@ db_namespace = namespace :db do 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace['_dump'].invoke end desc 'Display status of migrations' @@ -224,16 +230,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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace['_dump'].invoke 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['structure:dump'].invoke if ActiveRecord::Base.schema_format == :sql + db_namespace['_dump'].invoke end # desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.' -- cgit v1.2.3 From b96aaf8ccbd06986dc489a9f29ea4092dd5cf911 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 15:30:00 -0800 Subject: dbfile isn't supported anymore, so remove --- activerecord/lib/active_record/railties/databases.rake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index d19208dd76..ef8f815207 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -388,7 +388,7 @@ db_namespace = namespace :db do `pg_dump -i -s -x -O -f db/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'] + dbfile = abcs[Rails.env]['database'] `sqlite3 #{dbfile} .schema > db/structure.sql` when 'sqlserver' `smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f db\\structure.sql -A -U` @@ -424,7 +424,7 @@ db_namespace = namespace :db do ENV['PGUSER'] = abcs[env]['username'].to_s if abcs[env]['username'] `psql -f "#{Rails.root}/db/structure.sql" #{abcs[env]['database']} #{abcs[env]['template']}` when /sqlite/ - dbfile = abcs[env]['database'] || abcs[env]['dbfile'] + dbfile = abcs[env]['database'] `sqlite3 #{dbfile} < "#{Rails.root}/db/structure.sql"` when 'sqlserver' `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i db\\structure.sql` @@ -481,7 +481,7 @@ db_namespace = namespace :db do drop_database(abcs['test']) create_database(abcs['test']) when /sqlite/ - dbfile = abcs['test']['database'] || abcs['test']['dbfile'] + dbfile = abcs['test']['database'] File.delete(dbfile) if File.exist?(dbfile) when 'sqlserver' test = abcs.deep_dup['test'] -- cgit v1.2.3 From 97ca6358c5fbd5458b684d6d46489b7b68c48225 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 15:40:20 -0800 Subject: Join method uses empty string by default, so remove it --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index baf4c043c4..e26b10fa97 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -389,11 +389,11 @@ module ActiveRecord sql = "SHOW TABLES" end - select_all(sql).map do |table| + select_all(sql).map { |table| table.delete('Table_type') sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}" exec_without_stmt(sql).first['Create Table'] + ";\n\n" - end.join("") + }.join end # Drops the database specified on the +name+ attribute -- cgit v1.2.3 From bb95e815380c85d14afade807ebb2dd227d90b9e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 15:51:25 -0800 Subject: Adding a deprecation warning for use of the schema_info table. --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') 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 11da84e245..7742f32213 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/array/wrap' +require 'active_support/deprecation/reporting' module ActiveRecord module ConnectionAdapters # :nodoc: @@ -445,6 +446,7 @@ module ActiveRecord si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix if table_exists?(si_table) + ActiveRecord::Deprecation.warn "Usage of the schema table `#{si_table}` is deprecated. Please switch to using `schema_migrations` table" old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i assume_migrated_upto_version(old_version) -- cgit v1.2.3 From 4c1a1933cbc5ab96efe340a3b31ac5fee12c99c8 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Wed, 16 Nov 2011 16:18:00 -0800 Subject: No need to `readlines` then `join`, just use `read` :heart: --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ef8f815207..5964fa9cb0 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -414,7 +414,7 @@ db_namespace = namespace :db do when /mysql/ ActiveRecord::Base.establish_connection(abcs[env]) ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.readlines("#{Rails.root}/db/structure.sql").join.split("\n\n").each do |table| + IO.read("#{Rails.root}/db/structure.sql").split("\n\n").each do |table| ActiveRecord::Base.connection.execute(table) end when /postgresql/ -- cgit v1.2.3 From 5ccd9bcc1a3012573e6ab19ac52abf8917374bea Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Thu, 17 Nov 2011 12:24:40 +0530 Subject: No need to `readlines` then `join`, just use `read` :heart: same as 4c1a1933cbc5ab96efe340a3b31ac5fee12c99c8 --- activerecord/lib/active_record/railties/databases.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 5964fa9cb0..4ffcf6dbc1 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -430,7 +430,7 @@ db_namespace = namespace :db do `sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i db\\structure.sql` when 'oci', 'oracle' ActiveRecord::Base.establish_connection(abcs[env]) - IO.readlines("#{Rails.root}/db/structure.sql").join.split(";\n\n").each do |ddl| + IO.read("#{Rails.root}/db/structure.sql").split(";\n\n").each do |ddl| ActiveRecord::Base.connection.execute(ddl) end when 'firebird' -- cgit v1.2.3 From a89fabbb0266e9a5a391a88fd39b97d08879412f Mon Sep 17 00:00:00 2001 From: Oscar Del Ben Date: Thu, 17 Nov 2011 18:49:47 +0100 Subject: Cleanup of databases.rake psql env variables --- activerecord/lib/active_record/railties/databases.rake | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 4ffcf6dbc1..589ed3bd11 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -377,10 +377,7 @@ db_namespace = namespace :db do ActiveRecord::Base.establish_connection(abcs[Rails.env]) File.open("#{Rails.root}/db/structure.sql", "w:utf-8") { |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'] - ENV['PGUSER'] = abcs[Rails.env]['username'].to_s if abcs[Rails.env]['username'] + set_psql_env(abcs[Rails.env]) search_path = abcs[Rails.env]['schema_search_path'] unless search_path.blank? search_path = search_path.split(",").map{|search_path_part| "--schema=#{search_path_part.strip}" }.join(" ") @@ -418,10 +415,7 @@ db_namespace = namespace :db do ActiveRecord::Base.connection.execute(table) end when /postgresql/ - ENV['PGHOST'] = abcs[env]['host'] if abcs[env]['host'] - ENV['PGPORT'] = abcs[env]['port'].to_s if abcs[env]['port'] - ENV['PGPASSWORD'] = abcs[env]['password'].to_s if abcs[env]['password'] - ENV['PGUSER'] = abcs[env]['username'].to_s if abcs[env]['username'] + set_psql_env(abcs[env]) `psql -f "#{Rails.root}/db/structure.sql" #{abcs[env]['database']} #{abcs[env]['template']}` when /sqlite/ dbfile = abcs[env]['database'] @@ -599,3 +593,10 @@ end def firebird_db_string(config) FireRuby::Database.db_string_for(config.symbolize_keys) end + +def set_psql_env(config) + ENV['PGHOST'] = config['host'] if config['host'] + ENV['PGPORT'] = config['port'].to_s if config['port'] + ENV['PGPASSWORD'] = config['password'].to_s if config['password'] + ENV['PGUSER'] = config['username'].to_s if config['username'] +end -- cgit v1.2.3 From 649f2513a453cae319be8b63a9de25ae11fb9e8f Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 17 Nov 2011 10:00:33 -0800 Subject: Revert "Merge pull request #3603 from vijaydev/change_table_without_block_arg" This reverts commit 81fad6a270ec3cbbb88553c9f2e8200c34fd4d13, reversing changes made to 23101de283de13517e30c4c3d1ecc65525264886. Conflicts: activerecord/test/cases/migration_test.rb --- .../connection_adapters/abstract/schema_statements.rb | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') 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 7742f32213..1d837f29be 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -242,19 +242,14 @@ module ActiveRecord # # See also Table for details on # all of the various column transformation - def change_table(table_name, options = {}, &blk) - bulk_change = supports_bulk_alter? && options[:bulk] - recorder = bulk_change ? ActiveRecord::Migration::CommandRecorder.new(self) : self - table = Table.new(table_name, recorder) - - if block_given? - if blk.arity == 1 - yield table - else - table.instance_eval(&blk) - end + def change_table(table_name, options = {}) + if supports_bulk_alter? && options[:bulk] + recorder = ActiveRecord::Migration::CommandRecorder.new(self) + yield Table.new(table_name, recorder) + bulk_change_table(table_name, recorder.commands) + else + yield Table.new(table_name, self) end - bulk_change_table(table_name, recorder.commands) if bulk_change end # Renames a table. -- cgit v1.2.3 From a2f14e23441ec016f9643b9054f409006b0e16b3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Thu, 17 Nov 2011 10:28:23 -0800 Subject: Revert "Merge pull request #1163 from amatsuda/sexier_migration_31" This reverts commit 0e407a90413d8a19002b85508d811ccdf2190783, reversing changes made to 533a9f84b035756eedf9fdccf0c494dc9701ba72. Conflicts: activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb activerecord/test/cases/migration_test.rb --- .../connection_adapters/abstract/schema_statements.rb | 10 ++-------- .../connection_adapters/sqlite_adapter.rb | 18 ++++++++---------- activerecord/lib/active_record/session_store.rb | 7 +++---- 3 files changed, 13 insertions(+), 22 deletions(-) (limited to 'activerecord/lib') 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 1d837f29be..faa42e2d19 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -155,17 +155,11 @@ module ActiveRecord # ) # # See also TableDefinition#column for details on how to create columns. - def create_table(table_name, options = {}, &blk) + def create_table(table_name, options = {}) td = table_definition td.primary_key(options[:primary_key] || Base.get_primary_key(table_name.to_s.singularize)) unless options[:id] == false - if block_given? - if blk.arity == 1 - yield td - else - td.instance_eval(&blk) - end - end + yield td if block_given? if options[:force] && table_exists?(table_name) drop_table(table_name) diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 35df0a1542..bc3804b3d9 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -480,30 +480,28 @@ module ActiveRecord drop_table(from) end - def copy_table(from, to, options = {}, &block) #:nodoc: - from_columns, from_primary_key = columns(from), primary_key(from) - options = options.merge(:id => (!from_columns.detect {|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) - table_definition = nil + def copy_table(from, to, options = {}) #:nodoc: + options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) create_table(to, options) do |definition| - table_definition = definition - from_columns.each do |column| + @definition = definition + columns(from).each do |column| column_name = options[:rename] ? (options[:rename][column.name] || options[:rename][column.name.to_sym] || column.name) : column.name - table_definition.column(column_name, column.type, + @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, :precision => column.precision, :scale => column.scale, :null => column.null) end - table_definition.primary_key from_primary_key if from_primary_key - table_definition.instance_eval(&block) if block + @definition.primary_key(primary_key(from)) if primary_key(from) + yield @definition if block_given? end copy_table_indexes(from, to, options[:rename] || {}) copy_table_contents(from, to, - table_definition.columns.map {|column| column.name}, + @definition.columns.map {|column| column.name}, options[:rename] || {}) end diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 92550c7efc..76c37cc367 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -64,13 +64,12 @@ module ActiveRecord end def create_table! - id_col_name, data_col_name = session_id_column, data_column_name connection_pool.clear_table_cache!(table_name) connection.create_table(table_name) do |t| - t.string id_col_name, :limit => 255 - t.text data_col_name + t.string session_id_column, :limit => 255 + t.text data_column_name end - connection.add_index table_name, id_col_name, :unique => true + connection.add_index table_name, session_id_column, :unique => true end end -- cgit v1.2.3 From 42954c426b2338bf90f286b8e425b2e192d483ae Mon Sep 17 00:00:00 2001 From: Sam Ruby Date: Fri, 18 Nov 2011 10:05:06 -0500 Subject: Stop db:seed from performing a reload of the db Fix regression introduced by 38d26b0cb56d82093889efa95992a35ba3bb9f29 --- activerecord/lib/active_record/railties/databases.rake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 589ed3bd11..abd71793fd 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -295,10 +295,10 @@ db_namespace = namespace :db do end desc 'Create the database, load the schema, and initialize with the seed data (use db:reset to also drop the db first)' - task :setup => :seed + task :setup => ['db:schema:load_if_ruby', 'db:structure:load_if_sql', :seed] desc 'Load the seed data from db/seeds.rb' - task :seed => ['db:schema:load_if_ruby', 'db:structure:load_if_sql'] do + task :seed do db_namespace['abort_if_pending_migrations'].invoke Rails.application.load_seed end -- cgit v1.2.3 From 280b2b725b488ef71556970ee895ccaddc315e0c Mon Sep 17 00:00:00 2001 From: Christos Zisopoulos Date: Fri, 18 Nov 2011 18:29:47 +0100 Subject: Only used detailed schema introspection when doing a schema dump. Fixes #3678 --- .../active_record/connection_adapters/abstract_mysql_adapter.rb | 7 +++++++ activerecord/lib/active_record/schema_dumper.rb | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index e26b10fa97..3e043992e9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -576,6 +576,13 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) + execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result| + keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] } + keys.length == 1 ? [keys.first, nil] : nil + end + end + + def detailed_pk_and_sequence_for(table) sql = <<-SQL SELECT t.constraint_type, k.column_name FROM information_schema.table_constraints t diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index cdde5cf3b9..f70aa7a0bd 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -86,7 +86,9 @@ HEADER tbl = StringIO.new # first dump primary key column - if @connection.respond_to?(:pk_and_sequence_for) + if @connection.respond_to?(:detailed_pk_and_sequence_for) + pk, _ = @connection.detailed_pk_and_sequence_for(table) + elsif @connection.respond_to?(:pk_and_sequence_for) pk, _ = @connection.pk_and_sequence_for(table) elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) -- cgit v1.2.3 From f9b917901608cda534d54b83b7d251c1fc73e31b Mon Sep 17 00:00:00 2001 From: kennyj Date: Sat, 19 Nov 2011 04:11:38 +0900 Subject: Use `show index from`. We could fix `pk_and_sequence_for` method's performance problem (GH #3678) --- .../connection_adapters/abstract_mysql_adapter.rb | 20 ++------------------ activerecord/lib/active_record/schema_dumper.rb | 4 +--- 2 files changed, 3 insertions(+), 21 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 3e043992e9..f72d2974c8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -576,24 +576,8 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) - execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result| - keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] } - keys.length == 1 ? [keys.first, nil] : nil - end - end - - def detailed_pk_and_sequence_for(table) - sql = <<-SQL - SELECT t.constraint_type, k.column_name - FROM information_schema.table_constraints t - JOIN information_schema.key_column_usage k - USING (constraint_name, table_schema, table_name) - WHERE t.table_schema = DATABASE() - AND t.table_name = '#{table}' - SQL - - execute_and_free(sql, 'SCHEMA') do |result| - keys = each_hash(result).select { |row| row[:constraint_type] == 'PRIMARY KEY' }.map { |row| row[:column_name] } + execute_and_free("SHOW INDEX FROM #{quote_table_name(table)} WHERE Key_name = 'PRIMARY'", 'SCHEMA') do |result| + keys = each_hash(result).map { |row| row[:Column_name] } keys.length == 1 ? [keys.first, nil] : nil end end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index f70aa7a0bd..cdde5cf3b9 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -86,9 +86,7 @@ HEADER tbl = StringIO.new # first dump primary key column - if @connection.respond_to?(:detailed_pk_and_sequence_for) - pk, _ = @connection.detailed_pk_and_sequence_for(table) - elsif @connection.respond_to?(:pk_and_sequence_for) + if @connection.respond_to?(:pk_and_sequence_for) pk, _ = @connection.pk_and_sequence_for(table) elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) -- cgit v1.2.3 From fec85cf10d831ab90386a549ea558b0dd58a813f Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Sat, 19 Nov 2011 14:10:21 +0000 Subject: Perf fix If we're deleting all records in an association, don't add a IN(..) clause to the query. Fixes #3672. --- .../lib/active_record/associations/has_many_association.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 50ee60284c..3353cdf1ef 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -89,8 +89,12 @@ module ActiveRecord records.each { |r| r.destroy } update_counter(-records.length) unless inverse_updates_counter_cache? else - keys = records.map { |r| r[reflection.association_primary_key] } - scope = scoped.where(reflection.association_primary_key => keys) + scope = scoped + + unless records == load_target + keys = records.map { |r| r[reflection.association_primary_key] } + scope = scoped.where(reflection.association_primary_key => keys) + end if method == :delete_all update_counter(-scope.delete_all) -- cgit v1.2.3 From bd2f5c062da011e092c1f122567f24bd5fc6d9b5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 19 Nov 2011 20:19:53 -0800 Subject: pushing caching and visitors down to the connection --- activerecord/lib/active_record/base.rb | 18 ++-- .../abstract/connection_pool.rb | 108 ++++++--------------- .../connection_adapters/abstract_adapter.rb | 3 + .../connection_adapters/schema_cache.rb | 72 ++++++++++++++ .../connection_adapters/sqlite_adapter.rb | 5 +- .../active_record/relation/predicate_builder.rb | 2 +- activerecord/lib/active_record/session_store.rb | 4 +- 7 files changed, 119 insertions(+), 93 deletions(-) create mode 100644 activerecord/lib/active_record/connection_adapters/schema_cache.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3558ae3545..7ba67b8540 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -710,21 +710,21 @@ module ActiveRecord #:nodoc: # Returns an array of column objects for the table associated with this class. def columns if defined?(@primary_key) - connection_pool.primary_keys[table_name] ||= primary_key + connection.schema_cache.primary_keys[table_name] ||= primary_key end - connection_pool.columns[table_name] + connection.schema_cache.columns[table_name] end # Returns a hash of column objects for the table associated with this class. def columns_hash - connection_pool.columns_hash[table_name] + connection.schema_cache.columns_hash[table_name] end # Returns a hash where the keys are column names and the values are # default values when instantiating the AR object for this table. def column_defaults - connection_pool.column_defaults[table_name] + connection.schema_cache.column_defaults[table_name] end # Returns an array of column names as strings. @@ -781,14 +781,14 @@ module ActiveRecord #:nodoc: def reset_column_information connection.clear_cache! undefine_attribute_methods - connection_pool.clear_table_cache!(table_name) if table_exists? + connection.schema_cache.clear_table_cache!(table_name) if table_exists? @column_names = @content_columns = @dynamic_methods_hash = @inheritance_column = nil @arel_engine = @relation = nil end def clear_cache! # :nodoc: - connection_pool.clear_cache! + connection.schema_cache.clear! end def attribute_method?(attribute) @@ -1356,9 +1356,9 @@ MSG return nil if condition.blank? case condition - when Array; sanitize_sql_array(condition) - when Hash; sanitize_sql_hash_for_conditions(condition, table_name) - else condition + when Array; sanitize_sql_array(condition) + when Hash; sanitize_sql_hash_for_conditions(condition, table_name) + else condition end end alias_method :sanitize_sql, :sanitize_sql_for_conditions 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 0ec0576795..f1781f057c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,6 +2,7 @@ require 'thread' require 'monitor' require 'set' require 'active_support/core_ext/module/synchronization' +require 'active_support/core_ext/module/deprecation' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -59,8 +60,6 @@ module ActiveRecord class ConnectionPool attr_accessor :automatic_reconnect attr_reader :spec, :connections - attr_reader :columns, :columns_hash, :primary_keys, :tables - attr_reader :column_defaults # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, @@ -85,72 +84,7 @@ module ActiveRecord @connections = [] @checked_out = [] @automatic_reconnect = true - @tables = {} @visitor = nil - - @columns = Hash.new do |h, table_name| - h[table_name] = with_connection do |conn| - - # Fetch a list of columns - conn.columns(table_name, "#{table_name} Columns").tap do |columns| - - # set primary key information - columns.each do |column| - column.primary = column.name == primary_keys[table_name] - end - end - end - end - - @columns_hash = Hash.new do |h, table_name| - h[table_name] = Hash[columns[table_name].map { |col| - [col.name, col] - }] - end - - @column_defaults = Hash.new do |h, table_name| - h[table_name] = Hash[columns[table_name].map { |col| - [col.name, col.default] - }] - end - - @primary_keys = Hash.new do |h, table_name| - h[table_name] = with_connection do |conn| - table_exists?(table_name) ? conn.primary_key(table_name) : 'id' - end - end - end - - # A cached lookup for table existence. - def table_exists?(name) - return @tables[name] if @tables.key? name - - with_connection do |conn| - conn.tables.each { |table| @tables[table] = true } - @tables[name] = conn.table_exists?(name) if !@tables.key?(name) - end - - @tables[name] - end - - # Clears out internal caches: - # - # * columns - # * columns_hash - # * tables - def clear_cache! - @columns.clear - @columns_hash.clear - @column_defaults.clear - @tables.clear - end - - # Clear out internal caches for table with +table_name+. - def clear_table_cache!(table_name) - @columns.delete table_name - @columns_hash.delete table_name - @column_defaults.delete table_name - @primary_keys.delete table_name end # Retrieve the connection associated with the current thread, or call @@ -227,6 +161,35 @@ module ActiveRecord end end + def columns + with_connection do |c| + c.schema_cache.columns + end + end + deprecate :columns + + def columns_hash + with_connection do |c| + c.schema_cache.columns_hash + end + end + deprecate :columns_hash + + def primary_keys + raise + with_connection do |c| + c.schema_cache.primary_keys + end + end + deprecate :primary_keys + + def clear_cache! + with_connection do |c| + c.schema_cache.clear! + end + end + deprecate :clear_cache! + # Return any checked-out connections back to the pool by threads that # are no longer alive. def clear_stale_cached_connections! @@ -301,16 +264,7 @@ module ActiveRecord private def new_connection - connection = ActiveRecord::Base.send(spec.adapter_method, spec.config) - - # TODO: This is a bit icky, and in the long term we may want to change the method - # signature for connections. Also, if we switch to have one visitor per - # connection (and therefore per thread), we can get rid of the thread-local - # variable in Arel::Visitors::ToSql. - @visitor ||= connection.class.visitor_for(self) - connection.visitor = @visitor - - connection + ActiveRecord::Base.send(spec.adapter_method, spec.config) end def current_connection_id #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c47bcfc406..cbb7423480 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -3,6 +3,7 @@ require 'bigdecimal' require 'bigdecimal/util' require 'active_support/core_ext/benchmark' require 'active_support/deprecation' +require 'active_record/connection_adapters/schema_cache' module ActiveRecord module ConnectionAdapters # :nodoc: @@ -51,6 +52,7 @@ module ActiveRecord define_callbacks :checkout, :checkin attr_accessor :visitor + attr_reader :schema_cache def initialize(connection, logger = nil) #:nodoc: @active = nil @@ -60,6 +62,7 @@ module ActiveRecord @open_transactions = 0 @instrumenter = ActiveSupport::Notifications.instrumenter @visitor = nil + @schema_cache = SchemaCache.new self end # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb new file mode 100644 index 0000000000..b14b37ce89 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -0,0 +1,72 @@ +module ActiveRecord + module ConnectionAdapters + class SchemaCache + attr_reader :columns, :columns_hash, :primary_keys, :tables + attr_reader :column_defaults + attr_reader :connection + + def initialize(conn) + @connection = conn + @tables = {} + + @columns = Hash.new do |h, table_name| + h[table_name] = + # Fetch a list of columns + conn.columns(table_name, "#{table_name} Columns").tap do |cs| + # set primary key information + cs.each do |column| + column.primary = column.name == primary_keys[table_name] + end + end + end + + @columns_hash = Hash.new do |h, table_name| + h[table_name] = Hash[columns[table_name].map { |col| + [col.name, col] + }] + end + + @column_defaults = Hash.new do |h, table_name| + h[table_name] = Hash[columns[table_name].map { |col| + [col.name, col.default] + }] + end + + @primary_keys = Hash.new do |h, table_name| + h[table_name] = table_exists?(table_name) ? + conn.primary_key(table_name) : 'id' + end + end + + # A cached lookup for table existence. + def table_exists?(name) + return @tables[name] if @tables.key? name + + connection.tables.each { |table| @tables[table] = true } + @tables[name] = connection.table_exists?(name) if !@tables.key?(name) + + @tables[name] + end + + # Clears out internal caches: + # + # * columns + # * columns_hash + # * tables + def clear! + @columns.clear + @columns_hash.clear + @column_defaults.clear + @tables.clear + end + + # Clear out internal caches for table with +table_name+. + def clear_table_cache!(table_name) + @columns.delete table_name + @columns_hash.delete table_name + @column_defaults.delete table_name + @primary_keys.delete table_name + end + 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 bc3804b3d9..c11f82a33f 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -89,10 +89,7 @@ module ActiveRecord @statements = StatementPool.new(@connection, config.fetch(:statement_limit) { 1000 }) @config = config - end - - def self.visitor_for(pool) # :nodoc: - Arel::Visitors::SQLite.new(pool) + @visitor = Arel::Visitors::SQLite.new self end def adapter_name #:nodoc: diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index af167dc59b..a789f48725 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -23,7 +23,7 @@ module ActiveRecord attribute.in(value.arel.ast) when Array, ActiveRecord::Associations::CollectionProxy values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x} - ranges, values = values.partition {|value| value.is_a?(Range) || value.is_a?(Arel::Relation)} + ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)} array_predicates = ranges.map {|range| attribute.in(range)} diff --git a/activerecord/lib/active_record/session_store.rb b/activerecord/lib/active_record/session_store.rb index 76c37cc367..e3bbd06f7e 100644 --- a/activerecord/lib/active_record/session_store.rb +++ b/activerecord/lib/active_record/session_store.rb @@ -59,12 +59,12 @@ module ActiveRecord end def drop_table! - connection_pool.clear_table_cache!(table_name) + connection.schema_cache.clear_table_cache!(table_name) connection.drop_table table_name end def create_table! - connection_pool.clear_table_cache!(table_name) + connection.schema_cache.clear_table_cache!(table_name) connection.create_table(table_name) do |t| t.string session_id_column, :limit => 255 t.text data_column_name -- cgit v1.2.3 From 24fa524cefb64208c424bda9e2fd80a9e4199629 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 19 Nov 2011 20:28:41 -0800 Subject: adding visitors to the respective adapters --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 1 + activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 1 + 2 files changed, 2 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index f72d2974c8..18dc73337b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -127,6 +127,7 @@ module ActiveRecord super(connection, logger) @connection_options, @config = connection_options, config @quoted_column_names, @quoted_table_names = {}, {} + @visitor = Arel::Visitors::MySQL.new self end def self.visitor_for(pool) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index b7918c7f07..d98cf5051f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -307,6 +307,7 @@ module ActiveRecord def initialize(connection, logger, connection_parameters, config) super(connection, logger) @connection_parameters, @config = connection_parameters, config + @visitor = Arel::Visitors::PostgreSQL.new self # @local_tz is initialized as nil to avoid warnings when connect tries to use it @local_tz = nil -- cgit v1.2.3 From 599d7c40a4380c702ae5612983237d084156b7d2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sat, 19 Nov 2011 20:43:04 -0800 Subject: removing deprecated methods --- .../connection_adapters/abstract_adapter.rb | 18 ------------------ .../connection_adapters/abstract_mysql_adapter.rb | 4 ---- .../connection_adapters/postgresql_adapter.rb | 4 ---- 3 files changed, 26 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index cbb7423480..75e568b557 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -65,24 +65,6 @@ module ActiveRecord @schema_cache = SchemaCache.new self end - # Returns a visitor instance for this adaptor, which conforms to the Arel::ToSql interface - def self.visitor_for(pool) # :nodoc: - adapter = pool.spec.config[:adapter] - - if Arel::Visitors::VISITORS[adapter] - ActiveSupport::Deprecation.warn( - "Arel::Visitors::VISITORS is deprecated and will be removed. Database adapters " \ - "should define a visitor_for method which returns the appropriate visitor for " \ - "the database. For example, MysqlAdapter.visitor_for(pool) returns " \ - "Arel::Visitors::MySQL.new(pool)." - ) - - Arel::Visitors::VISITORS[adapter].new(pool) - else - Arel::Visitors::ToSql.new(pool) - end - end - # Returns the human-readable name of the adapter. Use mixed case - one # can always use downcase if needed. def adapter_name diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 18dc73337b..f143fd348e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -130,10 +130,6 @@ module ActiveRecord @visitor = Arel::Visitors::MySQL.new self end - def self.visitor_for(pool) # :nodoc: - Arel::Visitors::MySQL.new(pool) - end - def adapter_name #:nodoc: self.class::ADAPTER_NAME end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d98cf5051f..2f01fbb829 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -324,10 +324,6 @@ module ActiveRecord @local_tz = execute('SHOW TIME ZONE', 'SCHEMA').first["TimeZone"] end - def self.visitor_for(pool) # :nodoc: - Arel::Visitors::PostgreSQL.new(pool) - end - # Clears the prepared statements cache. def clear_cache! @statements.clear -- cgit v1.2.3 From 5d704fa152c2f2846b66973cf5572e3120a72792 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 20 Nov 2011 15:11:11 -0800 Subject: oops! I suck! :bomb: --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') 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 f1781f057c..e32154780a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -176,7 +176,6 @@ module ActiveRecord deprecate :columns_hash def primary_keys - raise with_connection do |c| c.schema_cache.primary_keys end -- cgit v1.2.3 From 13847ccb43c3d0bdd6341887deda212316861e2d Mon Sep 17 00:00:00 2001 From: Arun Agrawal Date: Thu, 17 Nov 2011 12:44:06 +0530 Subject: Bump Mysql2! --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 971f3c35f3..95f254ddd2 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,6 +1,6 @@ require 'active_record/connection_adapters/abstract_mysql_adapter' -gem 'mysql2', '~> 0.3.6' +gem 'mysql2', '~> 0.3.10' require 'mysql2' module ActiveRecord -- cgit v1.2.3 From 8896b4fdc8a543157cdf4dfc378607ebf6c10ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Nov 2011 23:18:13 +0000 Subject: Implement ArraySerializer and move old serialization API to a new namespace. The following constants were renamed: ActiveModel::Serialization => ActiveModel::Serializable ActiveModel::Serializers::JSON => ActiveModel::Serializable::JSON ActiveModel::Serializers::Xml => ActiveModel::Serializable::XML The main motivation for such a change is that `ActiveModel::Serializers::JSON` was not actually a serializer, but a module that when included allows the target to be serializable to JSON. With such changes, we were able to clean up the namespace to add true serializers as the ArraySerializer. --- activerecord/lib/active_record/serialization.rb | 2 +- activerecord/lib/active_record/serializers/xml_serializer.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index 5ad40d8cd9..c23514c465 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -2,7 +2,7 @@ module ActiveRecord #:nodoc: # = Active Record Serialization module Serialization extend ActiveSupport::Concern - include ActiveModel::Serializers::JSON + include ActiveModel::Serializable::JSON def serializable_hash(options = nil) options = options.try(:clone) || {} diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 0e7f57aa43..2da836ef0c 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/hash/conversions' module ActiveRecord #:nodoc: module Serialization - include ActiveModel::Serializers::Xml + include ActiveModel::Serializable::XML # Builds an XML document to represent the model. Some configuration is # available through +options+. However more complicated cases should @@ -176,13 +176,13 @@ module ActiveRecord #:nodoc: end end - class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: + class XmlSerializer < ActiveModel::Serializable::XML::Serializer #:nodoc: def initialize(*args) super options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column) end - class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: + class Attribute < ActiveModel::Serializable::XML::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class type = if klass.serialized_attributes.key?(name) -- cgit v1.2.3 From 0f5104d7208b706e9a0353d3d4ccb20683e81bde Mon Sep 17 00:00:00 2001 From: Brian Samson Date: Thu, 24 Nov 2011 13:11:33 -0700 Subject: load has_many associations keyed off a custom primary key if that key is present but the record is unsaved --- activerecord/lib/active_record/associations/collection_association.rb | 2 +- activerecord/lib/active_record/associations/has_many_association.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 362f1053cd..af37909c89 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -235,7 +235,7 @@ module ActiveRecord # This method is abstract in the sense that it relies on # +count_records+, which is a method descendants have to provide. def size - if owner.new_record? || (loaded? && !options[:uniq]) + if !find_target? || (loaded? && !options[:uniq]) target.size elsif !loaded? && options[:group] load_target.size diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 3353cdf1ef..c5b90e873a 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -103,6 +103,10 @@ module ActiveRecord end end end + + def foreign_key_present? + owner.attribute_present?(reflection.association_primary_key) + end end end end -- cgit v1.2.3 From 1be9830d4d99e2bf56f1cadf74b843f22d66da35 Mon Sep 17 00:00:00 2001 From: Xavier Noria Date: Fri, 25 Nov 2011 14:29:34 -0800 Subject: add the query to AR::Relation#explain output Rationale: this is more readable if serveral queries are involved in one call. Also, it will be possible to let AR log EXPLAINs automatically in production mode, where queries are not even around. --- activerecord/lib/active_record/relation.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index f0891440a6..0c32ad5139 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- require 'active_support/core_ext/object/blank' require 'active_support/core_ext/module/delegation' @@ -155,8 +156,8 @@ module ActiveRecord end queries.map do |sql| - @klass.connection.explain(sql) - end.join + "EXPLAIN for: #{sql}\n#{@klass.connection.explain(sql)}" + end.join("\n") end def to_a -- cgit v1.2.3 From 1756629835d39f60ef4c96aff81ac503c71b98b7 Mon Sep 17 00:00:00 2001 From: "Rahul P. Chaudhari" Date: Sun, 27 Nov 2011 20:02:09 +0530 Subject: Use any instead of length --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 2f01fbb829..852debcdde 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1239,7 +1239,7 @@ module ActiveRecord if match_data rest = name[match_data[0].length, name.length] rest = rest[1, rest.length] if rest.start_with? "." - [match_data[1], (rest.length > 0 ? rest : nil)] + [match_data[1], (rest.any? ? rest : nil)] end end -- cgit v1.2.3 From 61bcc318c865289d215e8e19618b9414bd07d1e8 Mon Sep 17 00:00:00 2001 From: Josh Susser Date: Sun, 27 Nov 2011 11:22:12 -0800 Subject: use GeneratedFeatureMethods module for associations --- .../lib/active_record/associations/builder/association.rb | 8 +++++--- .../associations/builder/has_and_belongs_to_many.rb | 12 ++++++++---- activerecord/lib/active_record/attribute_methods.rb | 6 ------ activerecord/lib/active_record/base.rb | 14 ++++++++++++++ 4 files changed, 27 insertions(+), 13 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 686db0725d..3534e037b7 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -6,7 +6,7 @@ module ActiveRecord::Associations::Builder # Set by subclasses class_attribute :macro - attr_reader :model, :name, :options, :reflection, :mixin + attr_reader :model, :name, :options, :reflection def self.build(model, name, options) new(model, name, options).build @@ -14,8 +14,10 @@ module ActiveRecord::Associations::Builder def initialize(model, name, options) @model, @name, @options = model, name, options - @mixin = Module.new - @model.__send__(:include, @mixin) + end + + def mixin + @model.generated_feature_methods end def build diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index f3391eba13..30fc44b4c2 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -15,10 +15,14 @@ module ActiveRecord::Associations::Builder def define_destroy_hook name = self.name - mixin.send(:define_method, :destroy_associations) do - association(name).delete_all - super() - end + model.send(:include, Module.new { + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def destroy_associations + association(#{name.to_sym.inspect}).delete_all + super + end + RUBY + }) end # TODO: These checks should probably be moved into the Reflection, and we should not be diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 2d720fe700..d7bfaa5655 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -8,12 +8,6 @@ module ActiveRecord include ActiveModel::AttributeMethods module ClassMethods - def inherited(child_class) - # force creation + include before accessor method modules - child_class.generated_attribute_methods - super - end - # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3558ae3545..a9c8ed1396 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -450,6 +450,20 @@ module ActiveRecord #:nodoc: :having, :create_with, :uniq, :to => :scoped delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped + def inherited(child_class) #:nodoc: + # force attribute methods to be higher in inheritance hierarchy than other generated methods + child_class.generated_attribute_methods + child_class.generated_feature_methods + super + end + + def generated_feature_methods + unless const_defined?(:GeneratedFeatureMethods, false) + include const_set(:GeneratedFeatureMethods, Module.new) + end + const_get(:GeneratedFeatureMethods) + end + # 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 Product.find_by_sql then the results will be returned in -- cgit v1.2.3 From cba5a3a367f0f9d3be042c0782716fdfd4930f1c Mon Sep 17 00:00:00 2001 From: Jan Varwig Date: Sun, 27 Nov 2011 11:47:45 +0100 Subject: Test case and fix for rails/rails#3450 Asssigning a parent id to a belongs_to association actually updates the object that is validated when the association has :validates => true --- activerecord/lib/active_record/autosave_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 056170d82a..d709a77fb0 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -264,7 +264,7 @@ module ActiveRecord # turned on for the association. def validate_single_association(reflection) association = association_instance_get(reflection.name) - record = association && association.target + record = association && association.reader association_valid?(reflection, record) if record end -- cgit v1.2.3 From 10834e975a54b63a07896cb8a6a16c336e20a792 Mon Sep 17 00:00:00 2001 From: Josh Susser Date: Sun, 27 Nov 2011 14:12:46 -0800 Subject: changelog & docs for GeneratedFeatureMethods --- activerecord/lib/active_record/associations.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 34684ad2f5..60bbc325df 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -196,6 +196,26 @@ module ActiveRecord # * Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1), # Project#categories.delete(category1) # + # === Overriding generated methods + # + # Association methods are generated in a module that is included into the model class, + # which allows you to easily override with your own methods and call the original + # generated method with +super+. For example: + # + # class Car < ActiveRecord::Base + # belongs_to :owner + # belongs_to :old_owner + # def owner=(new_owner) + # self.old_owner = self.owner + # super + # end + # end + # + # If your model class is Project, the module is + # named Project::GeneratedFeatureMethods. The GeneratedFeatureMethods module is + # is included in the model class immediately after the (anonymous) generated attributes methods + # module, meaning an association will override the methods for an attribute with the same name. + # # === A word of warning # # Don't create associations that have the same name as instance methods of -- cgit v1.2.3 From 124c97fbe201f810d77f807ce69f37148e903c44 Mon Sep 17 00:00:00 2001 From: Josh Susser Date: Sun, 27 Nov 2011 14:15:40 -0800 Subject: avoid warnings This change uses Module.redefine_method as defined in ActiveSupport. Making Module.define_method public would be as clean in the code, and would also emit warnings when redefining an association. That is pretty messy given current tests, so I'm leaving it for someone else to decide what approach is better. --- activerecord/lib/active_record/associations/builder/association.rb | 4 ++-- activerecord/lib/active_record/associations/builder/belongs_to.rb | 6 +++--- .../active_record/associations/builder/collection_association.rb | 4 ++-- activerecord/lib/active_record/associations/builder/has_many.rb | 6 +++--- activerecord/lib/active_record/associations/builder/has_one.rb | 4 ++-- .../lib/active_record/associations/builder/singular_association.rb | 6 +++--- 6 files changed, 15 insertions(+), 15 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 3534e037b7..d4f59100e8 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -40,14 +40,14 @@ module ActiveRecord::Associations::Builder def define_readers name = self.name - mixin.send(:define_method, name) do |*params| + mixin.redefine_method(name) do |*params| association(name).reader(*params) end end def define_writers name = self.name - mixin.send(:define_method, "#{name}=") do |value| + mixin.redefine_method("#{name}=") do |value| association(name).writer(value) end end diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 0ca107035f..1759a41d93 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -25,14 +25,14 @@ module ActiveRecord::Associations::Builder name = self.name method_name = "belongs_to_counter_cache_after_create_for_#{name}" - mixin.send(:define_method, method_name) do + mixin.redefine_method(method_name) do record = send(name) record.class.increment_counter(cache_column, record.id) unless record.nil? end model.after_create(method_name) method_name = "belongs_to_counter_cache_before_destroy_for_#{name}" - mixin.send(:define_method, method_name) do + mixin.redefine_method(method_name) do record = send(name) record.class.decrement_counter(cache_column, record.id) unless record.nil? end @@ -48,7 +48,7 @@ module ActiveRecord::Associations::Builder method_name = "belongs_to_touch_after_save_or_destroy_for_#{name}" touch = options[:touch] - mixin.send(:define_method, method_name) do + mixin.redefine_method(method_name) do record = send(name) unless record.nil? diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 1805ea2c1e..35f9a3ae8e 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -58,7 +58,7 @@ module ActiveRecord::Associations::Builder super name = self.name - mixin.send(:define_method, "#{name.to_s.singularize}_ids") do + mixin.redefine_method("#{name.to_s.singularize}_ids") do association(name).ids_reader end end @@ -67,7 +67,7 @@ module ActiveRecord::Associations::Builder super name = self.name - mixin.send(:define_method, "#{name.to_s.singularize}_ids=") do |ids| + mixin.redefine_method("#{name.to_s.singularize}_ids=") do |ids| association(name).ids_writer(ids) end end diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb index 8a6f5a87e7..d29a525b9e 100644 --- a/activerecord/lib/active_record/associations/builder/has_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_many.rb @@ -28,7 +28,7 @@ module ActiveRecord::Associations::Builder def define_destroy_dependency_method name = self.name - mixin.send(:define_method, dependency_method_name) do + mixin.redefine_method(dependency_method_name) do send(name).each do |o| # No point in executing the counter update since we're going to destroy the parent anyway counter_method = ('belongs_to_counter_cache_before_destroy_for_' + self.class.name.downcase).to_sym @@ -45,7 +45,7 @@ module ActiveRecord::Associations::Builder def define_delete_all_dependency_method name = self.name - mixin.send(:define_method, dependency_method_name) do + mixin.redefine_method(dependency_method_name) do send(name).delete_all end end @@ -53,7 +53,7 @@ module ActiveRecord::Associations::Builder def define_restrict_dependency_method name = self.name - mixin.send(:define_method, dependency_method_name) do + mixin.redefine_method(dependency_method_name) do raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).empty? end end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 2cea8b9805..7a6cd3890f 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -45,7 +45,7 @@ module ActiveRecord::Associations::Builder def define_destroy_dependency_method name = self.name - mixin.send(:define_method, dependency_method_name) do + mixin.redefine_method(dependency_method_name) do association(name).delete end end @@ -54,7 +54,7 @@ module ActiveRecord::Associations::Builder def define_restrict_dependency_method name = self.name - mixin.send(:define_method, dependency_method_name) do + mixin.redefine_method(dependency_method_name) do raise ActiveRecord::DeleteRestrictionError.new(name) unless send(name).nil? end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 020e9157b3..436b6c1524 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -16,15 +16,15 @@ module ActiveRecord::Associations::Builder def define_constructors name = self.name - mixin.send(:define_method, "build_#{name}") do |*params, &block| + mixin.redefine_method("build_#{name}") do |*params, &block| association(name).build(*params, &block) end - mixin.send(:define_method, "create_#{name}") do |*params, &block| + mixin.redefine_method("create_#{name}") do |*params, &block| association(name).create(*params, &block) end - mixin.send(:define_method, "create_#{name}!") do |*params, &block| + mixin.redefine_method("create_#{name}!") do |*params, &block| association(name).create!(*params, &block) end end -- cgit v1.2.3 From 89925e817c356c292a195c894779b55307c41512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 28 Nov 2011 11:18:39 +0100 Subject: Revert "Use any instead of length" This reverts commit 1756629835d39f60ef4c96aff81ac503c71b98b7. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 852debcdde..2f01fbb829 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -1239,7 +1239,7 @@ module ActiveRecord if match_data rest = name[match_data[0].length, name.length] rest = rest[1, rest.length] if rest.start_with? "." - [match_data[1], (rest.any? ? rest : nil)] + [match_data[1], (rest.length > 0 ? rest : nil)] end end -- cgit v1.2.3 From d1afd987464717f8af1ab0e9a78af6f37b9ce425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ku=C5=BAma?= Date: Mon, 28 Nov 2011 19:27:58 +0100 Subject: added information about callbacks created by autosave association (#3639) --- .../lib/active_record/autosave_association.rb | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index d709a77fb0..6d3f1839c5 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -21,6 +21,21 @@ module ActiveRecord # Note that :autosave => false is not same as not declaring :autosave. # When the :autosave option is not present new associations are saved. # + # == Validation + # + # Children records are validated unless :validate is +false+. + # + # == Callbacks + # + # Association with autosave option defines several callbacks on your + # model (before_save, after_create, after_update). Please note that + # callbacks are executed in the order they were defined in + # model. You should avoid modyfing the association content, before + # autosave callbacks are executed. Placing your callbacks after + # associations is usually a good practice. + # + # == Examples + # # === One-to-one Example # # class Post @@ -109,10 +124,7 @@ module ActiveRecord # Now it _is_ removed from the database: # # Comment.find_by_id(id).nil? # => true - # - # === Validation - # - # Children records are validated unless :validate is +false+. + module AutosaveAssociation extend ActiveSupport::Concern -- cgit v1.2.3 From 2a9a8ad4dfb2609a2275c1a3540ad2768562a026 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 11:06:59 -0800 Subject: break establish_connection to smaller methods --- .../abstract/connection_specification.rb | 70 ++++++++++++---------- 1 file changed, 40 insertions(+), 30 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 3d0f146fed..e335ebd9c2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -56,37 +56,47 @@ module ActiveRecord # may be returned on an error. def self.establish_connection(spec = ENV["DATABASE_URL"]) case spec - when nil - raise AdapterNotSpecified unless defined?(Rails.env) - establish_connection(Rails.env) - when ConnectionSpecification - self.connection_handler.establish_connection(name, spec) - when Symbol, String - if configuration = configurations[spec.to_s] - establish_connection(configuration) - elsif spec.is_a?(String) && hash = connection_url_to_hash(spec) - establish_connection(hash) - else - raise AdapterNotSpecified, "#{spec} database is not configured" - end - else - spec = spec.symbolize_keys - unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end - - begin - require "active_record/connection_adapters/#{spec[:adapter]}_adapter" - rescue LoadError => e - raise "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e})" - end - - adapter_method = "#{spec[:adapter]}_connection" - unless respond_to?(adapter_method) - raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" - end - - remove_connection - establish_connection(ConnectionSpecification.new(spec, adapter_method)) + when nil + raise AdapterNotSpecified unless defined?(Rails.env) + spec = resolve_string_connection Rails.env + when Symbol, String + spec = resolve_string_connection spec.to_s + when Hash + spec = resolve_hash_connection spec end + + if ConnectionSpecification === spec + return self.connection_handler.establish_connection(name, spec) + end + end + + def self.resolve_string_connection(spec) # :nodoc: + if configuration = configurations[spec] + spec = resolve_hash_connection(configuration) + elsif hash = connection_url_to_hash(spec) + spec = resolve_hash_connection(hash) + else + raise AdapterNotSpecified, "#{spec} database is not configured" + end + end + + def self.resolve_hash_connection(spec) # :nodoc: + spec = spec.symbolize_keys + unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end + + begin + require "active_record/connection_adapters/#{spec[:adapter]}_adapter" + rescue LoadError => e + raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace + end + + adapter_method = "#{spec[:adapter]}_connection" + unless respond_to?(adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" + end + + remove_connection + ConnectionSpecification.new(spec, adapter_method) end def self.connection_url_to_hash(url) # :nodoc: -- cgit v1.2.3 From ffb218c750a876758f92e04a96a0676c77943208 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 11:46:16 -0800 Subject: pools are 1:1 with spec now rather than 1:1 with class --- .../connection_adapters/abstract/connection_pool.rb | 9 ++++++--- .../connection_adapters/abstract/connection_specification.rb | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') 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 e32154780a..78cea5e2f7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -320,10 +320,12 @@ module ActiveRecord def initialize(pools = {}) @connection_pools = pools + @class_to_pool = {} end def establish_connection(name, spec) - @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec) + @connection_pools[spec] ||= ConnectionAdapters::ConnectionPool.new(spec) + @class_to_pool[name] = @connection_pools[spec] end # Returns true if there are any active connections among the connection @@ -374,16 +376,17 @@ module ActiveRecord # can be used as an argument for establish_connection, for easily # re-establishing the connection. def remove_connection(klass) - pool = @connection_pools.delete(klass.name) + pool = @class_to_pool.delete(klass.name) return nil unless pool + @connection_pools.delete pool.spec pool.automatic_reconnect = false pool.disconnect! pool.spec.config end def retrieve_connection_pool(klass) - pool = @connection_pools[klass.name] + pool = @class_to_pool[klass.name] return pool if pool return nil if ActiveRecord::Base == klass retrieve_connection_pool klass.superclass diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index e335ebd9c2..e1f3d40bc3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -82,7 +82,8 @@ module ActiveRecord def self.resolve_hash_connection(spec) # :nodoc: spec = spec.symbolize_keys - unless spec.key?(:adapter) then raise AdapterNotSpecified, "database configuration does not specify adapter" end + + raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) begin require "active_record/connection_adapters/#{spec[:adapter]}_adapter" -- cgit v1.2.3 From 30f7c59e9010ccdfa601f28e22fd4e449f266df1 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 11:56:29 -0800 Subject: clean up string => hash conversion for connection pool --- .../abstract/connection_specification.rb | 36 ++++++++++------------ 1 file changed, 17 insertions(+), 19 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index e1f3d40bc3..65570da0d9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -55,29 +55,27 @@ module ActiveRecord # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def self.establish_connection(spec = ENV["DATABASE_URL"]) - case spec - when nil - raise AdapterNotSpecified unless defined?(Rails.env) - spec = resolve_string_connection Rails.env - when Symbol, String - spec = resolve_string_connection spec.to_s - when Hash - spec = resolve_hash_connection spec - end - - if ConnectionSpecification === spec - return self.connection_handler.establish_connection(name, spec) - end + config = case spec + when nil + raise AdapterNotSpecified unless defined?(Rails.env) + resolve_string_connection Rails.env + when Symbol, String + resolve_string_connection spec.to_s + when Hash + resolve_hash_connection spec + end + + connection_handler.establish_connection(name, config) end def self.resolve_string_connection(spec) # :nodoc: - if configuration = configurations[spec] - spec = resolve_hash_connection(configuration) - elsif hash = connection_url_to_hash(spec) - spec = resolve_hash_connection(hash) - else - raise AdapterNotSpecified, "#{spec} database is not configured" + hash = configurations.fetch(spec) do |k| + connection_url_to_hash(k) end + + raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash + + resolve_hash_connection hash end def self.resolve_hash_connection(spec) # :nodoc: -- cgit v1.2.3 From dde21138673c111099890301ffce8b6185e9ea3c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 13:07:42 -0800 Subject: Move connection resoluion logic to it's own testable class. --- .../abstract/connection_specification.rb | 132 ++++++++++++--------- 1 file changed, 74 insertions(+), 58 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index 65570da0d9..f2e7f88011 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -5,6 +5,78 @@ module ActiveRecord def initialize (config, adapter_method) @config, @adapter_method = config, adapter_method end + + ## + # Builds a ConnectionSpecification from user input + class Resolver # :nodoc: + attr_reader :config, :klass, :configurations + + def initialize(config, klass, configurations) + @config = config + @klass = klass + @configurations = configurations + end + + def spec + case config + when nil + raise AdapterNotSpecified unless defined?(Rails.env) + resolve_string_connection Rails.env + when Symbol, String + resolve_string_connection config.to_s + when Hash + resolve_hash_connection config + end + end + + private + def resolve_string_connection(spec) # :nodoc: + hash = configurations.fetch(spec) do |k| + connection_url_to_hash(k) + end + + raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash + + resolve_hash_connection hash + end + + def resolve_hash_connection(spec) # :nodoc: + spec = spec.symbolize_keys + + raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) + + begin + require "active_record/connection_adapters/#{spec[:adapter]}_adapter" + rescue LoadError => e + raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace + end + + adapter_method = "#{spec[:adapter]}_connection" + unless klass.respond_to?(adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" + end + + ConnectionSpecification.new(spec, adapter_method) + end + + def connection_url_to_hash(url) # :nodoc: + config = URI.parse url + adapter = config.scheme + adapter = "postgresql" if adapter == "postgres" + spec = { :adapter => adapter, + :username => config.user, + :password => config.password, + :port => config.port, + :database => config.path.sub(%r{^/},""), + :host => config.host } + spec.reject!{ |_,value| !value } + if config.query + options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys + spec.merge!(options) + end + spec + end + end end ## @@ -55,65 +127,9 @@ module ActiveRecord # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def self.establish_connection(spec = ENV["DATABASE_URL"]) - config = case spec - when nil - raise AdapterNotSpecified unless defined?(Rails.env) - resolve_string_connection Rails.env - when Symbol, String - resolve_string_connection spec.to_s - when Hash - resolve_hash_connection spec - end - - connection_handler.establish_connection(name, config) - end - - def self.resolve_string_connection(spec) # :nodoc: - hash = configurations.fetch(spec) do |k| - connection_url_to_hash(k) - end - - raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash - - resolve_hash_connection hash - end - - def self.resolve_hash_connection(spec) # :nodoc: - spec = spec.symbolize_keys - - raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) - - begin - require "active_record/connection_adapters/#{spec[:adapter]}_adapter" - rescue LoadError => e - raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install activerecord-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace - end - - adapter_method = "#{spec[:adapter]}_connection" - unless respond_to?(adapter_method) - raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" - end - + resolver = ConnectionSpecification::Resolver.new spec, self, configurations remove_connection - ConnectionSpecification.new(spec, adapter_method) - end - - def self.connection_url_to_hash(url) # :nodoc: - config = URI.parse url - adapter = config.scheme - adapter = "postgresql" if adapter == "postgres" - spec = { :adapter => adapter, - :username => config.user, - :password => config.password, - :port => config.port, - :database => config.path.sub(%r{^/},""), - :host => config.host } - spec.reject!{ |_,value| !value } - if config.query - options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys - spec.merge!(options) - end - spec + connection_handler.establish_connection name, resolver.spec end class << self -- cgit v1.2.3 From 884a04007fd09a94cdaf484a6c92819af8fc1b4e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 13:37:42 -0800 Subject: remove useless class_eval --- activerecord/lib/active_record/base.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7ba67b8540..3d23565ff9 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2121,9 +2121,7 @@ MSG send("#{att}=", value) if respond_to?("#{att}=") end end - end - Base.class_eval do include ActiveRecord::Persistence extend ActiveModel::Naming extend QueryCache::ClassMethods -- cgit v1.2.3 From beff819a4d7060b7635410d2c4621be735033240 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 15:10:41 -0800 Subject: just check in all connections --- .../active_record/connection_adapters/abstract/connection_pool.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') 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 78cea5e2f7..061ec878c2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -128,11 +128,9 @@ module ActiveRecord # Disconnects all connections in the pool, and clears the pool. def disconnect! - @reserved_connections.each do |name,conn| - checkin conn - end @reserved_connections = {} @connections.each do |conn| + checkin conn conn.disconnect! end @connections = [] @@ -140,11 +138,9 @@ module ActiveRecord # Clears the cache which maps classes. def clear_reloadable_connections! - @reserved_connections.each do |name, conn| - checkin conn - end @reserved_connections = {} @connections.each do |conn| + checkin conn conn.disconnect! if conn.requires_reloading? end @connections.delete_if do |conn| -- cgit v1.2.3 From 52a9884ce486dc97e0a8d88d0126ac6194d52346 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Mon, 28 Nov 2011 15:20:24 -0800 Subject: remove unused instance variable --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') 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 061ec878c2..0c64ffdeaf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -84,7 +84,6 @@ module ActiveRecord @connections = [] @checked_out = [] @automatic_reconnect = true - @visitor = nil end # Retrieve the connection associated with the current thread, or call -- cgit v1.2.3 From f73f53455a01a93bd90cb8c0cee1a7c54afdb301 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 11:48:55 -0800 Subject: respond_to? information of AR is not the responsibility of the spec resolver. --- .../abstract/connection_specification.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb index f2e7f88011..ca9fb11e95 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb @@ -11,9 +11,8 @@ module ActiveRecord class Resolver # :nodoc: attr_reader :config, :klass, :configurations - def initialize(config, klass, configurations) + def initialize(config, configurations) @config = config - @klass = klass @configurations = configurations end @@ -52,9 +51,6 @@ module ActiveRecord end adapter_method = "#{spec[:adapter]}_connection" - unless klass.respond_to?(adapter_method) - raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" - end ConnectionSpecification.new(spec, adapter_method) end @@ -127,9 +123,15 @@ module ActiveRecord # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def self.establish_connection(spec = ENV["DATABASE_URL"]) - resolver = ConnectionSpecification::Resolver.new spec, self, configurations + resolver = ConnectionSpecification::Resolver.new spec, configurations + spec = resolver.spec + + unless respond_to?(spec.adapter_method) + raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter" + end + remove_connection - connection_handler.establish_connection name, resolver.spec + connection_handler.establish_connection name, spec end class << self -- cgit v1.2.3 From 0b72a04d0c93b666c23500aefbe4a6a76593cd36 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 12:28:04 +0000 Subject: Deprecate set_table_name in favour of self.table_name= or defining your own method. --- activerecord/lib/active_record/base.rb | 81 +++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 22 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e1908312b8..bccf1b9b77 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -23,6 +23,7 @@ require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/blank' +require 'active_support/deprecation' require 'arel' require 'active_record/errors' require 'active_record/log_subscriber' @@ -624,14 +625,61 @@ module ActiveRecord #:nodoc: # the table name guess for an Invoice class becomes "myapp_invoices". # Invoice::Lineitem becomes "myapp_invoice_lineitems". # - # You can also overwrite this class method to allow for unguessable - # links, such as a Mouse class with a link to a "mice" table. Example: + # You can also set your own table name explicitly: # # class Mouse < ActiveRecord::Base - # set_table_name "mice" + # self.table_name = "mice" # end + # + # Alternatively, you can override the table_name method to define your + # own computation. (Possibly using super to manipulate the default + # table name.) Example: + # + # class Post < ActiveRecord::Base + # def self.table_name + # "special_" + super + # end + # end + # Post.table_name # => "special_posts" def table_name - reset_table_name + reset_table_name unless defined?(@table_name) + @table_name + end + + # Sets the table name explicitly. Example: + # + # class Project < ActiveRecord::Base + # self.table_name = "project" + # end + # + # You can also just define your own self.table_name method; see + # the documentation for ActiveRecord::Base#table_name. + def table_name=(value) + @quoted_table_name = nil + @arel_table = nil + @table_name = value + @relation = Relation.new(self, arel_table) + end + + def set_table_name(value = nil, &block) #:nodoc: + if block + ActiveSupport::Deprecation.warn( + "Calling set_table_name is deprecated. If you need to lazily evaluate " \ + "the table name, define your own `self.table_name` class method. You can use `super` " \ + "to get the default table name where you would have called `original_table_name`." + ) + + @quoted_table_name = nil + define_attr_method :table_name, value, &block + @arel_table = nil + @relation = Relation.new(self, arel_table) + else + ActiveSupport::Deprecation.warn( + "Calling set_table_name is deprecated. Please use `self.table_name = 'the_name'` instead." + ) + + self.table_name = value + end end # Returns a quoted version of the table name, used to construct SQL statements. @@ -641,9 +689,13 @@ module ActiveRecord #:nodoc: # Computes the table name, (re)sets it internally, and returns it. def reset_table_name #:nodoc: - return if abstract_class? - - self.table_name = compute_table_name + if superclass.abstract_class? + self.table_name = superclass.table_name || compute_table_name + elsif abstract_class? + self.table_name = superclass == Base ? nil : superclass.table_name + else + self.table_name = compute_table_name + end end def full_table_name_prefix #:nodoc: @@ -668,21 +720,6 @@ module ActiveRecord #:nodoc: default end - # 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 - # set_table_name "project" - # end - def set_table_name(value = nil, &block) - @quoted_table_name = nil - define_attr_method :table_name, value, &block - @arel_table = nil - - @relation = Relation.new(self, arel_table) - end - alias :table_name= :set_table_name - # Sets the name of the inheritance column to use to the given value, # or (if the value # is nil or false) to the value returned by the # given block. -- cgit v1.2.3 From 34609d67b442366644945a95b019daf5b474727b Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 15:34:22 +0000 Subject: Deprecate set_inheritance_column in favour of self.inheritance_column= --- activerecord/lib/active_record/base.rb | 46 +++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bccf1b9b77..d1d3888de2 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -702,10 +702,36 @@ module ActiveRecord #:nodoc: (parents.detect{ |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix end - # Defines the column name for use with single table inheritance. Use - # set_inheritance_column to set a different value. + # The name of the column containing the object's class when Single Table Inheritance is used def inheritance_column - @inheritance_column ||= "type" + if self == Base + 'type' + else + defined?(@inheritance_column) ? @inheritance_column : superclass.inheritance_column + end + end + + # Sets the value of inheritance_column + def inheritance_column=(value) + @inheritance_column = value.to_s + end + + def set_inheritance_column(value = nil, &block) #:nodoc: + if block + ActiveSupport::Deprecation.warn( + "Calling set_inheritance_column is deprecated. If you need to lazily evaluate " \ + "the inheritance column, define your own `self.inheritance_column` class method. You can use `super` " \ + "to get the default inheritance column where you would have called `original_inheritance_column`." + ) + + define_attr_method :inheritance_column, value, &block + else + ActiveSupport::Deprecation.warn( + "Calling set_inheritance_column is deprecated. Please use `self.inheritance_column = 'the_name'` instead." + ) + + self.inheritance_column = value + end end # Lazy-set the sequence name to the connection's default. This method @@ -720,20 +746,6 @@ module ActiveRecord #:nodoc: default end - # Sets the name of the inheritance column to use to the given value, - # or (if the value # is nil or false) to the value returned by the - # given block. - # - # class Project < ActiveRecord::Base - # set_inheritance_column do - # original_inheritance_column + "_id" - # end - # end - def set_inheritance_column(value = nil, &block) - define_attr_method :inheritance_column, value, &block - end - alias :inheritance_column= :set_inheritance_column - # Sets the name of the sequence to use when generating ids to the given # value, or (if the value is nil or false) to the value returned by the # given block. This is required for Oracle and is useful for any -- cgit v1.2.3 From fd7ca98bb6218de42b821d48db083ea8c0e97d67 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 16:04:22 +0000 Subject: Add tests for set_sequence_name etc --- activerecord/lib/active_record/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index d1d3888de2..40fa010558 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -707,7 +707,7 @@ module ActiveRecord #:nodoc: if self == Base 'type' else - defined?(@inheritance_column) ? @inheritance_column : superclass.inheritance_column + (@inheritance_column ||= nil) || superclass.inheritance_column end end -- cgit v1.2.3 From 7af719e81c46d06f50cd9b3caff38b945c5f2d84 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 16:33:50 +0000 Subject: Deprecate set_sequence_name in favour of self.sequence_name= --- activerecord/lib/active_record/base.rb | 39 ++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 40fa010558..9f173a4bcc 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -734,16 +734,16 @@ module ActiveRecord #:nodoc: end end - # 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 + def sequence_name + if superclass == Base + @sequence_name ||= reset_sequence_name + else + (@sequence_name ||= nil) || superclass.sequence_name + end end def reset_sequence_name #:nodoc: - default = connection.default_sequence_name(table_name, primary_key) - set_sequence_name(default) - default + self.sequence_name = connection.default_sequence_name(table_name, primary_key) end # Sets the name of the sequence to use when generating ids to the given @@ -758,12 +758,29 @@ module ActiveRecord #:nodoc: # will discover the sequence corresponding to your primary key for you. # # class Project < ActiveRecord::Base - # set_sequence_name "projectseq" # default would have been "project_seq" + # self.sequence_name = "projectseq" # default would have been "project_seq" # end - def set_sequence_name(value = nil, &block) - define_attr_method :sequence_name, value, &block + def sequence_name=(value) + @sequence_name = value.to_s + end + + def set_sequence_name(value = nil, &block) #:nodoc: + if block + ActiveSupport::Deprecation.warn( + "Calling set_sequence_name is deprecated. If you need to lazily evaluate " \ + "the sequence name, define your own `self.sequence_name` class method. You can use `super` " \ + "to get the default sequence name where you would have called `original_sequence_name`." + ) + + define_attr_method :sequence_name, value, &block + else + ActiveSupport::Deprecation.warn( + "Calling set_sequence_name is deprecated. Please use `self.sequence_name = 'the_name'` instead." + ) + + self.sequence_name = value + end end - alias :sequence_name= :set_sequence_name # Indicates whether the table associated with this class exists def table_exists? -- cgit v1.2.3 From e51ecfaaa32fde3ada4583ade4328546fef1e421 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 17:42:05 +0000 Subject: extract method --- activerecord/lib/active_record/base.rb | 74 ++++++++++++---------------------- 1 file changed, 25 insertions(+), 49 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 9f173a4bcc..3f57aaed8f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -586,6 +586,24 @@ module ActiveRecord #:nodoc: self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder) end + def deprecated_property_setter(property, value, block) #:nodoc: + if block + ActiveSupport::Deprecation.warn( + "Calling set_#{property} is deprecated. If you need to lazily evaluate " \ + "the #{property}, define your own `self.#{property}` class method. You can use `super` " \ + "to get the default #{property} where you would have called `original_#{property}`." + ) + + define_attr_method property, value, &block + else + ActiveSupport::Deprecation.warn( + "Calling set_#{property} is deprecated. Please use `self.#{property} = 'the_name'` instead." + ) + + define_attr_method property, value + end + end + # Guesses the table name (in forced lower-case) based on the name of the class in the # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy # looks like: Reply < Message < ActiveRecord::Base, then Message is used @@ -655,31 +673,17 @@ module ActiveRecord #:nodoc: # You can also just define your own self.table_name method; see # the documentation for ActiveRecord::Base#table_name. def table_name=(value) + @table_name = value @quoted_table_name = nil @arel_table = nil - @table_name = value @relation = Relation.new(self, arel_table) end def set_table_name(value = nil, &block) #:nodoc: - if block - ActiveSupport::Deprecation.warn( - "Calling set_table_name is deprecated. If you need to lazily evaluate " \ - "the table name, define your own `self.table_name` class method. You can use `super` " \ - "to get the default table name where you would have called `original_table_name`." - ) - - @quoted_table_name = nil - define_attr_method :table_name, value, &block - @arel_table = nil - @relation = Relation.new(self, arel_table) - else - ActiveSupport::Deprecation.warn( - "Calling set_table_name is deprecated. Please use `self.table_name = 'the_name'` instead." - ) - - self.table_name = value - end + deprecated_property_setter :table_name, value, block + @quoted_table_name = nil + @arel_table = nil + @relation = Relation.new(self, arel_table) end # Returns a quoted version of the table name, used to construct SQL statements. @@ -717,21 +721,7 @@ module ActiveRecord #:nodoc: end def set_inheritance_column(value = nil, &block) #:nodoc: - if block - ActiveSupport::Deprecation.warn( - "Calling set_inheritance_column is deprecated. If you need to lazily evaluate " \ - "the inheritance column, define your own `self.inheritance_column` class method. You can use `super` " \ - "to get the default inheritance column where you would have called `original_inheritance_column`." - ) - - define_attr_method :inheritance_column, value, &block - else - ActiveSupport::Deprecation.warn( - "Calling set_inheritance_column is deprecated. Please use `self.inheritance_column = 'the_name'` instead." - ) - - self.inheritance_column = value - end + deprecated_property_setter :inheritance_column, value, block end def sequence_name @@ -765,21 +755,7 @@ module ActiveRecord #:nodoc: end def set_sequence_name(value = nil, &block) #:nodoc: - if block - ActiveSupport::Deprecation.warn( - "Calling set_sequence_name is deprecated. If you need to lazily evaluate " \ - "the sequence name, define your own `self.sequence_name` class method. You can use `super` " \ - "to get the default sequence name where you would have called `original_sequence_name`." - ) - - define_attr_method :sequence_name, value, &block - else - ActiveSupport::Deprecation.warn( - "Calling set_sequence_name is deprecated. Please use `self.sequence_name = 'the_name'` instead." - ) - - self.sequence_name = value - end + deprecated_property_setter :sequence_name, value, block end # Indicates whether the table associated with this class exists -- cgit v1.2.3 From 4aad289428bf46cf4a13f159819f1993f8fc978f Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 18:30:22 +0000 Subject: Make sure the original_foo accessor still works (but deprecated) if we are using self.foo= --- activerecord/lib/active_record/base.rb | 37 ++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3f57aaed8f..b178419657 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -604,6 +604,16 @@ module ActiveRecord #:nodoc: end end + def deprecated_original_property_getter(property) #:nodoc: + ActiveSupport::Deprecation.warn("original_#{property} is deprecated. Define self.#{property} and call super instead.") + + if !instance_variable_defined?("@original_#{property}") && respond_to?("reset_#{property}") + send("reset_#{property}") + else + instance_variable_get("@original_#{property}") + end + end + # Guesses the table name (in forced lower-case) based on the name of the class in the # inheritance hierarchy descending directly from ActiveRecord::Base. So if the hierarchy # looks like: Reply < Message < ActiveRecord::Base, then Message is used @@ -664,6 +674,10 @@ module ActiveRecord #:nodoc: @table_name end + def original_table_name #:nodoc: + deprecated_original_property_getter :table_name + end + # Sets the table name explicitly. Example: # # class Project < ActiveRecord::Base @@ -673,10 +687,11 @@ module ActiveRecord #:nodoc: # You can also just define your own self.table_name method; see # the documentation for ActiveRecord::Base#table_name. def table_name=(value) - @table_name = value - @quoted_table_name = nil - @arel_table = nil - @relation = Relation.new(self, arel_table) + @original_table_name = @table_name if defined?(@table_name) + @table_name = value + @quoted_table_name = nil + @arel_table = nil + @relation = Relation.new(self, arel_table) end def set_table_name(value = nil, &block) #:nodoc: @@ -715,9 +730,14 @@ module ActiveRecord #:nodoc: end end + def original_inheritance_column #:nodoc: + deprecated_original_property_getter :inheritance_column + end + # Sets the value of inheritance_column def inheritance_column=(value) - @inheritance_column = value.to_s + @original_inheritance_column = inheritance_column + @inheritance_column = value.to_s end def set_inheritance_column(value = nil, &block) #:nodoc: @@ -732,6 +752,10 @@ module ActiveRecord #:nodoc: end end + def original_sequence_name #:nodoc: + deprecated_original_property_getter :sequence_name + end + def reset_sequence_name #:nodoc: self.sequence_name = connection.default_sequence_name(table_name, primary_key) end @@ -751,7 +775,8 @@ module ActiveRecord #:nodoc: # self.sequence_name = "projectseq" # default would have been "project_seq" # end def sequence_name=(value) - @sequence_name = value.to_s + @original_sequence_name = @sequence_name if defined?(@sequence_name) + @sequence_name = value.to_s end def set_sequence_name(value = nil, &block) #:nodoc: -- cgit v1.2.3 From 1a474cc8e41522ae079871d297c0e61ee4f6ef35 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 18:58:41 +0000 Subject: Deprecate set_primary_key in favour of self.primary_key= --- .../active_record/attribute_methods/primary_key.rb | 46 ++++++++++++---------- .../abstract/schema_statements.rb | 2 +- 2 files changed, 27 insertions(+), 21 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index a404a5edd7..93dae3ff86 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -22,11 +22,11 @@ module ActiveRecord end def reset_primary_key #:nodoc: - key = self == base_class ? get_primary_key(base_class.name) : - base_class.primary_key - - set_primary_key(key) - key + if self == base_class + self.primary_key = get_primary_key(base_class.name) + else + self.primary_key = base_class.primary_key + end end def get_primary_key(base_name) #:nodoc: @@ -46,27 +46,33 @@ module ActiveRecord end end - attr_accessor :original_primary_key - - # Attribute writer for the primary key column - def primary_key=(value) - @quoted_primary_key = nil - @primary_key = value + def original_primary_key #:nodoc: + deprecated_original_property_getter :primary_key end - # Sets the name of the primary key column to use to the given value, - # or (if the value is nil or false) to the value returned by the given - # block. + # Sets the name of the primary key column. # # class Project < ActiveRecord::Base - # set_primary_key "sysid" + # self.primary_key = "sysid" # end - def set_primary_key(value = nil, &block) + # + # You can also define the primary_key method yourself: + # + # class Project < ActiveRecord::Base + # def self.primary_key + # "foo_" + super + # end + # end + # Project.primary_key # => "foo_id" + def primary_key=(value) + @original_primary_key = @primary_key if defined?(@primary_key) + @primary_key = value && value.to_s + @quoted_primary_key = nil + end + + def set_primary_key(value = nil, &block) #:nodoc: + deprecated_property_setter :primary_key, value, block @quoted_primary_key = nil - @primary_key ||= '' - self.original_primary_key = @primary_key - value &&= value.to_s - self.primary_key = block_given? ? instance_eval(&block) : value end end end 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 faa42e2d19..ce4c5a1383 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -114,7 +114,7 @@ module ActiveRecord # Defaults to +id+. If :id is false this option is ignored. # # Also note that this just sets the primary key in the table. You additionally - # need to configure the primary key in the model via the +set_primary_key+ macro. + # need to configure the primary key in the model via +self.primary_key=+. # Models do NOT auto-detect the primary key from their table definition. # # [:options] -- cgit v1.2.3 From f3c84dc31692204aacac3c125dcfcc986fd961a0 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 19:40:29 +0000 Subject: Deprecate set_locking_column in favour of self.locking_column= --- .../lib/active_record/locking/optimistic.rb | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 1a29ded787..531f104c02 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -51,10 +51,6 @@ module ActiveRecord included do cattr_accessor :lock_optimistically, :instance_writer => false self.lock_optimistically = true - - class << self - alias_method :locking_column=, :set_locking_column - end end def locking_enabled? #:nodoc: @@ -148,15 +144,24 @@ module ActiveRecord lock_optimistically && columns_hash[locking_column] end + def locking_column=(value) + @original_locking_column = @locking_column if defined?(@locking_column) + @locking_column = value.to_s + end + # Set the column to use for optimistic locking. Defaults to +lock_version+. def set_locking_column(value = nil, &block) - define_attr_method :locking_column, value, &block - value + deprecated_property_setter :locking_column, value, block end # The version column used for optimistic locking. Defaults to +lock_version+. def locking_column - reset_locking_column + reset_locking_column unless defined?(@locking_column) + @locking_column + end + + def original_locking_column #:nodoc: + deprecated_original_property_getter :locking_column end # Quote the column name used for optimistic locking. @@ -166,7 +171,7 @@ module ActiveRecord # Reset the column used for optimistic locking back to the +lock_version+ default. def reset_locking_column - set_locking_column DEFAULT_LOCKING_COLUMN + self.locking_column = DEFAULT_LOCKING_COLUMN end # Make sure the lock version column gets updated when counters are -- cgit v1.2.3 From 8df787d42890017f182c1ac6cb082317c255a456 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 20:10:33 +0000 Subject: Deprecated `define_attr_method` in `ActiveModel::AttributeMethods` This only existed to support methods like `set_table_name` in Active Record, which are themselves being deprecated. --- activerecord/lib/active_record/base.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b178419657..8cef066608 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -594,13 +594,13 @@ module ActiveRecord #:nodoc: "to get the default #{property} where you would have called `original_#{property}`." ) - define_attr_method property, value, &block + define_attr_method property, value, false, &block else ActiveSupport::Deprecation.warn( "Calling set_#{property} is deprecated. Please use `self.#{property} = 'the_name'` instead." ) - define_attr_method property, value + define_attr_method property, value, false end end -- cgit v1.2.3 From 3da5fba56a4b01e9a4ac5aaf9b7c7c35ec2cc2f7 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 29 Nov 2011 20:43:05 +0000 Subject: Fix ruby 1.8 compat. const_defined? only takes a second arg on 1.9. --- activerecord/lib/active_record/base.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 8cef066608..484fe5fb16 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -459,10 +459,11 @@ module ActiveRecord #:nodoc: end def generated_feature_methods - unless const_defined?(:GeneratedFeatureMethods, false) - include const_set(:GeneratedFeatureMethods, Module.new) + @generated_feature_methods ||= begin + mod = const_set(:GeneratedFeatureMethods, Module.new) + include mod + mod end - const_get(:GeneratedFeatureMethods) end # Executes a custom SQL query against your database and returns all the results. The results will -- cgit v1.2.3 From 9bf8bf82b9d8614d9c7ccc31892887f6a79ce2f5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 13:12:56 -0800 Subject: Adapters keep in_use flag when leased --- .../active_record/connection_adapters/abstract_adapter.rb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 75e568b557..4a9653c08c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -4,6 +4,7 @@ require 'bigdecimal/util' require 'active_support/core_ext/benchmark' require 'active_support/deprecation' require 'active_record/connection_adapters/schema_cache' +require 'monitor' module ActiveRecord module ConnectionAdapters # :nodoc: @@ -48,13 +49,17 @@ module ActiveRecord include DatabaseLimits include QueryCache include ActiveSupport::Callbacks + include MonitorMixin define_callbacks :checkout, :checkin attr_accessor :visitor - attr_reader :schema_cache + attr_reader :schema_cache, :last_use, :in_use + alias :in_use? :in_use def initialize(connection, logger = nil) #:nodoc: + super() + @active = nil @connection, @logger = connection, logger @query_cache_enabled = false @@ -63,6 +68,14 @@ module ActiveRecord @instrumenter = ActiveSupport::Notifications.instrumenter @visitor = nil @schema_cache = SchemaCache.new self + @in_use = false + end + + def lease + synchronize do + @in_use = true + @last_use = Time.now + end end # Returns the human-readable name of the adapter. Use mixed case - one -- cgit v1.2.3 From 134cc8f9398a9a67b80f3cc26651dcc5c073416c Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 13:15:20 -0800 Subject: Leased connections return false on second lease --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 4a9653c08c..deb24fd393 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -73,8 +73,10 @@ module ActiveRecord def lease synchronize do - @in_use = true - @last_use = Time.now + unless in_use + @in_use = true + @last_use = Time.now + end end end -- cgit v1.2.3 From f866f66b3073aaef4140f0c9ccbb8b3df707416e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 13:19:58 -0800 Subject: expire will set in_use to false --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index deb24fd393..ce3417ad94 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -69,6 +69,7 @@ module ActiveRecord @visitor = nil @schema_cache = SchemaCache.new self @in_use = false + @last_use = false end def lease @@ -80,6 +81,10 @@ module ActiveRecord end end + def expire + @in_use = false + end + # Returns the human-readable name of the adapter. Use mixed case - one # can always use downcase if needed. def adapter_name -- cgit v1.2.3 From b72b477c373b54200bfc49c8c0b0f9e42e7e68e3 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 13:40:27 -0800 Subject: Use connection lease to determine "checked_out" connections --- .../abstract/connection_pool.rb | 38 ++++++++++++---------- 1 file changed, 21 insertions(+), 17 deletions(-) (limited to 'activerecord/lib') 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 0c64ffdeaf..260d58b2e0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -82,7 +82,6 @@ module ActiveRecord @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 @connections = [] - @checked_out = [] @automatic_reconnect = true end @@ -216,20 +215,27 @@ module ActiveRecord # Checkout an available connection @connection_mutex.synchronize do loop do - conn = if @checked_out.size < @connections.size - checkout_existing_connection - elsif @connections.size < @size - checkout_new_connection - end - return conn if conn + conn = @connections.find { |c| c.lease } + + unless conn + if @connections.size < @size + conn = checkout_new_connection + conn.lease + end + end + + if conn + checkout_and_verify conn + return conn + end @queue.wait(@timeout) - if(@checked_out.size < @connections.size) + if(checked_out.size < @connections.size) next else clear_stale_cached_connections! - if @size == @checked_out.size + if @size == checked_out.size raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it." end end @@ -246,7 +252,7 @@ module ActiveRecord def checkin(conn) @connection_mutex.synchronize do conn.run_callbacks :checkin do - @checked_out.delete conn + conn.expire @queue.signal end end @@ -270,21 +276,19 @@ module ActiveRecord c = new_connection @connections << c - checkout_and_verify(c) - end - - def checkout_existing_connection - c = (@connections - @checked_out).first - checkout_and_verify(c) + c end def checkout_and_verify(c) c.run_callbacks :checkout do c.verify! - @checked_out << c end c end + + def checked_out + @connections.find_all { |c| c.in_use? } + end end # ConnectionHandler is a collection of ConnectionPool objects. It is used -- cgit v1.2.3 From 5725e397fe7ea34c7479863278d7db6f18e7658e Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 13:48:31 -0800 Subject: Rename `checked_out` to more descriptive `active_connections` --- .../active_record/connection_adapters/abstract/connection_pool.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 260d58b2e0..0b3971a0a6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -231,11 +231,11 @@ module ActiveRecord @queue.wait(@timeout) - if(checked_out.size < @connections.size) + if(active_connections.size < @connections.size) next else clear_stale_cached_connections! - if @size == checked_out.size + if @size == active_connections.size raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it." end end @@ -286,7 +286,7 @@ module ActiveRecord c end - def checked_out + def active_connections @connections.find_all { |c| c.in_use? } end end -- cgit v1.2.3 From ce3d8d646a7a4077907b1ec2bad5101840d989e5 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 13:56:24 -0800 Subject: Start implementing @reserved_connections in terms of connection leases. --- .../active_record/connection_adapters/abstract/connection_pool.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 0b3971a0a6..04465db61d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -97,7 +97,7 @@ module ActiveRecord # Check to see if there is an active connection in this connection # pool. def active_connection? - @reserved_connections.key? current_connection_id + active_connections.any? end # Signal that the thread is finished with the current connection. @@ -113,7 +113,7 @@ module ActiveRecord # connection when finished. def with_connection connection_id = current_connection_id - fresh_connection = true unless @reserved_connections[connection_id] + fresh_connection = true unless active_connection? yield connection ensure release_connection(connection_id) if fresh_connection @@ -121,7 +121,7 @@ module ActiveRecord # Returns true if a connection has already been opened. def connected? - !@connections.empty? + @connections.any? end # Disconnects all connections in the pool, and clears the pool. -- cgit v1.2.3 From 29d2040b2992c112ca475a7a56bcd7f2016252ce Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 14:40:37 -0800 Subject: AbstractAdapter#close can be called to add the connection back to the pool. --- .../abstract/connection_pool.rb | 1 + .../connection_adapters/abstract_adapter.rb | 29 ++++++++++++++-------- 2 files changed, 19 insertions(+), 11 deletions(-) (limited to 'activerecord/lib') 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 04465db61d..656073b47a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -275,6 +275,7 @@ module ActiveRecord raise ConnectionNotEstablished unless @automatic_reconnect c = new_connection + c.pool = self @connections << c c end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index ce3417ad94..1a4cc93d2d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -53,23 +53,25 @@ module ActiveRecord define_callbacks :checkout, :checkin - attr_accessor :visitor + attr_accessor :visitor, :pool attr_reader :schema_cache, :last_use, :in_use alias :in_use? :in_use - def initialize(connection, logger = nil) #:nodoc: + def initialize(connection, logger = nil, pool = nil) #:nodoc: super() - @active = nil - @connection, @logger = connection, logger + @active = nil + @connection = connection + @in_use = false + @instrumenter = ActiveSupport::Notifications.instrumenter + @last_use = false + @logger = logger + @open_transactions = 0 + @pool = pool + @query_cache = Hash.new { |h,sql| h[sql] = {} } @query_cache_enabled = false - @query_cache = Hash.new { |h,sql| h[sql] = {} } - @open_transactions = 0 - @instrumenter = ActiveSupport::Notifications.instrumenter - @visitor = nil - @schema_cache = SchemaCache.new self - @in_use = false - @last_use = false + @schema_cache = SchemaCache.new self + @visitor = nil end def lease @@ -256,6 +258,11 @@ module ActiveRecord "active_record_#{open_transactions}" end + # Check the connection back in to the connection pool + def close + pool.checkin self + end + protected def log(sql, name = "SQL", binds = []) -- cgit v1.2.3 From 0e2477b602b3aa5b66c849d19737a8b66c73f633 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 15:04:41 -0800 Subject: Automatic closure of connections in threads is deprecated. For example the following code is deprecated: Thread.new { Post.find(1) }.join It should be changed to close the database connection at the end of the thread: Thread.new { Post.find(1) Post.connection.close }.join Only people who spawn threads in their application code need to worry about this change. --- .../active_record/connection_adapters/abstract/connection_pool.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') 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 656073b47a..d127b3ebd8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -190,7 +190,13 @@ module ActiveRecord t.alive? }.map { |thread| thread.object_id } keys.each do |key| - checkin @reserved_connections[key] + conn = @reserved_connections[key] + ActiveSupport::Deprecation.warn(<<-eowarn) if conn.in_use? +Database connections will not be closed automatically, please close your +database connection at the end of the thread by calling `close` on your +connection. For example: ActiveRecord::Base.connection.close + eowarn + checkin conn @reserved_connections.delete(key) end end -- cgit v1.2.3 From c606fe2c6f1112d13b5ff01f3204e29cbdb09649 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Tue, 29 Nov 2011 15:30:04 -0800 Subject: push synchronization in to each method. Reduces method calls and makes it clear which methods are synchronized. --- .../abstract/connection_pool.rb | 54 ++++++++++++---------- 1 file changed, 29 insertions(+), 25 deletions(-) (limited to 'activerecord/lib') 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 d127b3ebd8..698da34d26 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1,7 +1,6 @@ require 'thread' require 'monitor' require 'set' -require 'active_support/core_ext/module/synchronization' require 'active_support/core_ext/module/deprecation' module ActiveRecord @@ -58,6 +57,8 @@ module ActiveRecord # * +wait_timeout+: number of seconds to block and wait for a connection # before giving up and raising a timeout error (default 5 seconds). class ConnectionPool + include MonitorMixin + attr_accessor :automatic_reconnect attr_reader :spec, :connections @@ -68,14 +69,14 @@ module ActiveRecord # # The default ConnectionPool maximum size is 5. def initialize(spec) + super() + @spec = spec # The cache of reserved connections mapped to threads @reserved_connections = {} - # The mutex used to synchronize pool access - @connection_mutex = Monitor.new - @queue = @connection_mutex.new_cond + @queue = new_cond @timeout = spec.config[:wait_timeout] || 5 # default max pool size to 5 @@ -121,37 +122,43 @@ module ActiveRecord # Returns true if a connection has already been opened. def connected? - @connections.any? + synchronize { @connections.any? } end # Disconnects all connections in the pool, and clears the pool. def disconnect! - @reserved_connections = {} - @connections.each do |conn| - checkin conn - conn.disconnect! + synchronize do + @reserved_connections = {} + @connections.each do |conn| + checkin conn + conn.disconnect! + end + @connections = [] end - @connections = [] end # Clears the cache which maps classes. def clear_reloadable_connections! - @reserved_connections = {} - @connections.each do |conn| - checkin conn - conn.disconnect! if conn.requires_reloading? - end - @connections.delete_if do |conn| - conn.requires_reloading? + synchronize do + @reserved_connections = {} + @connections.each do |conn| + checkin conn + conn.disconnect! if conn.requires_reloading? + end + @connections.delete_if do |conn| + conn.requires_reloading? + end end end # Verify active connections and remove and disconnect connections # associated with stale threads. def verify_active_connections! #:nodoc: - clear_stale_cached_connections! - @connections.each do |connection| - connection.verify! + synchronize do + clear_stale_cached_connections! + @connections.each do |connection| + connection.verify! + end end end @@ -219,7 +226,7 @@ connection. For example: ActiveRecord::Base.connection.close # within the timeout period. def checkout # Checkout an available connection - @connection_mutex.synchronize do + synchronize do loop do conn = @connections.find { |c| c.lease } @@ -256,7 +263,7 @@ connection. For example: ActiveRecord::Base.connection.close # +conn+: an AbstractAdapter object, which was obtained by earlier by # calling +checkout+ on this pool. def checkin(conn) - @connection_mutex.synchronize do + synchronize do conn.run_callbacks :checkin do conn.expire @queue.signal @@ -264,9 +271,6 @@ connection. For example: ActiveRecord::Base.connection.close end end - synchronize :clear_reloadable_connections!, :verify_active_connections!, - :connected?, :disconnect!, :with => :@connection_mutex - private def new_connection -- cgit v1.2.3 From a382d60f6abc94b6a965525872f858e48abc00de Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Wed, 30 Nov 2011 11:03:00 +0200 Subject: ActiveRecord::Relation#pluck method --- .../lib/active_record/associations/collection_proxy.rb | 2 +- activerecord/lib/active_record/base.rb | 2 +- activerecord/lib/active_record/relation/calculations.rb | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 3181ca9a32..80bc4990d2 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -39,7 +39,7 @@ module ActiveRecord instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|^respond_to|proxy_/ } delegate :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, - :lock, :readonly, :having, :to => :scoped + :lock, :readonly, :having, :pluck, :to => :scoped delegate :target, :load_target, :loaded?, :scoped, :to => :@association diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 484fe5fb16..2a02380591 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -449,7 +449,7 @@ module ActiveRecord #:nodoc: delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :to => :scoped - delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped + delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :to => :scoped def inherited(child_class) #:nodoc: # force attribute methods to be higher in inheritance hierarchy than other generated methods diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index af86771d2d..0f57e9831d 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -166,6 +166,23 @@ module ActiveRecord 0 end + # This method is designed to perform select by a single column as direct SQL query + # Returns Array with values of the specified column name + # The values has same data type as column. + # + # Examples: + # + # Person.pluck(:id) # SELECT people.id FROM people + # Person.uniq.pluck(:role) # SELECT DISTINCT role FROM people + # Person.where(:confirmed => true).limit(5).pluck(:id) + # + def pluck(column_name) + scope = self.select(column_name) + self.connection.select_values(scope.to_sql).map! do |value| + type_cast_using_column(value, column_for(column_name)) + end + end + private def perform_calculation(operation, column_name, options = {}) -- cgit v1.2.3 From 5b2eb64ceb08cd005dc06b721935de5853971473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 30 Nov 2011 18:38:28 +0100 Subject: Revert "Implement ArraySerializer and move old serialization API to a new namespace." This reverts commit 8896b4fdc8a543157cdf4dfc378607ebf6c10ab0. Conflicts: activemodel/lib/active_model.rb activemodel/lib/active_model/serializable.rb activemodel/lib/active_model/serializer.rb activemodel/test/cases/serializer_test.rb --- activerecord/lib/active_record/serialization.rb | 2 +- activerecord/lib/active_record/serializers/xml_serializer.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb index c23514c465..5ad40d8cd9 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -2,7 +2,7 @@ module ActiveRecord #:nodoc: # = Active Record Serialization module Serialization extend ActiveSupport::Concern - include ActiveModel::Serializable::JSON + include ActiveModel::Serializers::JSON def serializable_hash(options = nil) options = options.try(:clone) || {} diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 2da836ef0c..0e7f57aa43 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/hash/conversions' module ActiveRecord #:nodoc: module Serialization - include ActiveModel::Serializable::XML + include ActiveModel::Serializers::Xml # Builds an XML document to represent the model. Some configuration is # available through +options+. However more complicated cases should @@ -176,13 +176,13 @@ module ActiveRecord #:nodoc: end end - class XmlSerializer < ActiveModel::Serializable::XML::Serializer #:nodoc: + class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc: def initialize(*args) super options[:except] = Array.wrap(options[:except]) | Array.wrap(@serializable.class.inheritance_column) end - class Attribute < ActiveModel::Serializable::XML::Serializer::Attribute #:nodoc: + class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class type = if klass.serialized_attributes.key?(name) -- cgit v1.2.3 From 04b4fe77c925c0a3eb718254fac9c8ab403c37a3 Mon Sep 17 00:00:00 2001 From: Vijay Dev Date: Thu, 1 Dec 2011 01:04:47 +0530 Subject: s/is is/is --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 60bbc325df..98efdb90c8 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -213,7 +213,7 @@ module ActiveRecord # # If your model class is Project, the module is # named Project::GeneratedFeatureMethods. The GeneratedFeatureMethods module is - # is included in the model class immediately after the (anonymous) generated attributes methods + # included in the model class immediately after the (anonymous) generated attributes methods # module, meaning an association will override the methods for an attribute with the same name. # # === A word of warning -- cgit v1.2.3 From 40840aa9eadac27b940a9af49e86778071efe59b Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 14:08:32 +0000 Subject: fix indent --- activerecord/lib/active_record/attribute_methods/dirty.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 3eff3d54e3..f61e016f46 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -34,9 +34,9 @@ module ActiveRecord @previously_changed = changes @changed_attributes.clear end - rescue - IdentityMap.remove(self) if IdentityMap.enabled? - raise + rescue + IdentityMap.remove(self) if IdentityMap.enabled? + raise end # reload the record and clears changed attributes. -- cgit v1.2.3 From 4c33d517d9f4e6312ab23398cfaa6ca5a6177315 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 14:16:30 +0000 Subject: #id is an alias for whatever the primary key is --- activerecord/lib/active_record/attribute_methods/primary_key.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 93dae3ff86..a5c2849844 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -5,7 +5,7 @@ module ActiveRecord # Returns this record's primary key value wrapped in an Array if one is available def to_key - key = send(self.class.primary_key) + key = self.id [key] if key end -- cgit v1.2.3 From 61489dc6844539c86558f00670802c71927f9b51 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 15:09:50 +0000 Subject: Use inheritance to avoid special-case code for the 'id' method --- .../active_record/attribute_methods/primary_key.rb | 20 ++++++++++++++++++++ .../lib/active_record/attribute_methods/read.rb | 4 ---- .../lib/active_record/attribute_methods/write.rb | 4 ---- 3 files changed, 20 insertions(+), 8 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index a5c2849844..16362acfe9 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -9,7 +9,27 @@ module ActiveRecord [key] if key end + # Returns the primary key value + def id + read_attribute(self.class.primary_key) + end + alias _id id + + # Sets the primary key value + def id=(value) + write_attribute(self.class.primary_key, value) + end + + # Queries the primary key value + def id? + query_attribute(self.class.primary_key) + end + module ClassMethods + def dangerous_attribute_method?(method_name) + super && !['id', 'id=', 'id?', '_id'].include?(method_name) + end + # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the # primary_key_prefix_type setting, though. def primary_key diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 4a5afcd585..78dbbeb0e6 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -39,10 +39,6 @@ module ActiveRecord else define_read_method(attr_name, attr_name, columns_hash[attr_name]) end - - if attr_name == primary_key && attr_name != "id" - define_read_method('id', attr_name, columns_hash[attr_name]) - end end private diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index eb585ee906..b605c09889 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,10 +17,6 @@ module ActiveRecord write_attribute(attr_name, new_value) end end - - if attr_name == primary_key && attr_name != "id" - generated_attribute_methods.module_eval("alias :id= :'#{primary_key}='") - end end end -- cgit v1.2.3 From f4853dc17490c88966721ca1ad422baf6ae49745 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 15:52:09 +0000 Subject: Extract attribute serialization code into a separate module --- activerecord/lib/active_record.rb | 1 + .../lib/active_record/attribute_methods/read.rb | 33 +++-------------- .../attribute_methods/serialization.rb | 42 ++++++++++++++++++++++ activerecord/lib/active_record/base.rb | 1 + 4 files changed, 49 insertions(+), 28 deletions(-) create mode 100644 activerecord/lib/active_record/attribute_methods/serialization.rb (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 3572c640eb..bf28cffbbe 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -92,6 +92,7 @@ module ActiveRecord autoload :Read autoload :TimeZoneConversion autoload :Write + autoload :Serialization end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 78dbbeb0e6..d3ef7f0ae5 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -34,22 +34,12 @@ module ActiveRecord protected def define_method_attribute(attr_name) - if serialized_attributes.include?(attr_name) - define_read_method_for_serialized_attribute(attr_name) - else - define_read_method(attr_name, attr_name, columns_hash[attr_name]) - end + define_read_method(attr_name, attr_name, columns_hash[attr_name]) end private def cacheable_column?(column) - serialized_attributes.include?(column.name) || attribute_types_cached_by_default.include?(column.type) - end - - # Define read method for serialized attribute. - def define_read_method_for_serialized_attribute(attr_name) - access_code = "@attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}']" - generated_attribute_methods.module_eval("def _#{attr_name}; #{access_code}; end; alias #{attr_name} _#{attr_name}", __FILE__, __LINE__) + attribute_types_cached_by_default.include?(column.type) end # Define an attribute reader method. Cope with nil column. @@ -107,28 +97,15 @@ module ActiveRecord value = @attributes[attr_name] unless value.nil? if column = column_for_attribute(attr_name) - if unserializable_attribute?(attr_name, column) - unserialize_attribute(attr_name) - else - column.type_cast(value) - end + type_cast_attribute(column, value) else value end end end - # Returns true if the attribute is of a text column and marked for serialization. - def unserializable_attribute?(attr_name, column) - column.text? && self.class.serialized_attributes.include?(attr_name) - end - - # Returns the unserialized object of the attribute. - def unserialize_attribute(attr_name) - coder = self.class.serialized_attributes[attr_name] - unserialized_object = coder.load(@attributes[attr_name]) - - @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object + def type_cast_attribute(column, value) #:nodoc: + column.type_cast(value) end private diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb new file mode 100644 index 0000000000..686754fbc2 --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -0,0 +1,42 @@ +module ActiveRecord + module AttributeMethods + module Serialization + extend ActiveSupport::Concern + + module ClassMethods + def define_method_attribute(attr_name) + if serialized_attributes.include?(attr_name) + generated_attribute_methods.module_eval(<<-CODE, __FILE__, __LINE__) + def _#{attr_name} + @attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}'] + end + alias #{attr_name} _#{attr_name} + CODE + else + super + end + end + + def cacheable_column?(column) + serialized_attributes.include?(column.name) || super + end + end + + def type_cast_attribute(column, value) + coder = self.class.serialized_attributes[column.name] + + if column.text? && coder + unserialized_object = coder.load(@attributes[column.name]) + + if @attributes.frozen? + unserialized_object + else + @attributes[column.name] = unserialized_object + end + else + super + end + end + end + end +end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2a02380591..f8f10d651f 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2219,6 +2219,7 @@ MSG include AttributeMethods::PrimaryKey include AttributeMethods::TimeZoneConversion include AttributeMethods::Dirty + include AttributeMethods::Serialization include ActiveModel::MassAssignmentSecurity include Callbacks, ActiveModel::Observing, Timestamp include Associations, NamedScope -- cgit v1.2.3 From 6c63f1aa44780c887dd3e52765e86588911c2802 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 16:00:19 +0000 Subject: Move some serialization stuff out of Base --- .../attribute_methods/serialization.rb | 42 ++++++++++++++++++++++ activerecord/lib/active_record/base.rb | 40 --------------------- 2 files changed, 42 insertions(+), 40 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 686754fbc2..001620d889 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -3,7 +3,41 @@ module ActiveRecord module Serialization extend ActiveSupport::Concern + included do + # Returns a hash of all the attributes that have been specified for serialization as + # keys and their class restriction as values. + class_attribute :serialized_attributes + self.serialized_attributes = {} + end + module ClassMethods + # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object, + # then specify the name of that attribute using this method and it will be handled automatically. + # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that + # class on retrieval or SerializationTypeMismatch will be raised. + # + # ==== Parameters + # + # * +attr_name+ - The field name that should be serialized. + # * +class_name+ - Optional, class name that the object type should be equal to. + # + # ==== Example + # # Serialize a preferences attribute + # class User < ActiveRecord::Base + # serialize :preferences + # end + def serialize(attr_name, class_name = Object) + coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) } + class_name + else + Coders::YAMLColumn.new(class_name) + end + + # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy + # has its own hash of own serialized attributes + self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder) + end + def define_method_attribute(attr_name) if serialized_attributes.include?(attr_name) generated_attribute_methods.module_eval(<<-CODE, __FILE__, __LINE__) @@ -22,6 +56,14 @@ module ActiveRecord end end + def set_serialized_attributes + sattrs = self.class.serialized_attributes + + sattrs.each do |key, coder| + @attributes[key] = coder.load @attributes[key] if @attributes.key?(key) + end + end + def type_cast_attribute(column, value) coder = self.class.serialized_attributes[column.name] diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index f8f10d651f..2c7cb09d7a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -433,11 +433,6 @@ module ActiveRecord #:nodoc: class_attribute :default_scopes, :instance_writer => false self.default_scopes = [] - # Returns a hash of all the attributes that have been specified for serialization as - # keys and their class restriction as values. - class_attribute :serialized_attributes - self.serialized_attributes = {} - class_attribute :_attr_readonly, :instance_writer => false self._attr_readonly = [] @@ -560,33 +555,6 @@ module ActiveRecord #:nodoc: self._attr_readonly end - # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object, - # then specify the name of that attribute using this method and it will be handled automatically. - # The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that - # class on retrieval or SerializationTypeMismatch will be raised. - # - # ==== Parameters - # - # * +attr_name+ - The field name that should be serialized. - # * +class_name+ - Optional, class name that the object type should be equal to. - # - # ==== Example - # # Serialize a preferences attribute - # class User < ActiveRecord::Base - # serialize :preferences - # end - def serialize(attr_name, class_name = Object) - coder = if [:load, :dump].all? { |x| class_name.respond_to?(x) } - class_name - else - Coders::YAMLColumn.new(class_name) - end - - # merge new serialized attribute and create new hash to ensure that each class in inheritance hierarchy - # has its own hash of own serialized attributes - self.serialized_attributes = serialized_attributes.merge(attr_name.to_s => coder) - end - def deprecated_property_setter(property, value, block) #:nodoc: if block ActiveSupport::Deprecation.warn( @@ -2004,14 +1972,6 @@ MSG nil end - def set_serialized_attributes - sattrs = self.class.serialized_attributes - - sattrs.each do |key, coder| - @attributes[key] = coder.load @attributes[key] if @attributes.key?(key) - end - end - # Sets the attribute used for single table inheritance to this class name if this is not the # ActiveRecord::Base descendant. # Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to -- cgit v1.2.3 From efcc95acb71a68b9edd7c7c77550abdf412cc5ad Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 16:06:42 +0000 Subject: No longer need to undef id as we are defining it ourselves --- activerecord/lib/active_record/attribute_methods/read.rb | 3 --- 1 file changed, 3 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index d3ef7f0ae5..69507b5838 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -8,9 +8,6 @@ module ActiveRecord included do cattr_accessor :attribute_types_cached_by_default, :instance_writer => false self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - - # Undefine id so it can be used as an attribute name - undef_method(:id) if method_defined?(:id) end module ClassMethods -- cgit v1.2.3 From 035b4244bae8620515d3757fdc3be42ac77dddec Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 16:12:51 +0000 Subject: Don't need second param --- activerecord/lib/active_record/attribute_methods/read.rb | 6 +++--- activerecord/lib/active_record/attribute_methods/serialization.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 69507b5838..788d671ad1 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -94,15 +94,15 @@ module ActiveRecord value = @attributes[attr_name] unless value.nil? if column = column_for_attribute(attr_name) - type_cast_attribute(column, value) + type_cast_attribute(column) else value end end end - def type_cast_attribute(column, value) #:nodoc: - column.type_cast(value) + def type_cast_attribute(column) #:nodoc: + column.type_cast(@attributes[column.name]) end private diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 001620d889..ac65ef94f9 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -64,7 +64,7 @@ module ActiveRecord end end - def type_cast_attribute(column, value) + def type_cast_attribute(column) coder = self.class.serialized_attributes[column.name] if column.text? && coder -- cgit v1.2.3 From 7895182d0fce11131024305f53d0cbb32817e65c Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 22:31:52 +0000 Subject: omg computer science! Implement a mini state machine for serialized attributes. This means we do not have to deserialize the values upon initialization, which means that if we never actually access the attribute, we never have to deserialize it. --- .../attribute_methods/serialization.rb | 52 ++++++++++++++-------- .../lib/active_record/attribute_methods/write.rb | 14 ++++-- activerecord/lib/active_record/base.rb | 4 +- 3 files changed, 46 insertions(+), 24 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index ac65ef94f9..bc7e9d7a94 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -10,6 +10,26 @@ module ActiveRecord self.serialized_attributes = {} end + class Attribute < Struct.new(:coder, :value, :state) + def unserialized_value + state == :serialized ? unserialize : value + end + + def serialized_value + state == :unserialized ? serialize : value + end + + def unserialize + self.state = :unserialized + self.value = coder.load(value) + end + + def serialize + self.state = :serialized + self.value = coder.dump(value) + end + end + module ClassMethods # If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object, # then specify the name of that attribute using this method and it will be handled automatically. @@ -42,7 +62,7 @@ module ActiveRecord if serialized_attributes.include?(attr_name) generated_attribute_methods.module_eval(<<-CODE, __FILE__, __LINE__) def _#{attr_name} - @attributes_cache['#{attr_name}'] ||= @attributes['#{attr_name}'] + @attributes['#{attr_name}'].unserialized_value end alias #{attr_name} _#{attr_name} CODE @@ -50,31 +70,27 @@ module ActiveRecord super end end - - def cacheable_column?(column) - serialized_attributes.include?(column.name) || super - end end def set_serialized_attributes - sattrs = self.class.serialized_attributes - - sattrs.each do |key, coder| - @attributes[key] = coder.load @attributes[key] if @attributes.key?(key) + self.class.serialized_attributes.each do |key, coder| + if @attributes.key?(key) + @attributes[key] = Attribute.new(coder, @attributes[key], :serialized) + end end end def type_cast_attribute(column) - coder = self.class.serialized_attributes[column.name] - - if column.text? && coder - unserialized_object = coder.load(@attributes[column.name]) + if column.text? && self.class.serialized_attributes.include?(column.name) + @attributes[column.name].unserialized_value + else + super + end + end - if @attributes.frozen? - unserialized_object - else - @attributes[column.name] = unserialized_object - end + def type_cast_attribute_for_write(column, attr_name, value) + if column && coder = self.class.serialized_attributes[column.name] + Attribute.new(coder, value, :unserialized) else super end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index b605c09889..650156f3cf 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -28,10 +28,8 @@ module ActiveRecord @attributes_cache.delete(attr_name) column = column_for_attribute(attr_name) - if column && column.number? - @attributes[attr_name] = convert_number_column_value(value) - elsif column || @attributes.has_key?(attr_name) - @attributes[attr_name] = value + if column || @attributes.has_key?(attr_name) + @attributes[attr_name] = type_cast_attribute_for_write(column, attr_name, value) else raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" end @@ -43,6 +41,14 @@ module ActiveRecord def attribute=(attribute_name, value) write_attribute(attribute_name, value) end + + def type_cast_attribute_for_write(column, attr_name, value) + if column && column.number? + convert_number_column_value(value) + else + value + end + end end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2c7cb09d7a..ee2833c5dc 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2003,8 +2003,8 @@ MSG if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name)) - value = if coder = klass.serialized_attributes[name] - coder.dump @attributes[name] + value = if klass.serialized_attributes.include?(name) + @attributes[name].serialized_value else # FIXME: we need @attributes to be used consistently. # If the values stored in @attributes were already type -- cgit v1.2.3 From d5f7884dc5e74cd876f031ea2d4467cec18defbb Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 22:45:35 +0000 Subject: Don't check column type, you might implement a custom coder that serializes to a different type --- activerecord/lib/active_record/attribute_methods/serialization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index bc7e9d7a94..b084fdc25b 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -81,7 +81,7 @@ module ActiveRecord end def type_cast_attribute(column) - if column.text? && self.class.serialized_attributes.include?(column.name) + if self.class.serialized_attributes.include?(column.name) @attributes[column.name].unserialized_value else super -- cgit v1.2.3 From 7a4949e7d5be37f64bf6a1f9dda6f427fbb5ac40 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Wed, 30 Nov 2011 23:03:45 +0000 Subject: consistency --- .../lib/active_record/attribute_methods/read.rb | 20 ++++++++++---------- .../active_record/attribute_methods/serialization.rb | 8 ++++---- .../lib/active_record/attribute_methods/write.rb | 4 ++-- 3 files changed, 16 insertions(+), 16 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 788d671ad1..4fdfe77bab 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -91,21 +91,21 @@ module ActiveRecord def _read_attribute(attr_name) attr_name = attr_name.to_s attr_name = self.class.primary_key if attr_name == 'id' - value = @attributes[attr_name] - unless value.nil? - if column = column_for_attribute(attr_name) - type_cast_attribute(column) + + unless @attributes[attr_name].nil? + type_cast_attribute(column_for_attribute(attr_name), @attributes[attr_name]) + end + end + + private + def type_cast_attribute(column, value) + if column + column.type_cast(value) else value end end - end - - def type_cast_attribute(column) #:nodoc: - column.type_cast(@attributes[column.name]) - end - private def attribute(attribute_name) read_attribute(attribute_name) end diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index b084fdc25b..d1eb3d5bfc 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -80,15 +80,15 @@ module ActiveRecord end end - def type_cast_attribute(column) - if self.class.serialized_attributes.include?(column.name) - @attributes[column.name].unserialized_value + def type_cast_attribute(column, value) + if column && self.class.serialized_attributes[column.name] + value.unserialized_value else super end end - def type_cast_attribute_for_write(column, attr_name, value) + def type_cast_attribute_for_write(column, value) if column && coder = self.class.serialized_attributes[column.name] Attribute.new(coder, value, :unserialized) else diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 650156f3cf..07499db9f0 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -29,7 +29,7 @@ module ActiveRecord column = column_for_attribute(attr_name) if column || @attributes.has_key?(attr_name) - @attributes[attr_name] = type_cast_attribute_for_write(column, attr_name, value) + @attributes[attr_name] = type_cast_attribute_for_write(column, value) else raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'" end @@ -42,7 +42,7 @@ module ActiveRecord write_attribute(attribute_name, value) end - def type_cast_attribute_for_write(column, attr_name, value) + def type_cast_attribute_for_write(column, value) if column && column.number? convert_number_column_value(value) else -- cgit v1.2.3 From 4e380828ffa8279740256164c6b83148cf0cbf13 Mon Sep 17 00:00:00 2001 From: Julius de Bruijn Date: Wed, 30 Nov 2011 17:56:16 +0100 Subject: If the table behind has no primary key, do not ask again and just return nil. --- activerecord/lib/active_record/attribute_methods/primary_key.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 16362acfe9..6eff716703 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -33,7 +33,8 @@ module ActiveRecord # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the # primary_key_prefix_type setting, though. def primary_key - @primary_key ||= reset_primary_key + @primary_key = reset_primary_key unless defined? @primary_key + @primary_key end # Returns a quoted version of the primary key name, used to construct SQL statements. -- cgit v1.2.3 From 52eedf5e2b0b39026ab5c63c5f28fe70f88ee8db Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Thu, 1 Dec 2011 01:15:16 +0000 Subject: Add hackery to make Syck use encode_with/init_with. Fixes 1.8 after recent changes to attribute serialization. --- activerecord/lib/active_record/base.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ee2833c5dc..76aa121ade 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1935,6 +1935,26 @@ MSG "#<#{self.class} #{inspection}>" end + # Hackery to accomodate Syck. Remove for 4.0. + def to_yaml(opts = {}) #:nodoc: + if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck? + super + else + coder = {} + encode_with(coder) + YAML.quick_emit(self, opts) do |out| + out.map(taguri, to_yaml_style) do |map| + coder.each { |k, v| map.add(k, v) } + end + end + end + end + + # Hackery to accomodate Syck. Remove for 4.0. + def yaml_initialize(tag, coder) #:nodoc: + init_with(coder) + end + protected def clone_attributes(reader_method = :read_attribute, attributes = {}) attribute_names.each do |name| -- cgit v1.2.3