diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-11-02 03:52:15 +0530 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-11-02 03:52:15 +0530 |
commit | 1147453fce0890ea229c3af5f43c909ebe53061e (patch) | |
tree | 221d816ef0c908044fd6029950ccad064866ab8f /activerecord | |
parent | a3aa0c17ef8594a0084511f4852be7b5dc66e5e2 (diff) | |
parent | 5a02f0bccf55191c2cfbcc69bd8165df6d7a2012 (diff) | |
download | rails-1147453fce0890ea229c3af5f43c909ebe53061e.tar.gz rails-1147453fce0890ea229c3af5f43c909ebe53061e.tar.bz2 rails-1147453fce0890ea229c3af5f43c909ebe53061e.zip |
Merge commit 'mainstream/master'
Conflicts:
railties/doc/guides/html/layouts_and_rendering.html
railties/doc/guides/source/active_record_basics.txt
railties/doc/guides/source/layouts_and_rendering.txt
Diffstat (limited to 'activerecord')
-rw-r--r-- | activerecord/CHANGELOG | 7 | ||||
-rwxr-xr-x | activerecord/lib/active_record/associations.rb | 21 | ||||
-rw-r--r-- | activerecord/lib/active_record/attribute_methods.rb | 2 | ||||
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 15 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb | 42 | ||||
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb | 147 | ||||
-rw-r--r-- | activerecord/lib/active_record/schema_dumper.rb | 18 | ||||
-rw-r--r-- | activerecord/test/cases/attribute_methods_test.rb | 11 | ||||
-rwxr-xr-x | activerecord/test/cases/base_test.rb | 6 | ||||
-rw-r--r-- | activerecord/test/cases/finder_test.rb | 11 |
10 files changed, 157 insertions, 123 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index fec110d569..4ca062b535 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,3 +1,10 @@ +*2.2.1 [RC2 or 2.2 final]* + +* Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster] + +* Fixed that serialized strings should never be type-casted (i.e. turning "Yes" to a boolean) #857 [Andreas Korth] + + *2.2.0 [RC1] (October 24th, 2008)* * Skip collection ids reader optimization if using :finder_sql [Jeremy Kemper] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 52f6a04da1..84caca3518 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1596,16 +1596,19 @@ module ActiveRecord reflection end + mattr_accessor :valid_keys_for_has_and_belongs_to_many_association + @@valid_keys_for_has_and_belongs_to_many_association = [ + :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, + :select, :conditions, :include, :order, :group, :limit, :offset, + :uniq, + :finder_sql, :delete_sql, :insert_sql, + :before_add, :after_add, :before_remove, :after_remove, + :extend, :readonly, + :validate + ] + def create_has_and_belongs_to_many_reflection(association_id, options, &extension) - options.assert_valid_keys( - :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key, - :select, :conditions, :include, :order, :group, :limit, :offset, - :uniq, - :finder_sql, :delete_sql, :insert_sql, - :before_add, :after_add, :before_remove, :after_remove, - :extend, :readonly, - :validate - ) + options.assert_valid_keys(valid_keys_for_has_and_belongs_to_many_association) options[:extend] = create_extension_modules(association_id, extension, options[:extend]) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1c753524de..177d156834 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -233,7 +233,7 @@ module ActiveRecord method_name = method_id.to_s if self.class.private_method_defined?(method_name) - raise NoMethodError("Attempt to call private method", method_name, args) + raise NoMethodError.new("Attempt to call private method", method_name, args) end # If we haven't generated any methods yet, generate them, then diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 039e14435f..a36a137f0d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1764,7 +1764,7 @@ module ActiveRecord #:nodoc: # # Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future # attempts to use it do not run through method_missing. - def method_missing(method_id, *arguments) + def method_missing(method_id, *arguments, &block) if match = DynamicFinderMatch.match(method_id) attribute_names = match.attribute_names super unless all_attributes_exists?(attribute_names) @@ -1819,7 +1819,7 @@ module ActiveRecord #:nodoc: end end }, __FILE__, __LINE__ - send(method_id, *arguments) + send(method_id, *arguments, &block) end else super @@ -2410,10 +2410,11 @@ module ActiveRecord #:nodoc: # be made (since they can't be persisted). def destroy unless new_record? - connection.delete <<-end_sql, "#{self.class.name} Destroy" - DELETE FROM #{self.class.quoted_table_name} - WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} - end_sql + connection.delete( + "DELETE FROM #{self.class.quoted_table_name} " + + "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}", + "#{self.class.name} Destroy" + ) end freeze @@ -2938,7 +2939,7 @@ module ActiveRecord #:nodoc: end def object_from_yaml(string) - return string unless string.is_a?(String) + return string unless string.is_a?(String) && string =~ /^---/ YAML::load(string) rescue string end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 2fc50b9bfa..950bd72101 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -15,7 +15,7 @@ module ActiveRecord method_names.each do |method_name| base.class_eval <<-end_code, __FILE__, __LINE__ def #{method_name}_with_query_dirty(*args) - clear_query_cache if query_cache_enabled + clear_query_cache if @query_cache_enabled #{method_name}_without_query_dirty(*args) end @@ -25,38 +25,24 @@ module ActiveRecord end end - def query_cache_enabled - Thread.current['query_cache_enabled'] - end - - def query_cache_enabled=(flag) - Thread.current['query_cache_enabled'] = flag - end - - def query_cache - Thread.current['query_cache'] - end - - def query_cache=(cache) - Thread.current['query_cache'] = cache - end + attr_reader :query_cache, :query_cache_enabled # Enable the query cache within the block. def cache - old, self.query_cache_enabled = query_cache_enabled, true - self.query_cache ||= {} + old, @query_cache_enabled = @query_cache_enabled, true + @query_cache ||= {} yield ensure clear_query_cache - self.query_cache_enabled = old + @query_cache_enabled = old end # Disable the query cache within the block. def uncached - old, self.query_cache_enabled = query_cache_enabled, false + old, @query_cache_enabled = @query_cache_enabled, false yield ensure - self.query_cache_enabled = old + @query_cache_enabled = old end # Clears the query cache. @@ -66,11 +52,11 @@ module ActiveRecord # the same SQL query and repeatedly return the same result each time, silently # undermining the randomness you were expecting. def clear_query_cache - query_cache.clear if query_cache + @query_cache.clear if @query_cache end def select_all_with_query_cache(*args) - if query_cache_enabled + if @query_cache_enabled cache_sql(args.first) { select_all_without_query_cache(*args) } else select_all_without_query_cache(*args) @@ -78,8 +64,8 @@ module ActiveRecord end def columns_with_query_cache(*args) - if query_cache_enabled - query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args) + if @query_cache_enabled + @query_cache["SHOW FIELDS FROM #{args.first}"] ||= columns_without_query_cache(*args) else columns_without_query_cache(*args) end @@ -88,11 +74,11 @@ module ActiveRecord private def cache_sql(sql) result = - if query_cache.has_key?(sql) + if @query_cache.has_key?(sql) log_info(sql, "CACHE", 0.0) - query_cache[sql] + @query_cache[sql] else - query_cache[sql] = yield + @query_cache[sql] = yield end if Array === result diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index bebab5d05d..60ec01b95e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -68,72 +68,6 @@ module ActiveRecord super end - # Escapes binary strings for bytea input to the database. - def self.string_to_binary(value) - if PGconn.respond_to?(:escape_bytea) - self.class.module_eval do - define_method(:string_to_binary) do |value| - PGconn.escape_bytea(value) if value - end - end - else - self.class.module_eval do - define_method(:string_to_binary) do |value| - if value - result = '' - value.each_byte { |c| result << sprintf('\\\\%03o', c) } - result - end - end - end - end - self.class.string_to_binary(value) - end - - # Unescapes bytea output from a database to the binary string it represents. - def self.binary_to_string(value) - # In each case, check if the value actually is escaped PostgreSQL bytea output - # or an unescaped Active Record attribute that was just written. - if PGconn.respond_to?(:unescape_bytea) - self.class.module_eval do - define_method(:binary_to_string) do |value| - if value =~ /\\\d{3}/ - PGconn.unescape_bytea(value) - else - value - end - end - end - else - self.class.module_eval do - define_method(:binary_to_string) do |value| - if value =~ /\\\d{3}/ - result = '' - i, max = 0, value.size - while i < max - char = value[i] - if char == ?\\ - if value[i+1] == ?\\ - char = ?\\ - i += 1 - else - char = value[i+1..i+3].oct - i += 3 - end - end - result << char - i += 1 - end - result - else - value - end - end - end - end - self.class.binary_to_string(value) - end - # Maps PostgreSQL-specific data types to logical Rails types. def simplified_type(field_type) case field_type @@ -347,10 +281,78 @@ module ActiveRecord # QUOTING ================================================== + # Escapes binary strings for bytea input to the database. + def escape_bytea(value) + if PGconn.respond_to?(:escape_bytea) + self.class.instance_eval do + define_method(:escape_bytea) do |value| + PGconn.escape_bytea(value) if value + end + end + else + self.class.instance_eval do + define_method(:escape_bytea) do |value| + if value + result = '' + value.each_byte { |c| result << sprintf('\\\\%03o', c) } + result + end + end + end + end + escape_bytea(value) + end + + # Unescapes bytea output from a database to the binary string it represents. + # NOTE: This is NOT an inverse of escape_bytea! This is only to be used + # on escaped binary output from database drive. + def unescape_bytea(value) + # In each case, check if the value actually is escaped PostgreSQL bytea output + # or an unescaped Active Record attribute that was just written. + if PGconn.respond_to?(:unescape_bytea) + self.class.instance_eval do + define_method(:unescape_bytea) do |value| + if value =~ /\\\d{3}/ + PGconn.unescape_bytea(value) + else + value + end + end + end + else + self.class.instance_eval do + define_method(:unescape_bytea) do |value| + if value =~ /\\\d{3}/ + result = '' + i, max = 0, value.size + while i < max + char = value[i] + if char == ?\\ + if value[i+1] == ?\\ + char = ?\\ + i += 1 + else + char = value[i+1..i+3].oct + i += 3 + end + end + result << char + i += 1 + end + result + else + value + end + end + end + end + unescape_bytea(value) + end + # Quotes PostgreSQL-specific data types for SQL input. def quote(value, column = nil) #:nodoc: if value.kind_of?(String) && column && column.type == :binary - "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'" + "#{quoted_string_prefix}'#{escape_bytea(value)}'" elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/ "xml '#{quote_string(value)}'" elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/ @@ -463,11 +465,20 @@ module ActiveRecord # create a 2D array representing the result set def result_as_array(res) #:nodoc: + # check if we have any binary column and if they need escaping + unescape_col = [] + for j in 0...res.nfields do + # unescape string passed BYTEA field (OID == 17) + unescape_col << ( res.ftype(j)==17 ) + end + ary = [] for i in 0...res.ntuples do ary << [] for j in 0...res.nfields do - ary[i] << res.getvalue(i,j) + data = res.getvalue(i,j) + data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String) + ary[i] << data end end return ary diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 4f96e225c1..2181bdf2dd 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -159,13 +159,19 @@ HEADER end def indexes(table, stream) - indexes = @connection.indexes(table) - indexes.each do |index| - stream.print " add_index #{index.table.inspect}, #{index.columns.inspect}, :name => #{index.name.inspect}" - stream.print ", :unique => true" if index.unique + if (indexes = @connection.indexes(table)).any? + add_index_statements = indexes.map do |index| + statment_parts = [ ('add_index ' + index.table.inspect) ] + statment_parts << index.columns.inspect + statment_parts << (':name => ' + index.name.inspect) + statment_parts << ':unique => true' if index.unique + + ' ' + statment_parts.join(', ') + end + + stream.puts add_index_statements.sort.join("\n") stream.puts end - stream.puts unless indexes.empty? end end -end +end
\ No newline at end of file diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 160716f944..77ee8d8fc4 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -233,8 +233,9 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "The pros and cons of programming naked.") assert !topic.respond_to?(:title) - assert_raise(NoMethodError) { topic.title } - topic.send(:title) + exception = assert_raise(NoMethodError) { topic.title } + assert_equal "Attempt to call private method", exception.message + assert_equal "I'm private", topic.send(:title) end def test_write_attributes_respect_access_control @@ -242,7 +243,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new assert !topic.respond_to?(:title=) - assert_raise(NoMethodError) { topic.title = "Pants"} + exception = assert_raise(NoMethodError) { topic.title = "Pants"} + assert_equal "Attempt to call private method", exception.message topic.send(:title=, "Very large pants") end @@ -251,7 +253,8 @@ class AttributeMethodsTest < ActiveRecord::TestCase topic = @target.new(:title => "Isaac Newton's pants") assert !topic.respond_to?(:title?) - assert_raise(NoMethodError) { topic.title? } + exception = assert_raise(NoMethodError) { topic.title? } + assert_equal "Attempt to call private method", exception.message assert topic.send(:title?) end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index d512834237..da9f2742d8 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1429,6 +1429,12 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.create("content" => myobj).reload assert_equal(myobj, topic.content) end + + def test_serialized_string_attribute + myobj = "Yes" + topic = Topic.create("content" => myobj).reload + assert_equal(myobj, topic.content) + end def test_nil_serialized_attribute_with_class_constraint myobj = MyObject.new('value1', 'value2') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 853474916c..153880afbd 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -846,6 +846,17 @@ class FinderTest < ActiveRecord::TestCase assert !c.new_record? end + def test_find_or_create_should_work_with_block_on_first_call + class << Company + undef_method(:find_or_create_by_name) if method_defined?(:find_or_create_by_name) + end + c = Company.find_or_create_by_name(:name => "Fortune 1000") { |f| f.rating = 1000 } + assert_equal "Fortune 1000", c.name + assert_equal 1000.to_f, c.rating.to_f + assert c.valid? + assert !c.new_record? + end + def test_dynamic_find_or_initialize_from_one_attribute_caches_method class << Company; self; end.send(:remove_method, :find_or_initialize_by_name) if Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } assert !Company.public_methods.any? { |m| m.to_s == 'find_or_initialize_by_name' } |