From a17027d13a48e1e64b14a28e7d58e341812f8cb4 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 13 Sep 2008 20:28:01 +0100 Subject: Merge docrails --- .../abstract/schema_definitions.rb | 38 ++++++++- activerecord/lib/active_record/locale/en-US.yml | 29 ++++++- activerecord/lib/active_record/validations.rb | 97 ++++++++++++---------- 3 files changed, 116 insertions(+), 48 deletions(-) (limited to 'activerecord') 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 75032efe57..22304edfc9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -252,6 +252,10 @@ module ActiveRecord class IndexDefinition < Struct.new(:table, :name, :unique, :columns) #:nodoc: end + # Abstract representation of a column definition. Instances of this type + # are typically created by methods in TableDefinition, and added to the + # +columns+ attribute of said TableDefinition object, in order to be used + # for generating a number of table creation or table changing SQL statements. class ColumnDefinition < Struct.new(:base, :name, :type, :limit, :precision, :scale, :default, :null) #:nodoc: def sql_type @@ -275,9 +279,29 @@ module ActiveRecord end end - # Represents a SQL table in an abstract way. - # Columns are stored as a ColumnDefinition in the +columns+ attribute. + # Represents the schema of an SQL table in an abstract way. This class + # provides methods for manipulating the schema representation. + # + # Inside migration files, the +t+ object in +create_table+ and + # +change_table+ is actually of this type: + # + # class SomeMigration < ActiveRecord::Migration + # def self.up + # create_table :foo do |t| + # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition" + # end + # end + # + # def self.down + # ... + # end + # end + # + # The table definitions + # The Columns are stored as a ColumnDefinition in the +columns+ attribute. class TableDefinition + # An array of ColumnDefinition objects, representing the column changes + # that have been defined. attr_accessor :columns def initialize(base) @@ -321,6 +345,12 @@ module ActiveRecord # * :scale - # Specifies the scale for a :decimal column. # + # For clarity's sake: the precision is the number of significant digits, + # while the scale is the number of digits that can be stored following + # the decimal point. For example, the number 123.45 has a precision of 5 + # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can + # range from -999.99 to 999.99. + # # Please be aware of different RDBMS implementations behavior with # :decimal columns: # * The SQL standard says the default scale should be 0, :scale <= @@ -374,6 +404,10 @@ module ActiveRecord # td.column(:huge_integer, :decimal, :precision => 30) # # => huge_integer DECIMAL(30) # + # # Defines a column with a database-specific type. + # td.column(:foo, 'polygon') + # # => foo polygon + # # == Short-hand examples # # Instead of calling +column+ directly, you can also work with the short-hand definitions for the default types. diff --git a/activerecord/lib/active_record/locale/en-US.yml b/activerecord/lib/active_record/locale/en-US.yml index 8148f31a81..421f0ebd60 100644 --- a/activerecord/lib/active_record/locale/en-US.yml +++ b/activerecord/lib/active_record/locale/en-US.yml @@ -25,9 +25,30 @@ en-US: even: "must be even" # Append your own errors here or at the model/attributes scope. + # You can define own errors for models or model attributes. + # The values :model, :attribute and :value are always available for interpolation. + # + # For example, + # models: + # user: + # blank: "This is a custom blank message for {{model}}: {{attribute}}" + # attributes: + # login: + # blank: "This is a custom blank message for User login" + # Will define custom blank validation message for User model and + # custom blank validation message for login attribute of User model. models: - # Overrides default messages - - attributes: - # Overrides model and default messages. + + # Translate model names. Used in Model.human_name(). + #models: + # For example, + # user: "Dude" + # will translate User model name to "Dude" + + # Translate model attribute names. Used in Model.human_attribute_name(attribute). + #attributes: + # For example, + # user: + # login: "Handle" + # will translate User attribute "login" as "Handle" diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 577e30ec86..518b59e433 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -259,6 +259,8 @@ module ActiveRecord end + # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations. + # # Active Records implement validation by overwriting Base#validate (or the variations, +validate_on_create+ and # +validate_on_update+). Each of these methods can inspect the state of the object, which usually means ensuring # that a number of attributes have a certain value (such as not empty, within a given range, matching a certain regular expression). @@ -297,8 +299,6 @@ module ActiveRecord # person.save # => true (and person is now saved in the database) # # An Errors object is automatically created for every Active Record. - # - # Please do have a look at ActiveRecord::Validations::ClassMethods for a higher level of validations. module Validations VALIDATIONS = %w( validate validate_on_create validate_on_update ) @@ -313,9 +313,50 @@ module ActiveRecord base.define_callbacks *VALIDATIONS end - # All of the following validations are defined in the class scope of the model that you're interested in validating. - # They offer a more declarative way of specifying when the model is valid and when it is not. It is recommended to use - # these over the low-level calls to +validate+ and +validate_on_create+ when possible. + # Active Record classes can implement validations in several ways. The highest level, easiest to read, + # and recommended approach is to use the declarative validates_..._of class methods (and + # +validates_associated+) documented below. These are sufficient for most model validations. + # + # Slightly lower level is +validates_each+. It provides some of the same options as the purely declarative + # validation methods, but like all the lower-level approaches it requires manually adding to the errors collection + # when the record is invalid. + # + # At a yet lower level, a model can use the class methods +validate+, +validate_on_create+ and +validate_on_update+ + # to add validation methods or blocks. These are ActiveSupport::Callbacks and follow the same rules of inheritance + # and chaining. + # + # The lowest level style is to define the instance methods +validate+, +validate_on_create+ and +validate_on_update+ + # as documented in ActiveRecord::Validations. + # + # == +validate+, +validate_on_create+ and +validate_on_update+ Class Methods + # + # Calls to these methods add a validation method or block to the class. Again, this approach is recommended + # only when the higher-level methods documented below (validates_..._of and +validates_associated+) are + # insufficient to handle the required validation. + # + # This can be done with a symbol pointing to a method: + # + # class Comment < ActiveRecord::Base + # validate :must_be_friends + # + # def must_be_friends + # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee) + # end + # end + # + # Or with a block which is passed the current record to be validated: + # + # class Comment < ActiveRecord::Base + # validate do |comment| + # comment.must_be_friends + # end + # + # def must_be_friends + # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee) + # end + # end + # + # This usage applies to +validate_on_create+ and +validate_on_update+ as well. module ClassMethods DEFAULT_VALIDATION_OPTIONS = { :on => :save, @@ -329,34 +370,6 @@ module ActiveRecord :equal_to => '==', :less_than => '<', :less_than_or_equal_to => '<=', :odd => 'odd?', :even => 'even?' }.freeze - # Adds a validation method or block to the class. This is useful when - # overriding the +validate+ instance method becomes too unwieldy and - # you're looking for more descriptive declaration of your validations. - # - # This can be done with a symbol pointing to a method: - # - # class Comment < ActiveRecord::Base - # validate :must_be_friends - # - # def must_be_friends - # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee) - # end - # end - # - # Or with a block which is passed the current record to be validated: - # - # class Comment < ActiveRecord::Base - # validate do |comment| - # comment.must_be_friends - # end - # - # def must_be_friends - # errors.add_to_base("Must be friends to leave a comment") unless commenter.friend_of?(commentee) - # end - # end - # - # This usage applies to +validate_on_create+ and +validate_on_update+ as well. - # Validates each attribute against a block. # # class Person < ActiveRecord::Base @@ -509,13 +522,13 @@ module ActiveRecord # # class Person < ActiveRecord::Base # validates_length_of :first_name, :maximum=>30 - # validates_length_of :last_name, :maximum=>30, :message=>"less than %d if you don't mind" + # validates_length_of :last_name, :maximum=>30, :message=>"less than {{count}} if you don't mind" # validates_length_of :fax, :in => 7..32, :allow_nil => true # validates_length_of :phone, :in => 7..32, :allow_blank => true # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name" - # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least %d character" - # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with %d characters... don't play me." - # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least %d words."), :tokenizer => lambda {|str| str.scan(/\w+/) } + # validates_length_of :fav_bra_size, :minimum => 1, :too_short => "please enter at least {{count}} character" + # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with {{count}} characters... don't play me." + # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least {{count}} words."), :tokenizer => lambda {|str| str.scan(/\w+/) } # end # # Configuration options: @@ -526,9 +539,9 @@ module ActiveRecord # * :in - A synonym(or alias) for :within. # * :allow_nil - Attribute may be +nil+; skip validation. # * :allow_blank - Attribute may be blank; skip validation. - # * :too_long - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)"). - # * :too_short - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)"). - # * :wrong_length - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)"). + # * :too_long - The error message if the attribute goes over the maximum (default is: "is too long (maximum is {{count}} characters)"). + # * :too_short - The error message if the attribute goes under the minimum (default is: "is too short (min is {{count}} characters)"). + # * :wrong_length - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be {{count}} characters)"). # * :message - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message. # * :on - Specifies when this validation is active (default is :save, other options :create, :update). # * :if - Specifies a method, proc or string to call to determine if the validation should @@ -731,7 +744,7 @@ module ActiveRecord # class Person < ActiveRecord::Base # validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!" # validates_inclusion_of :age, :in => 0..99 - # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list" + # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list" # end # # Configuration options: @@ -765,7 +778,7 @@ module ActiveRecord # class Person < ActiveRecord::Base # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here" # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60" - # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed" + # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension {{value}} is not allowed" # end # # Configuration options: -- cgit v1.2.3 From 9c4730d01e892df8d5c5493a08e0cddf0de5d575 Mon Sep 17 00:00:00 2001 From: miloops Date: Fri, 12 Sep 2008 11:28:47 -0300 Subject: Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/attribute_methods.rb | 2 +- activerecord/test/cases/attribute_methods_test.rb | 9 +++++++++ 3 files changed, 12 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 5bcc7ad3a9..5a0522f968 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] [miloops] + * Added find_last_by dynamic finder #762 [miloops] * Internal API: configurable association options and build_association method for reflections so plugins may extend and override. #985 [Hongli Lai] diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 0a1baff87d..020da01871 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -10,7 +10,7 @@ module ActiveRecord base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false base.time_zone_aware_attributes = false - base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false + base.class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false base.skip_time_zone_conversion_for_attributes = [] end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 7999e29264..ce293a469e 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -1,5 +1,6 @@ require "cases/helper" require 'models/topic' +require 'models/minimalistic' class AttributeMethodsTest < ActiveRecord::TestCase fixtures :topics @@ -219,6 +220,14 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable + Topic.skip_time_zone_conversion_for_attributes = [:field_a] + Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] + + assert_equal [:field_a], Topic.skip_time_zone_conversion_for_attributes + assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes + end + private def time_related_columns_on_topic Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) -- cgit v1.2.3 From d95943b276d52c5bc4f033e532376667badbad9f Mon Sep 17 00:00:00 2001 From: gbuesing Date: Sun, 14 Sep 2008 18:16:50 -0500 Subject: Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/base.rb | 2 +- activerecord/test/cases/base_test.rb | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 5a0522f968..d31e63017e 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] [Geoff Buesing] + * Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] [miloops] * Added find_last_by dynamic finder #762 [miloops] diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 91b69747e0..b20da512eb 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2730,7 +2730,7 @@ module ActiveRecord #:nodoc: end def instantiate_time_object(name, values) - if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym) + if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name)) Time.zone.local(*values) else Time.time_with_datetime_fallback(@@default_timezone, *values) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index bda6f346f0..aebcca634c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1084,6 +1084,24 @@ class BasicsTest < ActiveRecord::TestCase Time.zone = nil Topic.skip_time_zone_conversion_for_attributes = [] end + + def test_multiparameter_attributes_on_time_only_column_with_time_zone_aware_attributes_does_not_do_time_zone_conversion + ActiveRecord::Base.time_zone_aware_attributes = true + ActiveRecord::Base.default_timezone = :utc + Time.zone = ActiveSupport::TimeZone[-28800] + attributes = { + "bonus_time(1i)" => "2000", "bonus_time(2i)" => "1", "bonus_time(3i)" => "1", + "bonus_time(4i)" => "16", "bonus_time(5i)" => "24" + } + topic = Topic.find(1) + topic.attributes = attributes + assert_equal Time.utc(2000, 1, 1, 16, 24, 0), topic.bonus_time + assert topic.bonus_time.utc? + ensure + ActiveRecord::Base.time_zone_aware_attributes = false + ActiveRecord::Base.default_timezone = :local + Time.zone = nil + end def test_multiparameter_attributes_on_time_with_empty_seconds attributes = { -- cgit v1.2.3 From d51a39ff500d94ea4a81fbc22f0d1c540e83f4e1 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 14 Sep 2008 12:06:10 +0100 Subject: Deal with MySQL's quirky handling of defaults and blob/text columns [#1043 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/CHANGELOG | 2 ++ .../abstract/schema_definitions.rb | 4 +++ .../connection_adapters/mysql_adapter.rb | 7 ++++- activerecord/lib/active_record/schema_dumper.rb | 2 +- activerecord/test/cases/defaults_test.rb | 31 ++++++++++++++++++++++ activerecord/test/cases/migration_test.rb | 6 ++++- 6 files changed, 49 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index d31e63017e..ff2b064e1c 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* MySQL: cope with quirky default values for not-null text columns. #1043 [Frederick Cheung] + * Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] [Geoff Buesing] * Base.skip_time_zone_conversion_for_attributes uses class_inheritable_accessor, so that subclasses don't overwrite Base [#346 state:resolved] [miloops] 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 22304edfc9..58992f91da 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -40,6 +40,10 @@ module ActiveRecord type == :integer || type == :float || type == :decimal end + def has_default? + !default.nil? + end + # Returns the Ruby class that corresponds to the abstract data type. def klass case type diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index c2a0fb72bf..a26fd02b90 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -80,7 +80,7 @@ module ActiveRecord def extract_default(default) if type == :binary || type == :text if default.blank? - nil + return null ? nil : '' else raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}" end @@ -91,6 +91,11 @@ module ActiveRecord end end + def has_default? + return false if type == :binary || type == :text #mysql forbids defaults on blob and text columns + super + end + private def simplified_type(field_type) return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)") diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index b90ed88c6b..4f96e225c1 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -102,7 +102,7 @@ HEADER spec[:precision] = column.precision.inspect if !column.precision.nil? spec[:scale] = column.scale.inspect if !column.scale.nil? spec[:null] = 'false' if !column.null - spec[:default] = default_string(column.default) if !column.default.nil? + spec[:default] = default_string(column.default) if column.has_default? (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")} spec end.compact diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 3473b846a0..ee84cb8af8 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -19,6 +19,37 @@ class DefaultTest < ActiveRecord::TestCase end if current_adapter?(:MysqlAdapter) + + #MySQL 5 and higher is quirky with not null text/blob columns. + #With MySQL Text/blob columns cannot have defaults. If the column is not null MySQL will report that the column has a null default + #but it behaves as though the column had a default of '' + def test_mysql_text_not_null_defaults + klass = Class.new(ActiveRecord::Base) + klass.table_name = 'test_mysql_text_not_null_defaults' + klass.connection.create_table klass.table_name do |t| + t.column :non_null_text, :text, :null => false + t.column :non_null_blob, :blob, :null => false + t.column :null_text, :text, :null => true + t.column :null_blob, :blob, :null => true + end + assert_equal '', klass.columns_hash['non_null_blob'].default + assert_equal '', klass.columns_hash['non_null_text'].default + + assert_equal nil, klass.columns_hash['null_blob'].default + assert_equal nil, klass.columns_hash['null_text'].default + + assert_nothing_raised do + instance = klass.create! + assert_equal '', instance.non_null_text + assert_equal '', instance.non_null_blob + assert_nil instance.null_text + assert_nil instance.null_blob + end + ensure + klass.connection.drop_table(klass.table_name) rescue nil + end + + # MySQL uses an implicit default 0 rather than NULL unless in strict mode. # We use an implicit NULL so schema.rb is compatible with other databases. def test_mysql_integer_not_null_defaults diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index c1a8da2270..ac44dd7ffe 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1133,7 +1133,11 @@ if ActiveRecord::Base.connection.supports_migrations? columns = Person.connection.columns(:binary_testings) data_column = columns.detect { |c| c.name == "data" } - assert_nil data_column.default + if current_adapter?(:MysqlAdapter) + assert_equal '', data_column.default + else + assert_nil data_column.default + end Person.connection.drop_table :binary_testings rescue nil end -- cgit v1.2.3 From f636c6fda037fbef25e98c81d019a6c600a18f66 Mon Sep 17 00:00:00 2001 From: Frederick Cheung Date: Sun, 14 Sep 2008 20:40:09 +0100 Subject: stop AR's debug.log filling with warnings about not being able to load fixture classes [#1045 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/test/cases/helper.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f30d58546e..f7bdac8013 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -46,3 +46,17 @@ end class << ActiveRecord::Base public :with_scope, :with_exclusive_scope end + +unless ENV['FIXTURE_DEBUG'] + module Test #:nodoc: + module Unit #:nodoc: + class << TestCase #:nodoc: + def try_to_load_dependency_with_silence(*args) + ActiveRecord::Base.logger.silence { try_to_load_dependency_without_silence(*args)} + end + + alias_method_chain :try_to_load_dependency, :silence + end + end + end +end \ No newline at end of file -- cgit v1.2.3 From dc8bf7515de85f5bc28d17e96edf4a3e74a858da Mon Sep 17 00:00:00 2001 From: miloops Date: Mon, 15 Sep 2008 13:23:50 -0300 Subject: When counting grouped records the target should be loaded to return a valid groups count result. Without this change count_records will group for the count in the query and return erroneous results. Signed-off-by: Michael Koziarski [#937 state:committed] --- activerecord/lib/active_record/associations/association_collection.rb | 2 ++ activerecord/test/cases/associations/has_many_associations_test.rb | 2 ++ 2 files changed, 4 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 8de528f638..afb817f8ae 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -238,6 +238,8 @@ module ActiveRecord def size if @owner.new_record? || (loaded? && !@reflection.options[:uniq]) @target.size + elsif !loaded? && @reflection.options[:group] + load_target.size elsif !loaded? && !@reflection.options[:uniq] && @target.is_a?(Array) unsaved_records = @target.select { |r| r.new_record? } unsaved_records.size + count_records diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 5bcbc5eb5b..ba750b266c 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -249,7 +249,9 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_scoped_grouped + assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.size assert_equal 1, companies(:first_firm).clients_grouped_by_firm_id.length + assert_equal 2, companies(:first_firm).clients_grouped_by_name.size assert_equal 2, companies(:first_firm).clients_grouped_by_name.length end -- cgit v1.2.3 From 4db7e8de1160de6c813a33266fa415849e25fba6 Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Tue, 16 Sep 2008 18:50:36 +0200 Subject: Update the documentation to reflect the change handling :group earlier --- activerecord/lib/active_record/associations/has_many_association.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index dda22668c6..3b2f306637 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -17,7 +17,10 @@ module ActiveRecord # Returns the number of records in this collection. # # If the association has a counter cache it gets that value. Otherwise - # a count via SQL is performed, bounded to :limit if there's one. + # it will attempt to do a count via SQL, bounded to :limit if + # there's one. Some configuration options like :group make it impossible + # to do a SQL count, in those cases the array count will be used. + # # That does not depend on whether the collection has already been loaded # or not. The +size+ method is the one that takes the loaded flag into # account and delegates to +count_records+ if needed. -- cgit v1.2.3 From 9d7f186f746f38367851355fcd301ac24d6f6a51 Mon Sep 17 00:00:00 2001 From: Nathaniel Talbott Date: Fri, 19 Sep 2008 16:42:18 -0400 Subject: Fixed an error triggered by a reload followed by a foreign key assignment. Signed-off-by: Michael Koziarski --- activerecord/lib/active_record/associations.rb | 6 +++++- activerecord/test/cases/associations_test.rb | 8 ++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5d91315aad..d7aa4bfa98 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1268,7 +1268,11 @@ module ActiveRecord if association_proxy_class == BelongsToAssociation define_method("#{reflection.primary_key_name}=") do |target_id| - instance_variable_get(ivar).reset if instance_variable_defined?(ivar) + if instance_variable_defined?(ivar) + if association = instance_variable_get(ivar) + association.reset + end + end write_attribute(reflection.primary_key_name, target_id) end end diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index 2050d16179..cde451de0e 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -194,6 +194,14 @@ class AssociationProxyTest < ActiveRecord::TestCase assert_equal david, welcome.author end + def test_assigning_association_id_after_reload + welcome = posts(:welcome) + welcome.reload + assert_nothing_raised do + welcome.author_id = authors(:david).id + end + end + def test_reload_returns_assocition david = developers(:david) assert_nothing_raised do -- cgit v1.2.3 From 8cb7d460439a9b20a80a77b6370c1107233d1cbd Mon Sep 17 00:00:00 2001 From: Sven Fuchs Date: Sun, 14 Sep 2008 13:36:06 +0200 Subject: I18n: move old-style interpolation syntax deprecation to Active Record. [#1044 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record.rb | 2 +- .../i18n_interpolation_deprecation.rb | 26 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 activerecord/lib/active_record/i18n_interpolation_deprecation.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index a6bbd6fc82..3f24170ce1 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -77,5 +77,5 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/schema_dumper' +require 'active_record/i18n_interpolation_deprecation' I18n.load_translations File.dirname(__FILE__) + '/active_record/locale/en-US.yml' - diff --git a/activerecord/lib/active_record/i18n_interpolation_deprecation.rb b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb new file mode 100644 index 0000000000..cd634e1b8d --- /dev/null +++ b/activerecord/lib/active_record/i18n_interpolation_deprecation.rb @@ -0,0 +1,26 @@ +# Deprecates the use of the former message interpolation syntax in activerecord +# as in "must have %d characters". The new syntax uses explicit variable names +# as in "{{value}} must have {{count}} characters". + +require 'i18n/backend/simple' +module I18n + module Backend + class Simple + DEPRECATED_INTERPOLATORS = { '%d' => '{{count}}', '%s' => '{{value}}' } + + protected + def interpolate_with_deprecated_syntax(locale, string, values = {}) + return string unless string.is_a?(String) + + string = string.gsub(/%d|%s/) do |s| + instead = DEPRECATED_INTERPOLATORS[s] + ActiveSupport::Deprecation.warn "using #{s} in messages is deprecated; use #{instead} instead." + instead + end + + interpolate_without_deprecated_syntax(locale, string, values) + end + alias_method_chain :interpolate, :deprecated_syntax + end + end +end \ No newline at end of file -- cgit v1.2.3 From a3b7fa78bfdc33e45e39c095b67e02d50a2c7bea Mon Sep 17 00:00:00 2001 From: Sven Fuchs Date: Mon, 15 Sep 2008 10:26:50 +0200 Subject: I18n: Introduce I18n.load_path in favor of I18n.load_translations and change Simple backend to load translations lazily. [#1048 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record.rb | 2 +- activerecord/test/cases/validations_i18n_test.rb | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 3f24170ce1..219cd30f94 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -78,4 +78,4 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/schema_dumper' require 'active_record/i18n_interpolation_deprecation' -I18n.load_translations File.dirname(__FILE__) + '/active_record/locale/en-US.yml' +I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en-US.yml' diff --git a/activerecord/test/cases/validations_i18n_test.rb b/activerecord/test/cases/validations_i18n_test.rb index e34890ef19..42246f18b6 100644 --- a/activerecord/test/cases/validations_i18n_test.rb +++ b/activerecord/test/cases/validations_i18n_test.rb @@ -6,12 +6,16 @@ class ActiveRecordValidationsI18nTests < Test::Unit::TestCase def setup reset_callbacks Topic @topic = Topic.new + @old_load_path, @old_backend = I18n.load_path, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new I18n.backend.store_translations('en-US', :activerecord => {:errors => {:messages => {:custom => nil}}}) end def teardown reset_callbacks Topic - I18n.load_translations File.dirname(__FILE__) + '/../../lib/active_record/locale/en-US.yml' + I18n.load_path.replace @old_load_path + I18n.backend = @old_backend end def unique_topic -- cgit v1.2.3 From de96a8666d8edc9be57f6146e587a71d23dbeb41 Mon Sep 17 00:00:00 2001 From: Adeh DeSandies Date: Fri, 12 Sep 2008 18:02:40 +0800 Subject: applied patch to fix the associations with blocks in modules bug from an old trac ticket --- activerecord/lib/active_record/associations.rb | 8 ++++---- activerecord/test/cases/associations/extension_test.rb | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d7aa4bfa98..e6491cebd6 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1026,7 +1026,7 @@ module ActiveRecord # Create the callbacks to update counter cache if options[:counter_cache] cache_column = options[:counter_cache] == true ? - "#{self.to_s.underscore.pluralize}_count" : + "#{self.to_s.demodulize.underscore.pluralize}_count" : options[:counter_cache] method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym @@ -1755,12 +1755,12 @@ module ActiveRecord def create_extension_modules(association_id, block_extension, extensions) if block_extension - extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension" + extension_module_name = "#{self.to_s.demodulize}#{association_id.to_s.camelize}AssociationExtension" silence_warnings do - Object.const_set(extension_module_name, Module.new(&block_extension)) + self.parent.const_set(extension_module_name, Module.new(&block_extension)) end - Array(extensions).push(extension_module_name.constantize) + Array(extensions).push("#{self.parent}::#{extension_module_name}".constantize) else Array(extensions) end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index 5c01c3c1f5..9390633d5b 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -3,6 +3,7 @@ require 'models/post' require 'models/comment' require 'models/project' require 'models/developer' +require 'models/company_in_module' class AssociationsExtensionsTest < ActiveRecord::TestCase fixtures :projects, :developers, :developers_projects, :comments, :posts @@ -44,4 +45,18 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase david = Marshal.load(Marshal.dump(david)) assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent end + + + def test_extension_name + extension = Proc.new {} + name = :association_name + + assert_equal 'DeveloperAssociationNameAssociationExtension', Developer.send(:create_extension_modules, name, extension, []).first.name + assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', +MyApplication::Business::Developer.send(:create_extension_modules, name, extension, []).first.name + assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', MyApplication::Business::Developer.send(:create_extension_modules, name, extension, []).first.name + assert_equal 'MyApplication::Business::DeveloperAssociationNameAssociationExtension', MyApplication::Business::Developer.send(:create_extension_modules, name, extension, []).first.name + end + + end -- cgit v1.2.3 From 7329990d868d10e4fbf541097cd5be8f1254e2ce Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Sun, 21 Sep 2008 17:27:25 +0200 Subject: Change all calls to String#chars to String#mb_chars. Remove a exception for Ruby <= 1.9. --- activerecord/test/cases/base_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 activerecord/test/cases/base_test.rb (limited to 'activerecord') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb old mode 100644 new mode 100755 index aebcca634c..1c31b1fa7b --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1442,8 +1442,8 @@ class BasicsTest < ActiveRecord::TestCase topic = Topic.create(:author_name => str) assert_equal str, topic.author_name - assert_kind_of ActiveSupport::Multibyte::Chars, str.chars - topic = Topic.find_by_author_name(str.chars) + assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars + topic = Topic.find_by_author_name(str.mb_chars) assert_kind_of Topic, topic assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars" -- cgit v1.2.3 From 52f8c04e1e0f2e6610e54b00125179ec9dacbcd5 Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Sun, 21 Sep 2008 17:30:16 +0200 Subject: Fix a test that assumes .mb_chars to always return an instance of the proxy_class. --- activerecord/test/cases/base_test.rb | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 1c31b1fa7b..a4fddc2571 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1438,15 +1438,17 @@ class BasicsTest < ActiveRecord::TestCase if RUBY_VERSION < '1.9' def test_quote_chars - str = 'The Narrator' - topic = Topic.create(:author_name => str) - assert_equal str, topic.author_name + with_kcode('UTF8') do + str = 'The Narrator' + topic = Topic.create(:author_name => str) + assert_equal str, topic.author_name - assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars - topic = Topic.find_by_author_name(str.mb_chars) + assert_kind_of ActiveSupport::Multibyte.proxy_class, str.mb_chars + topic = Topic.find_by_author_name(str.mb_chars) - assert_kind_of Topic, topic - assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars" + assert_kind_of Topic, topic + assert_equal str, topic.author_name, "The right topic should have been found by name even with name passed as Chars" + end end end @@ -2039,4 +2041,18 @@ class BasicsTest < ActiveRecord::TestCase ensure ActiveRecord::Base.logger = original_logger end + + private + def with_kcode(kcode) + if RUBY_VERSION < '1.9' + orig_kcode, $KCODE = $KCODE, kcode + begin + yield + ensure + $KCODE = orig_kcode + end + else + yield + end + end end -- cgit v1.2.3 From 1585a7ed0228c2d4688e986c24fc221a4a9e5104 Mon Sep 17 00:00:00 2001 From: Manfred Stienstra Date: Sun, 21 Sep 2008 18:01:15 +0200 Subject: Change all calls to String#chars to String#mb_chars. --- activerecord/lib/active_record/validations.rb | 2 +- activerecord/test/cases/finder_test.rb | 4 ++-- activerecord/test/cases/sanitize_test.rb | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 518b59e433..684fb1de88 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -678,7 +678,7 @@ module ActiveRecord condition_params = [value] else condition_sql = "LOWER(#{sql_attribute}) #{comparison_operator}" - condition_params = [value.chars.downcase] + condition_params = [value.mb_chars.downcase] end if scope = configuration[:scope] diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index cbdff382fe..10f6aa0f2a 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -465,8 +465,8 @@ class FinderTest < ActiveRecord::TestCase quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi") assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper") - assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".chars) - assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".chars) + assert_equal "name=#{quoted_bambi}", bind('name=?', "Bambi".mb_chars) + assert_equal "name=#{quoted_bambi_and_thumper}", bind('name=?', "Bambi\nand\nThumper".mb_chars) end def test_bind_record diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb index 0106572ced..817897ceac 100644 --- a/activerecord/test/cases/sanitize_test.rb +++ b/activerecord/test/cases/sanitize_test.rb @@ -8,18 +8,18 @@ class SanitizeTest < ActiveRecord::TestCase def test_sanitize_sql_array_handles_string_interpolation quoted_bambi = ActiveRecord::Base.connection.quote_string("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi"]) - assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi".chars]) + assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=%s", "Bambi".mb_chars]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote_string("Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper"]) - assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper".chars]) + assert_equal "name=#{quoted_bambi_and_thumper}",Binary.send(:sanitize_sql_array, ["name=%s", "Bambi\nand\nThumper".mb_chars]) end def test_sanitize_sql_array_handles_bind_variables quoted_bambi = ActiveRecord::Base.connection.quote("Bambi") assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi"]) - assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".chars]) + assert_equal "name=#{quoted_bambi}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi".mb_chars]) quoted_bambi_and_thumper = ActiveRecord::Base.connection.quote("Bambi\nand\nThumper") assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper"]) - assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".chars]) + assert_equal "name=#{quoted_bambi_and_thumper}", Binary.send(:sanitize_sql_array, ["name=?", "Bambi\nand\nThumper".mb_chars]) end end -- cgit v1.2.3 From 46939a9b5a0098fddeac99a8a4331f66bdd0710e Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion" Date: Sun, 21 Sep 2008 23:01:32 +0200 Subject: Add Model#delete instance method, similar to Model.delete class method. [#1086 state:resolved] Signed-off-by: Pratik Naik --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/associations.rb | 4 ++-- activerecord/lib/active_record/base.rb | 10 ++++++++++ activerecord/test/cases/base_test.rb | 26 ++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index ff2b064e1c..6479cc5a9b 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Add Model#delete instance method, similar to Model.delete class method. #1086 [Hongli Lai] + * MySQL: cope with quirky default values for not-null text columns. #1043 [Frederick Cheung] * Multiparameter attributes skip time zone conversion for time-only columns [#1030 state:resolved] [Geoff Buesing] diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index e6491cebd6..6f4be9391b 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1470,7 +1470,7 @@ module ActiveRecord method_name = "has_one_dependent_delete_for_#{reflection.name}".to_sym define_method(method_name) do association = send(reflection.name) - association.class.delete(association.id) unless association.nil? + association.delete unless association.nil? end before_destroy method_name when :nullify @@ -1500,7 +1500,7 @@ module ActiveRecord method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym define_method(method_name) do association = send(reflection.name) - association.class.delete(association.id) unless association.nil? + association.delete unless association.nil? end before_destroy method_name else diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b20da512eb..3aa8e5541d 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2304,6 +2304,16 @@ module ActiveRecord #:nodoc: create_or_update || raise(RecordNotSaved) end + # Deletes the record in the database and freezes this instance to reflect that no changes should + # be made (since they can't be persisted). + # + # Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+ + # callbacks, nor will it enforce any association +:dependent+ rules. + def delete + self.class.delete(id) unless new_record? + freeze + end + # Deletes the record in the database and freezes this instance to reflect that no changes should # be made (since they can't be persisted). def destroy diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index aebcca634c..d3f00be894 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -472,6 +472,18 @@ class BasicsTest < ActiveRecord::TestCase assert topic.instance_variable_get("@custom_approved") end + def test_delete + topic = Topic.find(1) + assert_equal topic, topic.delete, 'topic.delete did not return self' + assert topic.frozen?, 'topic not frozen after delete' + assert_raise(ActiveRecord::RecordNotFound) { Topic.find(topic.id) } + end + + def test_delete_doesnt_run_callbacks + Topic.find(1).delete + assert_not_nil Topic.find(2) + end + def test_destroy topic = Topic.find(1) assert_equal topic, topic.destroy, 'topic.destroy did not return self' @@ -820,6 +832,20 @@ class BasicsTest < ActiveRecord::TestCase assert_equal [ Topic.find(1) ], [ Topic.find(2).topic ] & [ Topic.find(1) ] end + def test_delete_new_record + client = Client.new + client.delete + assert client.frozen? + end + + def test_delete_record_with_associations + client = Client.find(3) + client.delete + assert client.frozen? + assert_kind_of Firm, client.firm + assert_raises(ActiveSupport::FrozenObjectError) { client.name = "something else" } + end + def test_destroy_new_record client = Client.new client.destroy -- cgit v1.2.3 From 050e58441bf7e60d167f6708072f8fa7aee2ce76 Mon Sep 17 00:00:00 2001 From: Jan De Poorter Date: Mon, 22 Sep 2008 18:14:42 +0100 Subject: Association#first and last should not load the association if not needed. [#1091 state:resolved] Signed-off-by: Pratik Naik --- .../active_record/associations/association_collection.rb | 7 ++++--- .../has_and_belongs_to_many_associations_test.rb | 2 +- .../test/cases/associations/has_many_associations_test.rb | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index afb817f8ae..47a09510c8 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -63,7 +63,7 @@ module ActiveRecord # Fetches the first one using SQL if possible. def first(*args) - if fetch_first_or_last_using_find? args + if fetch_first_or_last_using_find?(args) find(:first, *args) else load_target unless loaded? @@ -73,7 +73,7 @@ module ActiveRecord # Fetches the last one using SQL if possible. def last(*args) - if fetch_first_or_last_using_find? args + if fetch_first_or_last_using_find?(args) find(:last, *args) else load_target unless loaded? @@ -420,7 +420,8 @@ module ActiveRecord end def fetch_first_or_last_using_find?(args) - args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer)) + args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || + @target.any? { |record| record.new_record? } || args.first.kind_of?(Integer)) end end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 9981f4c5d5..c1d4ea8b50 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -253,7 +253,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert !devel.projects.loaded? assert_equal devel.projects.last, proj - assert devel.projects.loaded? + assert !devel.projects.loaded? assert !proj.new_record? assert_equal Developer.find(1).projects.sort_by(&:id).last, proj # prove join table is updated diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ba750b266c..9d550916d7 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1009,6 +1009,19 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert firm.clients.loaded? end + def test_calling_first_or_last_on_existing_record_with_create_should_not_load_association + firm = companies(:first_firm) + firm.clients.create(:name => 'Foo') + assert !firm.clients.loaded? + + assert_queries 2 do + firm.clients.first + firm.clients.last + end + + assert !firm.clients.loaded? + end + def test_calling_first_or_last_on_new_record_should_not_run_queries firm = Firm.new -- cgit v1.2.3 From 5f86451a4c5d0beca5a746c4708be48b13f665be Mon Sep 17 00:00:00 2001 From: Michael Koziarski Date: Mon, 22 Sep 2008 17:14:54 +0200 Subject: Bump the Version constants to align with the *next* release rather than the previous release. This allows people tracking non-release gems or git submodules to use the constants. --- activerecord/lib/active_record/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index aaadef9979..2479b75789 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -1,7 +1,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 2 - MINOR = 1 + MINOR = 2 TINY = 0 STRING = [MAJOR, MINOR, TINY].join('.') -- cgit v1.2.3 From 70b8ea4fa6f432919340345ae0d5af6aa8f87ec8 Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Sat, 20 Sep 2008 21:59:49 +0200 Subject: Make AssociationCollection start transactions in the correct database. AssociationCollection now starts transactions by calling AssociationCollection#transaction instead of @owner.transaction or @reflection.klass.transaction. Signed-off-by: Michael Koziarski [#1081 state:committed] --- .../associations/association_collection.rb | 23 ++++++++++++++++++---- .../associations/has_many_through_association.rb | 4 ++-- .../has_and_belongs_to_many_associations_test.rb | 9 +++++++++ .../associations/has_many_associations_test.rb | 9 +++++++++ .../has_many_through_associations_test.rb | 9 +++++++++ 5 files changed, 48 insertions(+), 6 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index 47a09510c8..463de9d819 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -108,7 +108,7 @@ module ActiveRecord result = true load_target if @owner.new_record? - @owner.transaction do + transaction do flatten_deeper(records).each do |record| raise_on_type_mismatch(record) add_record_to_target_with_callbacks(record) do |r| @@ -123,6 +123,21 @@ module ActiveRecord alias_method :push, :<< alias_method :concat, :<< + # Starts a transaction in the association class's database connection. + # + # class Author < ActiveRecord::Base + # has_many :books + # end + # + # Author.find(:first).books.transaction do + # # same effect as calling Book.transaction + # end + def transaction(*args) + @reflection.klass.transaction(*args) do + yield + end + end + # Remove all records from this association def delete_all load_target @@ -173,7 +188,7 @@ module ActiveRecord records = flatten_deeper(records) records.each { |record| raise_on_type_mismatch(record) } - @owner.transaction do + transaction do records.each { |record| callback(:before_remove, record) } old_records = records.reject {|r| r.new_record? } @@ -200,7 +215,7 @@ module ActiveRecord end def destroy_all - @owner.transaction do + transaction do each { |record| record.destroy } end @@ -292,7 +307,7 @@ module ActiveRecord other = other_array.size < 100 ? other_array : other_array.to_set current = @target.size < 100 ? @target : @target.to_set - @owner.transaction do + transaction do delete(@target.select { |v| !other.include?(v) }) concat(other_array.select { |v| !current.include?(v) }) end 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 ebd2bf768c..3171665e19 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -9,14 +9,14 @@ module ActiveRecord alias_method :new, :build def create!(attrs = nil) - @reflection.klass.transaction do + transaction do self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!) object end end def create(attrs = nil) - @reflection.klass.transaction do + transaction do self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association) object end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index c1d4ea8b50..2949f1d304 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -738,4 +738,13 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase # Array#count in Ruby >=1.8.7, which would raise an ArgumentError assert_nothing_raised { david.projects.count(:all, :conditions => '1=1') } end + + uses_mocha 'mocking Post.transaction' do + def test_association_proxy_transaction_method_starts_transaction_in_association_class + Post.expects(:transaction) + Category.find(:first).posts.transaction do + # nothing + end + end + end end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 9d550916d7..315d77de07 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1071,4 +1071,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase ActiveRecord::Base.store_full_sti_class = old end + uses_mocha 'mocking Comment.transaction' do + def test_association_proxy_transaction_method_starts_transaction_in_association_class + Comment.expects(:transaction) + Post.find(:first).comments.transaction do + # nothing + end + end + end + end diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 0be050ec81..12cce98c26 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -220,4 +220,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal [posts(:welcome).id, posts(:authorless).id].sort, person.post_ids.sort assert !person.posts.loaded? end + + uses_mocha 'mocking Tag.transaction' do + def test_association_proxy_transaction_method_starts_transaction_in_association_class + Tag.expects(:transaction) + Post.find(:first).tags.transaction do + # nothing + end + end + end end -- cgit v1.2.3 From 487758b3b88a38da3a75900839aea03774904fe1 Mon Sep 17 00:00:00 2001 From: Pivotal Labs Date: Tue, 23 Sep 2008 13:32:17 -0700 Subject: Allowed passing arrays-of-strings to :join everywhere. Merge duplicate join strings to avoid table aliasing problems. Signed-off-by: Michael Koziarski [#1077 state:committed] --- activerecord/lib/active_record/base.rb | 34 ++++++++++++++++---------- activerecord/test/cases/finder_test.rb | 11 +++++++++ activerecord/test/cases/method_scoping_test.rb | 30 +++++++++++++++++++++++ activerecord/test/cases/named_scope_test.rb | 6 +++++ 4 files changed, 68 insertions(+), 13 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 3aa8e5541d..69ea155ace 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1576,19 +1576,19 @@ module ActiveRecord #:nodoc: (safe_to_array(first) + safe_to_array(second)).uniq end - def merge_joins(first, second) - if first.is_a?(String) && second.is_a?(String) - "#{first} #{second}" - elsif first.is_a?(String) || second.is_a?(String) - if first.is_a?(String) - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, second, nil) - "#{first} #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join}" - else - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, first, nil) - "#{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} #{second}" + def merge_joins(*joins) + if joins.any?{|j| j.is_a?(String) || array_of_strings?(j) } + joins = joins.collect do |join| + join = [join] if join.is_a?(String) + unless array_of_strings?(join) + join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil) + join = join_dependency.join_associations.collect { |assoc| assoc.association_join } + end + join end + joins.flatten.uniq else - (safe_to_array(first) + safe_to_array(second)).uniq + joins.collect{|j| safe_to_array(j)}.flatten.uniq end end @@ -1604,6 +1604,10 @@ module ActiveRecord #:nodoc: end end + def array_of_strings?(o) + o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} + end + def add_order!(sql, order, scope = :auto) scope = scope(:find) if :auto == scope scoped_order = scope[:order] if scope @@ -1652,8 +1656,12 @@ module ActiveRecord #:nodoc: merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins]) case merged_joins when Symbol, Hash, Array - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) - sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " + if array_of_strings?(merged_joins) + sql << merged_joins.join(' ') + " " + else + join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) + sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " + end when String sql << " #{merged_joins} " end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 10f6aa0f2a..292b88edbc 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -935,6 +935,17 @@ class FinderTest < ActiveRecord::TestCase assert_equal 1, first.id end + def test_joins_with_string_array + person_with_reader_and_post = Post.find( + :all, + :joins => [ + "INNER JOIN categorizations ON categorizations.post_id = posts.id", + "INNER JOIN categories ON categories.id = categorizations.category_id AND categories.type = 'SpecialCategory'" + ] + ) + assert_equal 1, person_with_reader_and_post.size + end + def test_find_by_id_with_conditions_with_or assert_nothing_raised do Post.find([1,2,3], diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index af6fcd32ad..ff10bfaf3e 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -138,6 +138,36 @@ class MethodScopingTest < ActiveRecord::TestCase assert_equal authors(:david).attributes, scoped_authors.first.attributes end + def test_scoped_find_merges_string_array_style_and_string_style_joins + scoped_authors = Author.with_scope(:find => { :joins => ["INNER JOIN posts ON posts.author_id = authors.id"]}) do + Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1') + end + assert scoped_authors.include?(authors(:david)) + assert !scoped_authors.include?(authors(:mary)) + assert_equal 1, scoped_authors.size + assert_equal authors(:david).attributes, scoped_authors.first.attributes + end + + def test_scoped_find_merges_string_array_style_and_hash_style_joins + scoped_authors = Author.with_scope(:find => { :joins => :posts}) do + Author.find(:all, :select => 'DISTINCT authors.*', :joins => ['INNER JOIN comments ON posts.id = comments.post_id'], :conditions => 'comments.id = 1') + end + assert scoped_authors.include?(authors(:david)) + assert !scoped_authors.include?(authors(:mary)) + assert_equal 1, scoped_authors.size + assert_equal authors(:david).attributes, scoped_authors.first.attributes + end + + def test_scoped_find_merges_joins_and_eliminates_duplicate_string_joins + scoped_authors = Author.with_scope(:find => { :joins => 'INNER JOIN posts ON posts.author_id = authors.id'}) do + Author.find(:all, :select => 'DISTINCT authors.*', :joins => ["INNER JOIN posts ON posts.author_id = authors.id", "INNER JOIN comments ON posts.id = comments.post_id"], :conditions => 'comments.id = 1') + end + assert scoped_authors.include?(authors(:david)) + assert !scoped_authors.include?(authors(:mary)) + assert_equal 1, scoped_authors.size + assert_equal authors(:david).attributes, scoped_authors.first.attributes + end + def test_scoped_count_include # with the include, will retrieve only developers for the given project Developer.with_scope(:find => { :include => :projects }) do diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index 444debd255..64e899780c 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -271,4 +271,10 @@ class NamedScopeTest < ActiveRecord::TestCase topics.size # use loaded (no query) end end + + def test_chaining_with_duplicate_joins + join = "INNER JOIN comments ON comments.post_id = posts.id" + post = Post.find(1) + assert_equal post.comments.size, Post.scoped(:joins => join).scoped(:joins => join, :conditions => "posts.id = #{post.id}").size + end end -- cgit v1.2.3 From 72b772ae9b692add0359574b0da7038bd1420a5a Mon Sep 17 00:00:00 2001 From: "Hongli Lai (Phusion)" Date: Sun, 21 Sep 2008 23:51:02 +0200 Subject: Refactor configure_dependency_for_has_many to use a few more methods. Add an additional conditions option to make it slightly easier for certain plugins. Signed-off-by: Michael Koziarski [#1087 state:committed] --- activerecord/lib/active_record/associations.rb | 37 +++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 6f4be9391b..3f8ec4d09c 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1428,15 +1428,23 @@ module ActiveRecord [] end + # Creates before_destroy callback methods that nullify, delete or destroy + # has_many associated objects, according to the defined :dependent rule. + # # See HasManyAssociation#delete_records. Dependent associations # delete children, otherwise foreign key is set to NULL. - def configure_dependency_for_has_many(reflection) + # + # The +extra_conditions+ parameter, which is not used within the main + # Active Record codebase, is meant to allow plugins to define extra + # finder conditions. + def configure_dependency_for_has_many(reflection, extra_conditions = nil) if reflection.options.include?(:dependent) # Add polymorphic type if the :as option is present dependent_conditions = [] dependent_conditions << "#{reflection.primary_key_name} = \#{record.quoted_id}" dependent_conditions << "#{reflection.options[:as]}_type = '#{base_class.name}'" if reflection.options[:as] dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions] + dependent_conditions << extra_conditions if extra_conditions dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ") case reflection.options[:dependent] @@ -1447,9 +1455,24 @@ module ActiveRecord end before_destroy method_name when :delete_all - module_eval "before_destroy { |record| #{reflection.class_name}.delete_all(%(#{dependent_conditions})) }" + module_eval %Q{ + before_destroy do |record| + delete_all_has_many_dependencies(record, + "#{reflection.name}", + #{reflection.class_name}, + "#{dependent_conditions}") + end + } when :nullify - module_eval "before_destroy { |record| #{reflection.class_name}.update_all(%(#{reflection.primary_key_name} = NULL), %(#{dependent_conditions})) }" + module_eval %Q{ + before_destroy do |record| + nullify_has_many_dependencies(record, + "#{reflection.name}", + #{reflection.class_name}, + "#{reflection.primary_key_name}", + "#{dependent_conditions}") + end + } else raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})" end @@ -1509,6 +1532,14 @@ module ActiveRecord end end + def delete_all_has_many_dependencies(record, reflection_name, association_class, dependent_conditions) + association_class.delete_all(dependent_conditions) + end + + def nullify_has_many_dependencies(record, reflection_name, association_class, primary_key_name, dependent_conditions) + association_class.update_all("#{primary_key_name} = NULL", dependent_conditions) + end + mattr_accessor :valid_keys_for_has_many_association @@valid_keys_for_has_many_association = [ :class_name, :table_name, :foreign_key, :primary_key, -- cgit v1.2.3 From 4d9a7ab5f5c28820e0b076f9ca44bdd20e19e6ea Mon Sep 17 00:00:00 2001 From: Adam Milligan Date: Sun, 21 Sep 2008 01:03:09 -0700 Subject: Changed ActiveRecord attributes to respect access control. Signed-off-by: Michael Koziarski [#1084 state:committed] --- .../lib/active_record/attribute_methods.rb | 8 +++- activerecord/test/cases/attribute_methods_test.rb | 51 ++++++++++++++++++++-- 2 files changed, 54 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 020da01871..e5486738f0 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -232,6 +232,10 @@ module ActiveRecord def method_missing(method_id, *args, &block) method_name = method_id.to_s + if self.class.private_method_defined?(method_name) + raise NoMethodError("Attempt to call private method", method_name, args) + end + # 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.generated_methods? @@ -334,10 +338,12 @@ module ActiveRecord # person.respond_to?(:name=), and person.respond_to?(:name?) # which will all return +true+. alias :respond_to_without_attributes? :respond_to? - def respond_to?(method, include_priv = false) + def respond_to?(method, include_private_methods = false) method_name = method.to_s if super return true + elsif self.private_methods.include?(method_name) && !include_private_methods + return false elsif !self.class.generated_methods? self.class.define_attribute_methods if self.class.generated_methods.include?(method_name) diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ce293a469e..160716f944 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -58,19 +58,19 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_kernel_methods_not_implemented_in_activerecord %w(test name display y).each do |method| - assert_equal false, ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" + assert !ActiveRecord::Base.instance_method_already_implemented?(method), "##{method} is defined" end end def test_primary_key_implemented - assert_equal true, Class.new(ActiveRecord::Base).instance_method_already_implemented?('id') + assert Class.new(ActiveRecord::Base).instance_method_already_implemented?('id') end def test_defined_kernel_methods_implemented_in_model %w(test name display y).each do |method| klass = Class.new ActiveRecord::Base klass.class_eval "def #{method}() 'defined #{method}' end" - assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined" + assert klass.instance_method_already_implemented?(method), "##{method} is not defined" end end @@ -80,7 +80,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase abstract.class_eval "def #{method}() 'defined #{method}' end" abstract.abstract_class = true klass = Class.new abstract - assert_equal true, klass.instance_method_already_implemented?(method), "##{method} is not defined" + assert klass.instance_method_already_implemented?(method), "##{method} is not defined" end end @@ -228,6 +228,40 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal [:field_b], Minimalistic.skip_time_zone_conversion_for_attributes end + def test_read_attributes_respect_access_control + privatize("title") + + topic = @target.new(:title => "The pros and cons of programming naked.") + assert !topic.respond_to?(:title) + assert_raise(NoMethodError) { topic.title } + topic.send(:title) + end + + def test_write_attributes_respect_access_control + privatize("title=(value)") + + topic = @target.new + assert !topic.respond_to?(:title=) + assert_raise(NoMethodError) { topic.title = "Pants"} + topic.send(:title=, "Very large pants") + end + + def test_question_attributes_respect_access_control + privatize("title?") + + topic = @target.new(:title => "Isaac Newton's pants") + assert !topic.respond_to?(:title?) + assert_raise(NoMethodError) { topic.title? } + assert topic.send(:title?) + end + + def test_bulk_update_respects_access_control + privatize("title=(value)") + + assert_raise(ActiveRecord::UnknownAttributeError) { topic = @target.new(:title => "Rants about pants") } + assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } + end + private def time_related_columns_on_topic Topic.columns.select{|c| [:time, :date, :datetime, :timestamp].include?(c.type)}.map(&:name) @@ -244,4 +278,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase Time.zone = old_zone ActiveRecord::Base.time_zone_aware_attributes = old_tz end + + def privatize(method_signature) + @target.class_eval <<-private_method + private + def #{method_signature} + "I'm private" + end + private_method + end end -- cgit v1.2.3 From ea609b265ffc30cac00bf09a262027f96964ed6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarmo=20T=C3=A4nav?= Date: Fri, 26 Sep 2008 20:57:56 +0300 Subject: Ignore all exceptions for validates_acceptance_of columns fetch so it can run even without a database connection Signed-off-by: Michael Koziarski --- activerecord/lib/active_record/validations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 684fb1de88..ed366527ce 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -472,7 +472,7 @@ module ActiveRecord db_cols = begin column_names - rescue ActiveRecord::StatementInvalid + rescue Exception # To ignore both statement and connection errors [] end names = attr_names.reject { |name| db_cols.include?(name.to_s) } -- cgit v1.2.3