diff options
Diffstat (limited to 'activerecord')
12 files changed, 154 insertions, 119 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index a1e34a3aa1..889c80386f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -194,6 +194,7 @@ module ActiveRecord # Returns the column object for the named attribute. def column_for_attribute(name) + # FIXME: should this return a null object for columns that don't exist? self.class.columns_hash[name.to_s] end diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index 948809c65a..1e841dc8e0 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -10,8 +10,11 @@ module ActiveRecord end def query_attribute(attr_name) - unless value = read_attribute(attr_name) - false + value = read_attribute(attr_name) + + case value + when true then true + when false, nil then false else column = self.class.columns_hash[attr_name] if column.nil? diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index fde55b95da..6b0230384b 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -37,30 +37,16 @@ module ActiveRecord alias_method :raw_write_attribute, :write_attribute private - # Handle *= for method_missing. - def attribute=(attribute_name, value) - write_attribute(attribute_name, value) - end + # Handle *= for method_missing. + def attribute=(attribute_name, value) + write_attribute(attribute_name, value) + end - def type_cast_attribute_for_write(column, value) - if column && column.number? - convert_number_column_value(value) - else - value - end - end + def type_cast_attribute_for_write(column, value) + return value unless column - def convert_number_column_value(value) - if value == false - 0 - elsif value == true - 1 - elsif value.is_a?(String) && value.blank? - nil - else - value - end - end + column.type_cast_for_write value + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 2ecb198edb..34d88edff3 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -66,6 +66,21 @@ module ActiveRecord end end + # Casts a Ruby value to something appropriate for writing to the database. + def type_cast_for_write(value) + return value unless number? + + if value == false + 0 + elsif value == true + 1 + elsif value.is_a?(String) && value.blank? + nil + else + value + end + end + # Casts value (which is a String) to an appropriate instance. def type_cast(value) return nil if value.nil? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index fe77eb725e..194c814e5b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -14,7 +14,7 @@ module ActiveRecord # Forward any unused config params to PGconn.connect. [:statement_limit, :encoding, :min_messages, :schema_search_path, - :schema_order, :adapter, :pool, :wait_timeout, + :schema_order, :adapter, :pool, :wait_timeout, :template, :reaping_frequency].each do |key| conn_params.delete key end diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index fc76410499..1f8c4fc203 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -11,7 +11,10 @@ module ActiveRecord # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on # our own EXPLAINs now matter how loopingly beautiful that would be. - IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN) + # + # On the other hand, we want to monitor the performance of our real database + # queries, not the performance of the access to the query cache. + IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN CACHE) def ignore_payload?(payload) payload[:exception] || IGNORED_PAYLOADS.include?(payload[:name]) end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 257963c2ce..236ec563d2 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -4,6 +4,8 @@ require 'active_record/base' module ActiveRecord class SchemaMigration < ActiveRecord::Base + attr_accessible :version + def self.table_name Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix end diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index 7f1dba5095..2e60521638 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -162,8 +162,9 @@ module ActiveRecord #:nodoc: # # class IHaveMyOwnXML < ActiveRecord::Base # def to_xml(options = {}) + # require 'builder' # options[:indent] ||= 2 - # xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent]) + # xml = options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) # xml.instruct! unless options[:skip_instruct] # xml.level_one do # xml.tag!(:second_level, 'content') diff --git a/activerecord/lib/active_record/test_case.rb b/activerecord/lib/active_record/test_case.rb index 86687afdda..4d881f0f7d 100644 --- a/activerecord/lib/active_record/test_case.rb +++ b/activerecord/lib/active_record/test_case.rb @@ -14,7 +14,7 @@ module ActiveRecord end def teardown - ActiveRecord::SQLCounter.log.clear + SQLCounter.log.clear end def cleanup_identity_map @@ -30,5 +30,65 @@ module ActiveRecord assert_equal expected.to_s, actual.to_s, message end end + + def assert_sql(*patterns_to_match) + SQLCounter.log = [] + yield + SQLCounter.log + ensure + failed_patterns = [] + patterns_to_match.each do |pattern| + failed_patterns << pattern unless SQLCounter.log.any?{ |sql| pattern === sql } + end + assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" + end + + def assert_queries(num = 1) + SQLCounter.log = [] + yield + ensure + assert_equal num, SQLCounter.log.size, "#{SQLCounter.log.size} instead of #{num} queries were executed.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}" + end + + def assert_no_queries(&block) + prev_ignored_sql = SQLCounter.ignored_sql + SQLCounter.ignored_sql = [] + assert_queries(0, &block) + ensure + SQLCounter.ignored_sql = prev_ignored_sql + end + + end + + class SQLCounter + class << self + attr_accessor :ignored_sql, :log + end + + self.log = [] + + self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] + + # FIXME: this needs to be refactored so specific database can add their own + # ignored SQL. This ignored SQL is for Oracle. + ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] + + + attr_reader :ignore + + def initialize(ignore = Regexp.union(self.class.ignored_sql)) + @ignore = ignore + end + + def call(name, start, finish, message_id, values) + sql = values[:sql] + + # FIXME: this seems bad. we should probably have a better way to indicate + # the query was cached + return if 'CACHE' == values[:name] || ignore =~ sql + self.class.log << sql + end end + + ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) end diff --git a/activerecord/test/cases/explain_subscriber_test.rb b/activerecord/test/cases/explain_subscriber_test.rb new file mode 100644 index 0000000000..e118add44c --- /dev/null +++ b/activerecord/test/cases/explain_subscriber_test.rb @@ -0,0 +1,48 @@ +require 'cases/helper' + +if ActiveRecord::Base.connection.supports_explain? + class ExplainSubscriberTest < ActiveRecord::TestCase + SUBSCRIBER = ActiveRecord::ExplainSubscriber.new + + def test_collects_nothing_if_available_queries_for_explain_is_nil + with_queries(nil) do + SUBSCRIBER.call + assert_nil Thread.current[:available_queries_for_explain] + end + end + + def test_collects_nothing_if_the_payload_has_an_exception + with_queries([]) do |queries| + SUBSCRIBER.call(:exception => Exception.new) + assert queries.empty? + end + end + + def test_collects_nothing_for_ignored_payloads + with_queries([]) do |queries| + ActiveRecord::ExplainSubscriber::IGNORED_PAYLOADS.each do |ip| + SUBSCRIBER.call(:name => ip) + end + assert queries.empty? + end + end + + def test_collects_pairs_of_queries_and_binds + sql = 'select 1 from users' + binds = [1, 2] + with_queries([]) do |queries| + SUBSCRIBER.call(:name => 'SQL', :sql => sql, :binds => binds) + assert_equal 1, queries.size + assert_equal sql, queries[0][0] + assert_equal binds, queries[0][1] + end + end + + def with_queries(queries) + Thread.current[:available_queries_for_explain] = queries + yield queries + ensure + Thread.current[:available_queries_for_explain] = nil + end + end +end
\ No newline at end of file diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index 734d017b6e..9f5f012073 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -61,37 +61,6 @@ ensure ActiveRecord::Base.default_timezone = old_zone end -module ActiveRecord - class SQLCounter - cattr_accessor :ignored_sql - self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/] - - # FIXME: this needs to be refactored so specific database can add their own - # ignored SQL. This ignored SQL is for Oracle. - ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im] - - cattr_accessor :log - self.log = [] - - attr_reader :ignore - - def initialize(ignore = Regexp.union(self.class.ignored_sql)) - @ignore = ignore - end - - def call(name, start, finish, message_id, values) - sql = values[:sql] - - # FIXME: this seems bad. we should probably have a better way to indicate - # the query was cached - return if 'CACHE' == values[:name] || ignore =~ sql - self.class.log << sql - end - end - - ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new) -end - unless ENV['FIXTURE_DEBUG'] module ActiveRecord::TestFixtures::ClassMethods def try_to_load_dependency_with_silence(*args) diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index f0fefe6c48..94a13d386c 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -1,63 +1,10 @@ -require 'active_support/test_case' - -module ActiveRecord - # = Active Record Test Case - # - # Defines some test assertions to test against SQL queries. - class TestCase < ActiveSupport::TestCase #:nodoc: - setup :cleanup_identity_map - - def setup - cleanup_identity_map - end - - def teardown - ActiveRecord::SQLCounter.log.clear - end - - def cleanup_identity_map - ActiveRecord::IdentityMap.clear - end - - def assert_date_from_db(expected, actual, message = nil) - # SybaseAdapter doesn't have a separate column type just for dates, - # so the time is in the string and incorrectly formatted - if current_adapter?(:SybaseAdapter) - assert_equal expected.to_s, actual.to_date.to_s, message - else - assert_equal expected.to_s, actual.to_s, message - end - end - - def assert_sql(*patterns_to_match) - ActiveRecord::SQLCounter.log = [] - yield - ActiveRecord::SQLCounter.log - ensure - failed_patterns = [] - patterns_to_match.each do |pattern| - failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql } - end - assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" - end - - def assert_queries(num = 1) - ActiveRecord::SQLCounter.log = [] - yield - ensure - assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}" - end - - def assert_no_queries(&block) - prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql - ActiveRecord::SQLCounter.ignored_sql = [] - assert_queries(0, &block) - ensure - ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql - end +require 'active_support/deprecation' +ActiveSupport::Deprecation.silence do + require 'active_record/test_case' +end - def sqlite3? connection - connection.class.name.split('::').last == "SQLite3Adapter" - end +ActiveRecord::TestCase.class_eval do + def sqlite3? connection + connection.class.name.split('::').last == "SQLite3Adapter" end end |