From c2b075bed084a59a674469d09db016aaa3365c2e Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Fri, 24 Jul 2009 00:25:27 -0500 Subject: Concernify AR AttributeMethods --- activerecord/lib/active_record.rb | 10 +- .../lib/active_record/attribute_methods.rb | 214 ++------------------- .../attribute_methods/before_type_cast.rb | 21 ++ .../lib/active_record/attribute_methods/dirty.rb | 187 ++++++++++++++++++ .../lib/active_record/attribute_methods/query.rb | 37 ++++ .../lib/active_record/attribute_methods/read.rb | 79 ++++++++ .../attribute_methods/time_zone_conversion.rb | 60 ++++++ .../lib/active_record/attribute_methods/write.rb | 36 ++++ activerecord/lib/active_record/base.rb | 4 +- activerecord/lib/active_record/dirty.rb | 186 ------------------ 10 files changed, 451 insertions(+), 383 deletions(-) create mode 100644 activerecord/lib/active_record/attribute_methods/before_type_cast.rb create mode 100644 activerecord/lib/active_record/attribute_methods/dirty.rb create mode 100644 activerecord/lib/active_record/attribute_methods/query.rb create mode 100644 activerecord/lib/active_record/attribute_methods/read.rb create mode 100644 activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb create mode 100644 activerecord/lib/active_record/attribute_methods/write.rb delete mode 100644 activerecord/lib/active_record/dirty.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 63eb5c3eeb..b2c1c9c024 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -52,7 +52,6 @@ module ActiveRecord autoload :Batches, 'active_record/batches' autoload :Calculations, 'active_record/calculations' autoload :Callbacks, 'active_record/callbacks' - autoload :Dirty, 'active_record/dirty' autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match' autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match' autoload :Migration, 'active_record/migration' @@ -71,6 +70,15 @@ module ActiveRecord autoload :Transactions, 'active_record/transactions' autoload :Validations, 'active_record/validations' + module AttributeMethods + autoload :BeforeTypeCast, 'active_record/attribute_methods/before_type_cast' + autoload :Dirty, 'active_record/attribute_methods/dirty' + autoload :Query, 'active_record/attribute_methods/query' + autoload :Read, 'active_record/attribute_methods/read' + autoload :TimeZoneConversion, 'active_record/attribute_methods/time_zone_conversion' + autoload :Write, 'active_record/attribute_methods/write' + end + module Locking autoload :Optimistic, 'active_record/locking/optimistic' autoload :Pessimistic, 'active_record/locking/pessimistic' diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ecd2d57a5a..dc42f05635 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -4,20 +4,11 @@ module ActiveRecord module AttributeMethods #:nodoc: extend ActiveSupport::Concern - DEFAULT_SUFFIXES = %w(= ? _before_type_cast) ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] included do - attribute_method_suffix(*DEFAULT_SUFFIXES) - cattr_accessor :attribute_types_cached_by_default, :instance_writer => false self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - - cattr_accessor :time_zone_aware_attributes, :instance_writer => false - self.time_zone_aware_attributes = false - - class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false - self.skip_time_zone_conversion_for_attributes = [] end # Declare and check for suffixed attribute methods. @@ -60,42 +51,32 @@ module ActiveRecord @@attribute_method_regexp.match(method_name) end - # Contains the names of the generated attribute methods. def generated_methods #:nodoc: @generated_methods ||= Set.new end - + def generated_methods? !generated_methods.empty? end - + # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods return if generated_methods? - columns_hash.each do |name, column| - unless instance_method_already_implemented?(name) - if self.serialized_attributes[name] - define_read_method_for_serialized_attribute(name) - elsif create_time_zone_conversion_attribute?(name, column) - define_read_method_for_time_zone_conversion(name) - else - define_read_method(name.to_sym, name, column) - end - end - - unless instance_method_already_implemented?("#{name}=") - if create_time_zone_conversion_attribute?(name, column) - define_write_method_for_time_zone_conversion(name) - else - define_write_method(name.to_sym) + columns_hash.keys.each do |name| + # TODO: Generate for all defined suffixes + ["", "=", "?"].each do |suffix| + method_name = "#{name}#{suffix}" + unless instance_method_already_implemented?(method_name) + generate_method = "define_attribute_method#{suffix}" + if respond_to?(generate_method) + send(generate_method, name) + else + evaluate_attribute_method(name, "def #{method_name}(*args); attribute#{suffix}('#{name}', *args); end", method_name) + end end end - - unless instance_method_already_implemented?("#{name}?") - define_question_method(name) - end end end @@ -104,14 +85,12 @@ module ActiveRecord # method is defined by Active Record though. def instance_method_already_implemented?(method_name) method_name = method_name.to_s - return true if method_name =~ /^id(=$|\?$|$)/ + return true if method_name =~ /^id(=$|\?$|$)/ # TODO: Check against all defined suffixes @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @_defined_class_methods.include?(method_name) end - - alias :define_read_methods :define_attribute_methods # +cache_attributes+ allows you to declare which converted attribute values should # be cached. Usually caching only pays off for attributes with expensive conversion @@ -133,83 +112,18 @@ module ActiveRecord end private - # Suffixes a, ?, c become regexp /(a|\?|c)$/ def rebuild_attribute_method_regexp suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) } @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze end - # Default to =, ?, _before_type_cast def attribute_method_suffixes @@attribute_method_suffixes ||= [] end - - def create_time_zone_conversion_attribute?(name, column) - time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type) - end - - # Define an attribute reader method. Cope with nil column. - def define_read_method(symbol, attr_name, column) - cast_code = column.type_cast_code('v') if column - access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" - - unless attr_name.to_s == self.primary_key.to_s - access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") - end - - if cache_attribute?(attr_name) - access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" - end - evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end" - end - - # Define read method for serialized attribute. - def define_read_method_for_serialized_attribute(attr_name) - evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end" - end - - # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone. - def define_read_method_for_time_zone_conversion(attr_name) - method_body = <<-EOV - def #{attr_name}(reload = false) - cached = @attributes_cache['#{attr_name}'] - return cached if cached && !reload - time = read_attribute('#{attr_name}') - @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time - end - EOV - evaluate_attribute_method attr_name, method_body - end - - # Defines a predicate method attr_name?. - def define_question_method(attr_name) - evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?" - end - - def define_write_method(attr_name) - evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}=" - end - - # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. - # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone. - def define_write_method_for_time_zone_conversion(attr_name) - method_body = <<-EOV - def #{attr_name}=(time) - unless time.acts_like?(:time) - time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time - end - time = time.in_time_zone rescue nil if time - write_attribute(:#{attr_name}, time) - end - EOV - evaluate_attribute_method attr_name, method_body, "#{attr_name}=" - end # Evaluate the definition for an attribute related method - def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name) - + def evaluate_attribute_method(attr_name, method_definition, method_name) unless method_name.to_s == primary_key.to_s generated_methods << method_name end @@ -225,8 +139,7 @@ module ActiveRecord end end end - end # ClassMethods - + end # Allows access to the object attributes, which are held in the @attributes hash, as though they # were first-class methods. So a Person class with a name attribute can use Person#name and @@ -248,7 +161,7 @@ module ActiveRecord return self.send(method_id, *args, &block) end end - + guard_private_attribute_method!(method_name, args) if self.class.primary_key.to_s == method_name id @@ -266,80 +179,6 @@ module ActiveRecord end end - # Returns the value of the attribute identified by attr_name after it has been typecast (for example, - # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). - def read_attribute(attr_name) - attr_name = attr_name.to_s - if !(value = @attributes[attr_name]).nil? - if column = column_for_attribute(attr_name) - if unserializable_attribute?(attr_name, column) - unserialize_attribute(attr_name) - else - column.type_cast(value) - end - else - value - end - else - nil - end - end - - def read_attribute_before_type_cast(attr_name) - @attributes[attr_name] - end - - # Returns true if the attribute is of a text column and marked for serialization. - def unserializable_attribute?(attr_name, column) - column.text? && self.class.serialized_attributes[attr_name] - end - - # Returns the unserialized object of the attribute. - def unserialize_attribute(attr_name) - unserialized_object = object_from_yaml(@attributes[attr_name]) - - if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil? - @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object - else - raise SerializationTypeMismatch, - "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}" - end - end - - - # Updates the attribute identified by attr_name with the specified +value+. Empty strings for fixnum and float - # columns are turned into +nil+. - def write_attribute(attr_name, value) - attr_name = attr_name.to_s - @attributes_cache.delete(attr_name) - if (column = column_for_attribute(attr_name)) && column.number? - @attributes[attr_name] = convert_number_column_value(value) - else - @attributes[attr_name] = value - end - end - - - def query_attribute(attr_name) - unless value = read_attribute(attr_name) - false - else - column = self.class.columns_hash[attr_name] - if column.nil? - if Numeric === value || value !~ /[^0-9]/ - !value.to_i.zero? - else - return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) - !value.blank? - end - elsif column.number? - !value.zero? - else - !value.blank? - end - end - end - # A Person object with a name attribute can ask person.respond_to?(:name), # person.respond_to?(:name=), and person.respond_to?(:name?) # which will all return +true+. @@ -358,7 +197,7 @@ module ActiveRecord return true end end - + if @attributes.nil? return super elsif @attributes.include?(method_name) @@ -376,24 +215,9 @@ module ActiveRecord raise NoMethodError.new("Attempt to call private method", method_name, args) end end - + def missing_attribute(attr_name, stack) raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack end - - # Handle *? for method_missing. - def attribute?(attribute_name) - query_attribute(attribute_name) - end - - # Handle *= for method_missing. - def attribute=(attribute_name, value) - write_attribute(attribute_name, value) - end - - # Handle *_before_type_cast for method_missing. - def attribute_before_type_cast(attribute_name) - read_attribute_before_type_cast(attribute_name) - end end end diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb new file mode 100644 index 0000000000..65845c4d9a --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -0,0 +1,21 @@ +module ActiveRecord + module AttributeMethods + module BeforeTypeCast + extend ActiveSupport::Concern + + included do + attribute_method_suffix "_before_type_cast" + end + + def read_attribute_before_type_cast(attr_name) + @attributes[attr_name] + end + + private + # Handle *_before_type_cast for method_missing. + def attribute_before_type_cast(attribute_name) + read_attribute_before_type_cast(attribute_name) + end + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb new file mode 100644 index 0000000000..b88c84938d --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -0,0 +1,187 @@ +module ActiveRecord + module AttributeMethods + # Track unsaved attribute changes. + # + # A newly instantiated object is unchanged: + # person = Person.find_by_name('uncle bob') + # person.changed? # => false + # + # Change the name: + # person.name = 'Bob' + # person.changed? # => true + # person.name_changed? # => true + # person.name_was # => 'uncle bob' + # person.name_change # => ['uncle bob', 'Bob'] + # person.name = 'Bill' + # person.name_change # => ['uncle bob', 'Bill'] + # + # Save the changes: + # person.save + # person.changed? # => false + # person.name_changed? # => false + # + # Assigning the same value leaves the attribute unchanged: + # person.name = 'Bill' + # person.name_changed? # => false + # person.name_change # => nil + # + # Which attributes have changed? + # person.name = 'bob' + # person.changed # => ['name'] + # person.changes # => { 'name' => ['Bill', 'bob'] } + # + # Before modifying an attribute in-place: + # person.name_will_change! + # person.name << 'by' + # person.name_change # => ['uncle bob', 'uncle bobby'] + module Dirty + extend ActiveSupport::Concern + + DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was'] + + included do + attribute_method_suffix *DIRTY_SUFFIXES + + alias_method_chain :save, :dirty + alias_method_chain :save!, :dirty + alias_method_chain :update, :dirty + alias_method_chain :reload, :dirty + + superclass_delegating_accessor :partial_updates + self.partial_updates = true + end + + # Do any attributes have unsaved changes? + # person.changed? # => false + # person.name = 'bob' + # person.changed? # => true + def changed? + !changed_attributes.empty? + end + + # List of attributes with unsaved changes. + # person.changed # => [] + # person.name = 'bob' + # person.changed # => ['name'] + def changed + changed_attributes.keys + end + + # Map of changed attrs => [original value, new value]. + # person.changes # => {} + # person.name = 'bob' + # person.changes # => { 'name' => ['bill', 'bob'] } + def changes + changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } + end + + # Attempts to +save+ the record and clears changed attributes if successful. + def save_with_dirty(*args) #:nodoc: + if status = save_without_dirty(*args) + changed_attributes.clear + end + status + end + + # Attempts to save! the record and clears changed attributes if successful. + def save_with_dirty!(*args) #:nodoc: + status = save_without_dirty!(*args) + changed_attributes.clear + status + end + + # reload the record and clears changed attributes. + def reload_with_dirty(*args) #:nodoc: + record = reload_without_dirty(*args) + changed_attributes.clear + record + end + + private + # Map of change attr => original value. + def changed_attributes + @changed_attributes ||= {} + end + + # Handle *_changed? for +method_missing+. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end + + # Handle *_change for +method_missing+. + def attribute_change(attr) + [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) + end + + # Handle *_was for +method_missing+. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + # Handle *_will_change! for +method_missing+. + def attribute_will_change!(attr) + changed_attributes[attr] = clone_attribute_value(:read_attribute, attr) + end + + # Wrap write_attribute to remember original attribute value. + def write_attribute(attr, value) + attr = attr.to_s + + # The attribute already has an unsaved change. + if changed_attributes.include?(attr) + old = changed_attributes[attr] + changed_attributes.delete(attr) unless field_changed?(attr, old, value) + else + old = clone_attribute_value(:read_attribute, attr) + changed_attributes[attr] = old if field_changed?(attr, old, value) + end + + # Carry on. + super(attr, value) + end + + def update_with_dirty + if partial_updates? + # Serialized attributes should always be written in case they've been + # changed in place. + update_without_dirty(changed | self.class.serialized_attributes.keys) + else + update_without_dirty + end + end + + def field_changed?(attr, old, value) + if column = column_for_attribute(attr) + if column.number? && column.null && (old.nil? || old == 0) && value.blank? + # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. + # Hence we don't record it as a change if the value changes from nil to ''. + # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll + # be typecast back to 0 (''.to_i => 0) + value = nil + else + value = column.type_cast(value) + end + end + + old != value + end + + module ClassMethods + def self.extended(base) + class << base + alias_method_chain :alias_attribute, :dirty + end + end + + def alias_attribute_with_dirty(new_name, old_name) + alias_attribute_without_dirty(new_name, old_name) + DIRTY_SUFFIXES.each do |suffix| + module_eval <<-STR, __FILE__, __LINE__+1 + def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end + STR + end + end + end + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb new file mode 100644 index 0000000000..a949d80120 --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -0,0 +1,37 @@ +module ActiveRecord + module AttributeMethods + module Query + extend ActiveSupport::Concern + + included do + attribute_method_suffix "?" + end + + def query_attribute(attr_name) + unless value = read_attribute(attr_name) + false + else + column = self.class.columns_hash[attr_name] + if column.nil? + if Numeric === value || value !~ /[^0-9]/ + !value.to_i.zero? + else + return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) + !value.blank? + end + elsif column.number? + !value.zero? + else + !value.blank? + end + end + end + + private + # Handle *? for method_missing. + def attribute?(attribute_name) + query_attribute(attribute_name) + end + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb new file mode 100644 index 0000000000..9f948536fc --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -0,0 +1,79 @@ +module ActiveRecord + module AttributeMethods + module Read + extend ActiveSupport::Concern + + # included do + # attribute_method_suffix "" + # end + + module ClassMethods + protected + def define_attribute_method(attr_name) + if self.serialized_attributes[attr_name] + define_read_method_for_serialized_attribute(attr_name) + else + define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) + end + end + + private + # Define read method for serialized attribute. + def define_read_method_for_serialized_attribute(attr_name) + evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end", attr_name + end + + # Define an attribute reader method. Cope with nil column. + def define_read_method(symbol, attr_name, column) + cast_code = column.type_cast_code('v') if column + access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']" + + unless attr_name.to_s == self.primary_key.to_s + access_code = access_code.insert(0, "missing_attribute('#{attr_name}', caller) unless @attributes.has_key?('#{attr_name}'); ") + end + + if cache_attribute?(attr_name) + access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" + end + evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end", attr_name + end + end + + # Returns the value of the attribute identified by attr_name after it has been typecast (for example, + # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). + def read_attribute(attr_name) + attr_name = attr_name.to_s + if !(value = @attributes[attr_name]).nil? + if column = column_for_attribute(attr_name) + if unserializable_attribute?(attr_name, column) + unserialize_attribute(attr_name) + else + column.type_cast(value) + end + else + value + end + else + nil + end + end + + # Returns true if the attribute is of a text column and marked for serialization. + def unserializable_attribute?(attr_name, column) + column.text? && self.class.serialized_attributes[attr_name] + end + + # Returns the unserialized object of the attribute. + def unserialize_attribute(attr_name) + unserialized_object = object_from_yaml(@attributes[attr_name]) + + if unserialized_object.is_a?(self.class.serialized_attributes[attr_name]) || unserialized_object.nil? + @attributes.frozen? ? unserialized_object : @attributes[attr_name] = unserialized_object + else + raise SerializationTypeMismatch, + "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}" + end + end + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb new file mode 100644 index 0000000000..f379b4c0f8 --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -0,0 +1,60 @@ +module ActiveRecord + module AttributeMethods + module TimeZoneConversion + extend ActiveSupport::Concern + + included do + cattr_accessor :time_zone_aware_attributes, :instance_writer => false + self.time_zone_aware_attributes = false + + class_inheritable_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false + self.skip_time_zone_conversion_for_attributes = [] + end + + module ClassMethods + protected + # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. + # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone. + def define_attribute_method(attr_name) + if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) + method_body = <<-EOV + def #{attr_name}(reload = false) + cached = @attributes_cache['#{attr_name}'] + return cached if cached && !reload + time = read_attribute('#{attr_name}') + @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time + end + EOV + evaluate_attribute_method attr_name, method_body, attr_name + else + super + end + end + + # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. + # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone. + def define_attribute_method=(attr_name) + if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) + method_body = <<-EOV + def #{attr_name}=(time) + unless time.acts_like?(:time) + time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time + end + time = time.in_time_zone rescue nil if time + write_attribute(:#{attr_name}, time) + end + EOV + evaluate_attribute_method attr_name, method_body, "#{attr_name}=" + else + super + end + end + + private + def create_time_zone_conversion_attribute?(name, column) + time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type) + end + end + end + end +end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb new file mode 100644 index 0000000000..aab816899c --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -0,0 +1,36 @@ +module ActiveRecord + module AttributeMethods + module Write + extend ActiveSupport::Concern + + included do + attribute_method_suffix "=" + end + + module ClassMethods + protected + def define_attribute_method=(attr_name) + evaluate_attribute_method attr_name, "def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", "#{attr_name}=" + end + end + + # Updates the attribute identified by attr_name with the specified +value+. Empty strings for fixnum and float + # columns are turned into +nil+. + def write_attribute(attr_name, value) + attr_name = attr_name.to_s + @attributes_cache.delete(attr_name) + if (column = column_for_attribute(attr_name)) && column.number? + @attributes[attr_name] = convert_number_column_value(value) + else + @attributes[attr_name] = value + end + end + + private + # Handle *= for method_missing. + def attribute=(attribute_name, value) + write_attribute(attribute_name, value) + end + end + end +end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 5a36ff5ba2..f3cb905754 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -3212,7 +3212,9 @@ module ActiveRecord #:nodoc: include Validations include Locking::Optimistic, Locking::Pessimistic include AttributeMethods - include Dirty + include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query + include AttributeMethods::TimeZoneConversion + include AttributeMethods::Dirty include Callbacks, ActiveModel::Observing, Timestamp include Associations, AssociationPreload, NamedScope include ActiveModel::Conversion diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb deleted file mode 100644 index 178767e0c3..0000000000 --- a/activerecord/lib/active_record/dirty.rb +++ /dev/null @@ -1,186 +0,0 @@ -module ActiveRecord - # Track unsaved attribute changes. - # - # A newly instantiated object is unchanged: - # person = Person.find_by_name('uncle bob') - # person.changed? # => false - # - # Change the name: - # person.name = 'Bob' - # person.changed? # => true - # person.name_changed? # => true - # person.name_was # => 'uncle bob' - # person.name_change # => ['uncle bob', 'Bob'] - # person.name = 'Bill' - # person.name_change # => ['uncle bob', 'Bill'] - # - # Save the changes: - # person.save - # person.changed? # => false - # person.name_changed? # => false - # - # Assigning the same value leaves the attribute unchanged: - # person.name = 'Bill' - # person.name_changed? # => false - # person.name_change # => nil - # - # Which attributes have changed? - # person.name = 'bob' - # person.changed # => ['name'] - # person.changes # => { 'name' => ['Bill', 'bob'] } - # - # Before modifying an attribute in-place: - # person.name_will_change! - # person.name << 'by' - # person.name_change # => ['uncle bob', 'uncle bobby'] - module Dirty - extend ActiveSupport::Concern - - DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was'] - - included do - attribute_method_suffix *DIRTY_SUFFIXES - - alias_method_chain :write_attribute, :dirty - alias_method_chain :save, :dirty - alias_method_chain :save!, :dirty - alias_method_chain :update, :dirty - alias_method_chain :reload, :dirty - - superclass_delegating_accessor :partial_updates - self.partial_updates = true - end - - # Do any attributes have unsaved changes? - # person.changed? # => false - # person.name = 'bob' - # person.changed? # => true - def changed? - !changed_attributes.empty? - end - - # List of attributes with unsaved changes. - # person.changed # => [] - # person.name = 'bob' - # person.changed # => ['name'] - def changed - changed_attributes.keys - end - - # Map of changed attrs => [original value, new value]. - # person.changes # => {} - # person.name = 'bob' - # person.changes # => { 'name' => ['bill', 'bob'] } - def changes - changed.inject({}) { |h, attr| h[attr] = attribute_change(attr); h } - end - - # Attempts to +save+ the record and clears changed attributes if successful. - def save_with_dirty(*args) #:nodoc: - if status = save_without_dirty(*args) - changed_attributes.clear - end - status - end - - # Attempts to save! the record and clears changed attributes if successful. - def save_with_dirty!(*args) #:nodoc: - status = save_without_dirty!(*args) - changed_attributes.clear - status - end - - # reload the record and clears changed attributes. - def reload_with_dirty(*args) #:nodoc: - record = reload_without_dirty(*args) - changed_attributes.clear - record - end - - private - # Map of change attr => original value. - def changed_attributes - @changed_attributes ||= {} - end - - # Handle *_changed? for +method_missing+. - def attribute_changed?(attr) - changed_attributes.include?(attr) - end - - # Handle *_change for +method_missing+. - def attribute_change(attr) - [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) - end - - # Handle *_was for +method_missing+. - def attribute_was(attr) - attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) - end - - # Handle *_will_change! for +method_missing+. - def attribute_will_change!(attr) - changed_attributes[attr] = clone_attribute_value(:read_attribute, attr) - end - - # Wrap write_attribute to remember original attribute value. - def write_attribute_with_dirty(attr, value) - attr = attr.to_s - - # The attribute already has an unsaved change. - if changed_attributes.include?(attr) - old = changed_attributes[attr] - changed_attributes.delete(attr) unless field_changed?(attr, old, value) - else - old = clone_attribute_value(:read_attribute, attr) - changed_attributes[attr] = old if field_changed?(attr, old, value) - end - - # Carry on. - write_attribute_without_dirty(attr, value) - end - - def update_with_dirty - if partial_updates? - # Serialized attributes should always be written in case they've been - # changed in place. - update_without_dirty(changed | self.class.serialized_attributes.keys) - else - update_without_dirty - end - end - - def field_changed?(attr, old, value) - if column = column_for_attribute(attr) - if column.number? && column.null && (old.nil? || old == 0) && value.blank? - # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values. - # Hence we don't record it as a change if the value changes from nil to ''. - # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll - # be typecast back to 0 (''.to_i => 0) - value = nil - else - value = column.type_cast(value) - end - end - - old != value - end - - module ClassMethods - def self.extended(base) - class << base - alias_method_chain :alias_attribute, :dirty - end - end - - def alias_attribute_with_dirty(new_name, old_name) - alias_attribute_without_dirty(new_name, old_name) - DIRTY_SUFFIXES.each do |suffix| - module_eval <<-STR, __FILE__, __LINE__+1 - def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end - STR - end - end - end - end -end -- cgit v1.2.3 From 94dabf9b4bf14de665cb17570209078f16920e54 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 28 Jul 2009 23:52:53 -0500 Subject: Generate methods for all suffixes --- activerecord/lib/active_record/attribute_methods.rb | 15 ++++----------- activerecord/lib/active_record/attribute_methods/read.rb | 11 ++++++++--- activerecord/lib/active_record/base.rb | 4 ---- activerecord/test/cases/attribute_methods_test.rb | 14 -------------- 4 files changed, 12 insertions(+), 32 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index dc42f05635..9a5d719179 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -65,8 +65,7 @@ module ActiveRecord def define_attribute_methods return if generated_methods? columns_hash.keys.each do |name| - # TODO: Generate for all defined suffixes - ["", "=", "?"].each do |suffix| + attribute_method_suffixes.each do |suffix| method_name = "#{name}#{suffix}" unless instance_method_already_implemented?(method_name) generate_method = "define_attribute_method#{suffix}" @@ -81,14 +80,10 @@ module ActiveRecord end # Checks whether the method is defined in the model or any of its subclasses - # that also derive from Active Record. Raises DangerousAttributeError if the - # method is defined by Active Record though. + # that also derive from Active Record. def instance_method_already_implemented?(method_name) method_name = method_name.to_s - return true if method_name =~ /^id(=$|\?$|$)/ # TODO: Check against all defined suffixes - @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set - @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set - raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) + @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map(&:to_s).to_set @_defined_class_methods.include?(method_name) end @@ -124,9 +119,7 @@ module ActiveRecord # Evaluate the definition for an attribute related method def evaluate_attribute_method(attr_name, method_definition, method_name) - unless method_name.to_s == primary_key.to_s - generated_methods << method_name - end + generated_methods << method_name begin class_eval(method_definition, __FILE__, __LINE__) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 9f948536fc..dda700d04c 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -3,9 +3,9 @@ module ActiveRecord module Read extend ActiveSupport::Concern - # included do - # attribute_method_suffix "" - # end + included do + attribute_method_suffix "" + end module ClassMethods protected @@ -74,6 +74,11 @@ module ActiveRecord "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, but was a #{unserialized_object.class.to_s}" end end + + private + def attribute(attribute_name) + read_attribute(attribute_name) + end end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index f3cb905754..b64c76b558 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -144,10 +144,6 @@ module ActiveRecord #:nodoc: class Rollback < ActiveRecordError end - # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods). - class DangerousAttributeError < ActiveRecordError - end - # Raised when you've tried to access a column which wasn't loaded by your finder. # Typically this is because :select has been specified. class MissingAttributeError < NoMethodError diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 183be1e2f9..77e406bbba 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -74,10 +74,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_primary_key_implemented - 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 @@ -96,16 +92,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model - %w(save create_or_update).each do |method| - klass = Class.new ActiveRecord::Base - klass.class_eval "def #{method}() 'defined #{method}' end" - assert_raise ActiveRecord::DangerousAttributeError do - klass.instance_method_already_implemented?(method) - end - end - end - def test_only_time_related_columns_are_meant_to_be_cached_by_default expected = %w(datetime timestamp time date).sort assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort -- cgit v1.2.3 From bd07c5ca8e67c93885d96a24dcf1167d6d503a8c Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 29 Jul 2009 21:00:46 -0500 Subject: cache_attributes is related to attribute reading --- activerecord/lib/active_record/attribute_methods.rb | 19 ------------------- .../lib/active_record/attribute_methods/read.rb | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 9a5d719179..b41c7ffdab 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -87,25 +87,6 @@ module ActiveRecord @_defined_class_methods.include?(method_name) end - # +cache_attributes+ allows you to declare which converted attribute values should - # be cached. Usually caching only pays off for attributes with expensive conversion - # methods, like time related columns (e.g. +created_at+, +updated_at+). - def cache_attributes(*attribute_names) - attribute_names.each {|attr| cached_attributes << attr.to_s} - end - - # Returns the attributes which are cached. By default time related columns - # with datatype :datetime, :timestamp, :time, :date are cached. - def cached_attributes - @cached_attributes ||= - columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set - end - - # Returns +true+ if the provided attribute is being cached. - def cache_attribute?(attr_name) - cached_attributes.include?(attr_name) - end - private # Suffixes a, ?, c become regexp /(a|\?|c)$/ def rebuild_attribute_method_regexp diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index dda700d04c..5285fda868 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -8,6 +8,25 @@ module ActiveRecord end module ClassMethods + # +cache_attributes+ allows you to declare which converted attribute values should + # be cached. Usually caching only pays off for attributes with expensive conversion + # methods, like time related columns (e.g. +created_at+, +updated_at+). + def cache_attributes(*attribute_names) + attribute_names.each {|attr| cached_attributes << attr.to_s} + end + + # Returns the attributes which are cached. By default time related columns + # with datatype :datetime, :timestamp, :time, :date are cached. + def cached_attributes + @cached_attributes ||= + columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map{|col| col.name}.to_set + end + + # Returns +true+ if the provided attribute is being cached. + def cache_attribute?(attr_name) + cached_attributes.include?(attr_name) + end + protected def define_attribute_method(attr_name) if self.serialized_attributes[attr_name] -- cgit v1.2.3 From 586baf8ffea46f9daa96df7c0258591d89f57aeb Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 29 Jul 2009 21:33:27 -0500 Subject: read_attribute is always available through attribute --- activerecord/lib/active_record/attribute_methods.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index b41c7ffdab..ef510a1a2c 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -146,8 +146,6 @@ module ActiveRecord else super end - elsif @attributes.include?(method_name) - read_attribute(method_name) else super end @@ -172,11 +170,7 @@ module ActiveRecord end end - if @attributes.nil? - return super - elsif @attributes.include?(method_name) - return true - elsif md = self.class.match_attribute_method?(method_name) + if md = self.class.match_attribute_method?(method_name) return true if @attributes.include?(md.pre_match) end super -- cgit v1.2.3 From e129c5673a99a09ae64759dafbe907aade16cf2b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Wed, 29 Jul 2009 21:59:02 -0500 Subject: Wrap up attribute method reset concerns in 'undefine_attribute_methods' --- activerecord/lib/active_record/attribute_methods.rb | 5 +++++ activerecord/lib/active_record/base.rb | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index ef510a1a2c..8370c212ca 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -79,6 +79,11 @@ module ActiveRecord end end + def undefine_attribute_methods + generated_methods.each { |name| undef_method(name) } + @generated_methods = nil + end + # Checks whether the method is defined in the model or any of its subclasses # that also derive from Active Record. def instance_method_already_implemented?(method_name) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b64c76b558..b93dfaf987 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1397,8 +1397,8 @@ module ActiveRecord #:nodoc: # end # end def reset_column_information - generated_methods.each { |name| undef_method(name) } - @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @generated_methods = @inheritance_column = nil + undefine_attribute_methods + @column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil end def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc: -- cgit v1.2.3 From 3e58f8e19117346ce459b7531379525d3143608a Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 00:07:10 -0500 Subject: Restore DangerousAttributeError --- activerecord/lib/active_record/attribute_methods.rb | 12 +++++++++--- activerecord/lib/active_record/base.rb | 4 ++++ activerecord/test/cases/attribute_methods_test.rb | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 8370c212ca..c7e6fe6a29 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -85,10 +85,14 @@ module ActiveRecord end # Checks whether the method is defined in the model or any of its subclasses - # that also derive from Active Record. + # that also derive from Active Record. Raises DangerousAttributeError if the + # method is defined by Active Record though. def instance_method_already_implemented?(method_name) method_name = method_name.to_s - @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map(&:to_s).to_set + return true if method_name =~ /^id(=$|\?$|_before_type_cast$|$)/ + @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set + @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set + raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @_defined_class_methods.include?(method_name) end @@ -105,7 +109,9 @@ module ActiveRecord # Evaluate the definition for an attribute related method def evaluate_attribute_method(attr_name, method_definition, method_name) - generated_methods << method_name + unless method_name.to_s == primary_key.to_s + generated_methods << method_name + end begin class_eval(method_definition, __FILE__, __LINE__) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b93dfaf987..7de5bf8a77 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -144,6 +144,10 @@ module ActiveRecord #:nodoc: class Rollback < ActiveRecordError end + # Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods). + class DangerousAttributeError < ActiveRecordError + end + # Raised when you've tried to access a column which wasn't loaded by your finder. # Typically this is because :select has been specified. class MissingAttributeError < NoMethodError diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 77e406bbba..183be1e2f9 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -74,6 +74,10 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_primary_key_implemented + 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 @@ -92,6 +96,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_raises_dangerous_attribute_error_when_defining_activerecord_method_in_model + %w(save create_or_update).each do |method| + klass = Class.new ActiveRecord::Base + klass.class_eval "def #{method}() 'defined #{method}' end" + assert_raise ActiveRecord::DangerousAttributeError do + klass.instance_method_already_implemented?(method) + end + end + end + def test_only_time_related_columns_are_meant_to_be_cached_by_default expected = %w(datetime timestamp time date).sort assert_equal expected, ActiveRecord::Base.attribute_types_cached_by_default.map(&:to_s).sort -- cgit v1.2.3 From 1841fd54e6b76e4e1af3c8e4ef11ce2df3e3234e Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 12:33:39 -0500 Subject: Move id attribute methods into their related concern --- .../attribute_methods/before_type_cast.rb | 12 +++++++++ .../lib/active_record/attribute_methods/read.rb | 11 ++++++++ .../lib/active_record/attribute_methods/write.rb | 5 ++++ activerecord/lib/active_record/base.rb | 29 ---------------------- 4 files changed, 28 insertions(+), 29 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index 65845c4d9a..8815f07df6 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -11,6 +11,18 @@ module ActiveRecord @attributes[attr_name] end + # Returns a hash of attributes before typecasting and deserialization. + def attributes_before_type_cast + self.attribute_names.inject({}) do |attrs, name| + attrs[name] = read_attribute_before_type_cast(name) + attrs + end + end + + def id_before_type_cast #:nodoc: + read_attribute_before_type_cast(self.class.primary_key) + end + private # Handle *_before_type_cast for method_missing. def attribute_before_type_cast(attribute_name) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 5285fda868..7e92e0bd68 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -77,6 +77,17 @@ module ActiveRecord end end + # A model instance's primary key is always available as model.id + # whether you name it the default 'id' or set it to something else. + def id + attr_name = self.class.primary_key + column = column_for_attribute(attr_name) + + self.class.send(:define_read_method, :id, attr_name, column) + # now that the method exists, call it + self.send attr_name.to_sym + end + # Returns true if the attribute is of a text column and marked for serialization. def unserializable_attribute?(attr_name, column) column.text? && self.class.serialized_attributes[attr_name] diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index aab816899c..140057c186 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -26,6 +26,11 @@ module ActiveRecord end end + # Sets the primary ID. + def id=(value) + write_attribute(self.class.primary_key, value) + end + private # Handle *= for method_missing. def attribute=(attribute_name, value) diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7de5bf8a77..7fabb5bfc6 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2508,18 +2508,6 @@ module ActiveRecord #:nodoc: result end - # A model instance's primary key is always available as model.id - # whether you name it the default 'id' or set it to something else. - def id - attr_name = self.class.primary_key - column = column_for_attribute(attr_name) - - self.class.send(:define_read_method, :id, attr_name, column) - # now that the method exists, call it - self.send attr_name.to_sym - - end - # Returns a String, which Action Pack uses for constructing an URL to this # object. The default implementation returns this record's id as a String, # or nil if this record's unsaved. @@ -2565,19 +2553,10 @@ module ActiveRecord #:nodoc: end end - def id_before_type_cast #:nodoc: - read_attribute_before_type_cast(self.class.primary_key) - end - def quoted_id #:nodoc: quote_value(id, column_for_attribute(self.class.primary_key)) end - # Sets the primary ID. - def id=(value) - write_attribute(self.class.primary_key, value) - end - # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false. def new_record? @new_record || false @@ -2822,14 +2801,6 @@ module ActiveRecord #:nodoc: end end - # Returns a hash of attributes before typecasting and deserialization. - def attributes_before_type_cast - self.attribute_names.inject({}) do |attrs, name| - attrs[name] = read_attribute_before_type_cast(name) - attrs - end - end - # Returns an #inspect-like string for the value of the # attribute +attr_name+. String attributes are elided after 50 # characters, and Date and Time attributes are returned in the -- cgit v1.2.3 From 831c38ffc7f3fb25284ceba82124807351de0371 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 12:38:19 -0500 Subject: Don't define id_before_type_cast, just let it be generated on its own --- activerecord/lib/active_record/attribute_methods.rb | 2 +- .../lib/active_record/attribute_methods/before_type_cast.rb | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index c7e6fe6a29..c066b2318a 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -89,7 +89,7 @@ module ActiveRecord # method is defined by Active Record though. def instance_method_already_implemented?(method_name) method_name = method_name.to_s - return true if method_name =~ /^id(=$|\?$|_before_type_cast$|$)/ + return true if method_name =~ /^id(=$|\?$|$)/ @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index 8815f07df6..a4e144f233 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -19,14 +19,14 @@ module ActiveRecord end end - def id_before_type_cast #:nodoc: - read_attribute_before_type_cast(self.class.primary_key) - end - private # Handle *_before_type_cast for method_missing. def attribute_before_type_cast(attribute_name) - read_attribute_before_type_cast(attribute_name) + if attribute_name == 'id' + read_attribute_before_type_cast(self.class.primary_key) + else + read_attribute_before_type_cast(attribute_name) + end end end end -- cgit v1.2.3 From 9cdcfb4fc5992d51d0773d88bfc1d97c089d536b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 12:57:26 -0500 Subject: ditto for id= --- activerecord/lib/active_record/attribute_methods.rb | 6 +++--- activerecord/lib/active_record/attribute_methods/write.rb | 6 +----- 2 files changed, 4 insertions(+), 8 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index c066b2318a..04a9932a4c 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -89,7 +89,7 @@ module ActiveRecord # method is defined by Active Record though. def instance_method_already_implemented?(method_name) method_name = method_name.to_s - return true if method_name =~ /^id(=$|\?$|$)/ + return true if method_name == "id" @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @@ -152,7 +152,7 @@ module ActiveRecord id elsif md = self.class.match_attribute_method?(method_name) attribute_name, method_type = md.pre_match, md.to_s - if @attributes.include?(attribute_name) + if attribute_name == 'id' || @attributes.include?(attribute_name) __send__("attribute#{method_type}", attribute_name, *args, &block) else super @@ -182,7 +182,7 @@ module ActiveRecord end if md = self.class.match_attribute_method?(method_name) - return true if @attributes.include?(md.pre_match) + return true if md.pre_match == 'id' || @attributes.include?(md.pre_match) end super end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 140057c186..b4f2f6eb1d 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -18,6 +18,7 @@ module ActiveRecord # columns are turned into +nil+. def write_attribute(attr_name, value) attr_name = attr_name.to_s + attr_name = self.class.primary_key if attr_name == 'id' @attributes_cache.delete(attr_name) if (column = column_for_attribute(attr_name)) && column.number? @attributes[attr_name] = convert_number_column_value(value) @@ -26,11 +27,6 @@ module ActiveRecord end end - # Sets the primary ID. - def id=(value) - write_attribute(self.class.primary_key, value) - end - private # Handle *= for method_missing. def attribute=(attribute_name, value) -- cgit v1.2.3 From f8d2c77c9056e16d4f98020c94f9316835c4e099 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 13:33:12 -0500 Subject: Redirect method missing for primary key to read_attribute --- activerecord/lib/active_record/attribute_methods.rb | 4 +--- activerecord/lib/active_record/attribute_methods/read.rb | 4 ++-- 2 files changed, 3 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 04a9932a4c..3eeb7fb4e9 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -148,9 +148,7 @@ module ActiveRecord end guard_private_attribute_method!(method_name, args) - if self.class.primary_key.to_s == method_name - id - elsif md = self.class.match_attribute_method?(method_name) + if md = self.class.match_attribute_method?(method_name) attribute_name, method_type = md.pre_match, md.to_s if attribute_name == 'id' || @attributes.include?(attribute_name) __send__("attribute#{method_type}", attribute_name, *args, &block) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 7e92e0bd68..a3327dc083 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -62,6 +62,7 @@ module ActiveRecord # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name) attr_name = attr_name.to_s + attr_name = self.class.primary_key if attr_name == 'id' if !(value = @attributes[attr_name]).nil? if column = column_for_attribute(attr_name) if unserializable_attribute?(attr_name, column) @@ -84,8 +85,7 @@ module ActiveRecord column = column_for_attribute(attr_name) self.class.send(:define_read_method, :id, attr_name, column) - # now that the method exists, call it - self.send attr_name.to_sym + id end # Returns true if the attribute is of a text column and marked for serialization. -- cgit v1.2.3 From 2c30c9fe6c22f0342aa0be3909efa3a5787dc33d Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 14:05:08 -0500 Subject: Undefine id and let it automatically be generated --- activerecord/lib/active_record/attribute_methods.rb | 8 ++++---- activerecord/lib/active_record/attribute_methods/read.rb | 13 ++----------- activerecord/test/cases/attribute_methods_test.rb | 4 ---- activerecord/test/cases/base_test.rb | 5 ----- 4 files changed, 6 insertions(+), 24 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 3eeb7fb4e9..4c9ea050c4 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -77,6 +77,9 @@ module ActiveRecord end end end + unless generated_methods.include?("id") + define_read_method(:id, primary_key, columns_hash[primary_key.to_s]) + end end def undefine_attribute_methods @@ -89,7 +92,6 @@ module ActiveRecord # method is defined by Active Record though. def instance_method_already_implemented?(method_name) method_name = method_name.to_s - return true if method_name == "id" @_defined_class_methods ||= ancestors.first(ancestors.index(ActiveRecord::Base)).sum([]) { |m| m.public_instance_methods(false) | m.private_instance_methods(false) | m.protected_instance_methods(false) }.map {|m| m.to_s }.to_set @@_defined_activerecord_methods ||= (ActiveRecord::Base.public_instance_methods(false) | ActiveRecord::Base.private_instance_methods(false) | ActiveRecord::Base.protected_instance_methods(false)).map{|m| m.to_s }.to_set raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @@ -109,9 +111,7 @@ module ActiveRecord # Evaluate the definition for an attribute related method def evaluate_attribute_method(attr_name, method_definition, method_name) - unless method_name.to_s == primary_key.to_s - generated_methods << method_name - end + generated_methods << method_name begin class_eval(method_definition, __FILE__, __LINE__) diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index a3327dc083..2ea09499c5 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -5,6 +5,7 @@ module ActiveRecord included do attribute_method_suffix "" + undef_method :id end module ClassMethods @@ -54,7 +55,7 @@ module ActiveRecord if cache_attribute?(attr_name) access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" end - evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end", attr_name + evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end", symbol end end @@ -78,16 +79,6 @@ module ActiveRecord end end - # A model instance's primary key is always available as model.id - # whether you name it the default 'id' or set it to something else. - def id - attr_name = self.class.primary_key - column = column_for_attribute(attr_name) - - self.class.send(:define_read_method, :id, attr_name, column) - id - end - # Returns true if the attribute is of a text column and marked for serialization. def unserializable_attribute?(attr_name, column) column.text? && self.class.serialized_attributes[attr_name] diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 183be1e2f9..168b617fbc 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -74,10 +74,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end - def test_primary_key_implemented - 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 diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e47f898485..7169d93841 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -422,11 +422,6 @@ class BasicsTest < ActiveRecord::TestCase end - def test_reader_for_invalid_column_names - Topic.send(:define_read_method, "mumub-jumbo".to_sym, "mumub-jumbo", nil) - assert !Topic.generated_methods.include?("mumub-jumbo") - end - def test_non_attribute_access_and_assignment topic = Topic.new assert !topic.respond_to?("mumbo") -- cgit v1.2.3 From 89e9efcbe245475dec1206373755f075e17fa3e2 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 14:16:36 -0500 Subject: Don't need to pass attr_name to evaluate_attribute_method anymore --- activerecord/lib/active_record/attribute_methods.rb | 8 ++++---- activerecord/lib/active_record/attribute_methods/read.rb | 4 ++-- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 4 ++-- activerecord/lib/active_record/attribute_methods/write.rb | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 4c9ea050c4..8686db1793 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -72,7 +72,7 @@ module ActiveRecord if respond_to?(generate_method) send(generate_method, name) else - evaluate_attribute_method(name, "def #{method_name}(*args); attribute#{suffix}('#{name}', *args); end", method_name) + evaluate_attribute_method("def #{method_name}(*args); attribute#{suffix}('#{name}', *args); end", method_name) end end end @@ -110,16 +110,16 @@ module ActiveRecord end # Evaluate the definition for an attribute related method - def evaluate_attribute_method(attr_name, method_definition, method_name) + def evaluate_attribute_method(method_definition, method_name) generated_methods << method_name begin class_eval(method_definition, __FILE__, __LINE__) rescue SyntaxError => err - generated_methods.delete(attr_name) + generated_methods.delete(method_name) if logger logger.warn "Exception occurred during reader method compilation." - logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?" + logger.warn "Maybe #{method_name} is not a valid Ruby identifier?" logger.warn err.message end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 2ea09499c5..1178c713b4 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -40,7 +40,7 @@ module ActiveRecord private # Define read method for serialized attribute. def define_read_method_for_serialized_attribute(attr_name) - evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end", attr_name + evaluate_attribute_method "def #{attr_name}; unserialize_attribute('#{attr_name}'); end", attr_name end # Define an attribute reader method. Cope with nil column. @@ -55,7 +55,7 @@ module ActiveRecord if cache_attribute?(attr_name) access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" end - evaluate_attribute_method attr_name, "def #{symbol}; #{access_code}; end", symbol + evaluate_attribute_method "def #{symbol}; #{access_code}; end", symbol end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index f379b4c0f8..9e2c6174c6 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -25,7 +25,7 @@ module ActiveRecord @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time end EOV - evaluate_attribute_method attr_name, method_body, attr_name + evaluate_attribute_method method_body, attr_name else super end @@ -44,7 +44,7 @@ module ActiveRecord write_attribute(:#{attr_name}, time) end EOV - evaluate_attribute_method attr_name, method_body, "#{attr_name}=" + evaluate_attribute_method method_body, "#{attr_name}=" else super end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index b4f2f6eb1d..497e72ee4a 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,7 +10,7 @@ module ActiveRecord module ClassMethods protected def define_attribute_method=(attr_name) - evaluate_attribute_method attr_name, "def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", "#{attr_name}=" + evaluate_attribute_method "def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", "#{attr_name}=" end end -- cgit v1.2.3 From d599ea27c563af2a79ccad3cabc0a1efd4c2427f Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 14:26:08 -0500 Subject: Move attribute_types_cached_by_default into attribute methods reading concern --- activerecord/lib/active_record/attribute_methods.rb | 10 ---------- activerecord/lib/active_record/attribute_methods/read.rb | 11 +++++++++++ 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 8686db1793..e7f3f4cba5 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -4,13 +4,6 @@ module ActiveRecord module AttributeMethods #:nodoc: extend ActiveSupport::Concern - ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] - - included do - cattr_accessor :attribute_types_cached_by_default, :instance_writer => false - self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT - end - # Declare and check for suffixed attribute methods. module ClassMethods # Declares a method available for all attributes with the given suffix. @@ -77,9 +70,6 @@ module ActiveRecord end end end - unless generated_methods.include?("id") - define_read_method(:id, primary_key, columns_hash[primary_key.to_s]) - end end def undefine_attribute_methods diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 1178c713b4..bea332ef26 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -3,8 +3,15 @@ module ActiveRecord module Read extend ActiveSupport::Concern + ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date] + included do attribute_method_suffix "" + + cattr_accessor :attribute_types_cached_by_default, :instance_writer => false + self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT + + # Undefine id so it can be used as an attribute name undef_method :id end @@ -35,6 +42,10 @@ module ActiveRecord else define_read_method(attr_name.to_sym, attr_name, columns_hash[attr_name]) end + + if attr_name == primary_key && attr_name != "id" + define_read_method(:id, attr_name, columns_hash[attr_name]) + end end private -- cgit v1.2.3 From 1ae7eb52006229f6d6975837f0ac1410e366d456 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 16:52:00 -0500 Subject: Make sure to reset defined methods after calling attribute_method_suffix --- activerecord/lib/active_record/attribute_methods.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index e7f3f4cba5..90a6a21981 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -34,8 +34,9 @@ module ActiveRecord # person.name = 'Hubert' # person.name_changed? # => true def attribute_method_suffix(*suffixes) - attribute_method_suffixes.concat suffixes + attribute_method_suffixes.concat(suffixes) rebuild_attribute_method_regexp + undefine_attribute_methods end # Returns MatchData if method_name is an attribute method. @@ -101,12 +102,12 @@ module ActiveRecord # Evaluate the definition for an attribute related method def evaluate_attribute_method(method_definition, method_name) - generated_methods << method_name + generated_methods << method_name.to_s begin class_eval(method_definition, __FILE__, __LINE__) rescue SyntaxError => err - generated_methods.delete(method_name) + generated_methods.delete(method_name.to_s) if logger logger.warn "Exception occurred during reader method compilation." logger.warn "Maybe #{method_name} is not a valid Ruby identifier?" @@ -137,17 +138,14 @@ module ActiveRecord end end - guard_private_attribute_method!(method_name, args) if md = self.class.match_attribute_method?(method_name) attribute_name, method_type = md.pre_match, md.to_s if attribute_name == 'id' || @attributes.include?(attribute_name) - __send__("attribute#{method_type}", attribute_name, *args, &block) - else - super + guard_private_attribute_method!(method_name, args) + return __send__("attribute#{method_type}", attribute_name, *args, &block) end - else - super end + super end # A Person object with a name attribute can ask person.respond_to?(:name), -- cgit v1.2.3 From ded3d97c5ad7e48efc7f4a25d99c1d5773c13af4 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 17:15:32 -0500 Subject: Make sure we use send for the default attribute method body because the suffix maybe an invalid method name --- activerecord/lib/active_record/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 90a6a21981..0d444f0239 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -66,7 +66,7 @@ module ActiveRecord if respond_to?(generate_method) send(generate_method, name) else - evaluate_attribute_method("def #{method_name}(*args); attribute#{suffix}('#{name}', *args); end", method_name) + evaluate_attribute_method("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", method_name) end end end -- cgit v1.2.3 From 62fd1d3716b4b5fd1d91cdcc77003efe80fc5a7e Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Thu, 30 Jul 2009 17:49:14 -0500 Subject: Start separating primary key concerns --- activerecord/lib/active_record.rb | 1 + .../lib/active_record/attribute_methods.rb | 30 ++++++++++ .../active_record/attribute_methods/primary_key.rb | 44 +++++++++++++++ activerecord/lib/active_record/base.rb | 66 +--------------------- 4 files changed, 76 insertions(+), 65 deletions(-) create mode 100644 activerecord/lib/active_record/attribute_methods/primary_key.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index b2c1c9c024..5e9ce0600a 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -73,6 +73,7 @@ module ActiveRecord module AttributeMethods autoload :BeforeTypeCast, 'active_record/attribute_methods/before_type_cast' autoload :Dirty, 'active_record/attribute_methods/dirty' + autoload :PrimaryKey, 'active_record/attribute_methods/primary_key' autoload :Query, 'active_record/attribute_methods/query' autoload :Read, 'active_record/attribute_methods/read' autoload :TimeZoneConversion, 'active_record/attribute_methods/time_zone_conversion' diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 0d444f0239..5cb536af1f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -39,6 +39,36 @@ module ActiveRecord undefine_attribute_methods end + # Defines an "attribute" method (like +inheritance_column+ or + # +table_name+). A new (class) method will be created with the + # given name. If a value is specified, the new method will + # return that value (as a string). Otherwise, the given block + # will be used to compute the value of the method. + # + # The original method will be aliased, with the new name being + # prefixed with "original_". This allows the new method to + # access the original value. + # + # Example: + # + # class A < ActiveRecord::Base + # define_attr_method :primary_key, "sysid" + # define_attr_method( :inheritance_column ) do + # original_inheritance_column + "_id" + # end + # end + def define_attr_method(name, value=nil, &block) + sing = metaclass + sing.send :alias_method, "original_#{name}", name + if block_given? + sing.send :define_method, name, &block + else + # use eval instead of a block to work around a memory leak in dev + # mode in fcgi + sing.class_eval "def #{name}; #{value.to_s.inspect}; end" + end + end + # Returns MatchData if method_name is an attribute method. def match_attribute_method?(method_name) rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb new file mode 100644 index 0000000000..365fdeb55a --- /dev/null +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -0,0 +1,44 @@ +module ActiveRecord + module AttributeMethods + module PrimaryKey + extend ActiveSupport::Concern + + module ClassMethods + # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the + # primary_key_prefix_type setting, though. + def primary_key + reset_primary_key + end + + def reset_primary_key #:nodoc: + key = get_primary_key(base_class.name) + set_primary_key(key) + key + end + + def get_primary_key(base_name) #:nodoc: + key = 'id' + case primary_key_prefix_type + when :table_name + key = base_name.to_s.foreign_key(false) + when :table_name_with_underscore + key = base_name.to_s.foreign_key + end + key + end + + # Sets the name of the primary key column to use to the given value, + # or (if the value is nil or false) to the value returned by the given + # block. + # + # class Project < ActiveRecord::Base + # set_primary_key "sysid" + # end + def set_primary_key(value = nil, &block) + define_attr_method :primary_key, value, &block + end + alias :primary_key= :set_primary_key + end + end + end +end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 7fabb5bfc6..ce93ea8eee 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1225,29 +1225,6 @@ module ActiveRecord #:nodoc: name end - # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the - # primary_key_prefix_type setting, though. - def primary_key - reset_primary_key - end - - def reset_primary_key #:nodoc: - key = get_primary_key(base_class.name) - set_primary_key(key) - key - end - - def get_primary_key(base_name) #:nodoc: - key = 'id' - case primary_key_prefix_type - when :table_name - key = base_name.to_s.foreign_key(false) - when :table_name_with_underscore - key = base_name.to_s.foreign_key - end - key - end - # Defines the column name for use with single table inheritance # -- can be set in subclasses like so: self.inheritance_column = "type_id" def inheritance_column @@ -1277,18 +1254,6 @@ module ActiveRecord #:nodoc: end alias :table_name= :set_table_name - # Sets the name of the primary key column to use to the given value, - # or (if the value is nil or false) to the value returned by the given - # block. - # - # class Project < ActiveRecord::Base - # set_primary_key "sysid" - # end - def set_primary_key(value = nil, &block) - define_attr_method :primary_key, value, &block - end - alias :primary_key= :set_primary_key - # Sets the name of the inheritance column to use to the given value, # or (if the value # is nil or false) to the value returned by the # given block. @@ -2077,36 +2042,6 @@ module ActiveRecord #:nodoc: end end - # Defines an "attribute" method (like +inheritance_column+ or - # +table_name+). A new (class) method will be created with the - # given name. If a value is specified, the new method will - # return that value (as a string). Otherwise, the given block - # will be used to compute the value of the method. - # - # The original method will be aliased, with the new name being - # prefixed with "original_". This allows the new method to - # access the original value. - # - # Example: - # - # class A < ActiveRecord::Base - # define_attr_method :primary_key, "sysid" - # define_attr_method( :inheritance_column ) do - # original_inheritance_column + "_id" - # end - # end - def define_attr_method(name, value=nil, &block) - sing = metaclass - sing.send :alias_method, "original_#{name}", name - if block_given? - sing.send :define_method, name, &block - else - # use eval instead of a block to work around a memory leak in dev - # mode in fcgi - sing.class_eval "def #{name}; #{value.to_s.inspect}; end" - end - end - protected # Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash. # method_name may be :find or :create. :find parameters may include the :conditions, :joins, @@ -3184,6 +3119,7 @@ module ActiveRecord #:nodoc: include Locking::Optimistic, Locking::Pessimistic include AttributeMethods include AttributeMethods::Read, AttributeMethods::Write, AttributeMethods::BeforeTypeCast, AttributeMethods::Query + include AttributeMethods::PrimaryKey include AttributeMethods::TimeZoneConversion include AttributeMethods::Dirty include Callbacks, ActiveModel::Observing, Timestamp -- cgit v1.2.3 From cdf60e46cc01e5f7b14e95a0b7d914516fcdcbc1 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Sat, 1 Aug 2009 18:15:52 -0700 Subject: SQLite: drop support for 'dbfile' option in favor of 'database.' --- activerecord/CHANGELOG | 2 ++ activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 659de99873..5515cf6b1a 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] + * Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha] # employees.company_name references companies.name Employee.belongs_to :company, :primary_key => 'name', :foreign_key => 'company_name' diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 5e5e30776a..c0f5046bff 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -27,7 +27,6 @@ module ActiveRecord private def parse_sqlite_config!(config) - config[:database] ||= config[:dbfile] # Require database. unless config[:database] raise ArgumentError, "No database file specified. Missing argument: database" -- cgit v1.2.3 From 9b68877897a44d4484cde40117abc42c9b029154 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Sun, 2 Aug 2009 21:06:35 -0500 Subject: Track generated attribute methods in a separate module --- .../lib/active_record/attribute_methods.rb | 44 +++++++--------------- .../lib/active_record/attribute_methods/read.rb | 4 +- .../attribute_methods/time_zone_conversion.rb | 4 +- .../lib/active_record/attribute_methods/write.rb | 2 +- 4 files changed, 19 insertions(+), 35 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 5cb536af1f..211b77f514 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -75,19 +75,18 @@ module ActiveRecord @@attribute_method_regexp.match(method_name) end - # Contains the names of the generated attribute methods. def generated_methods #:nodoc: - @generated_methods ||= Set.new - end - - def generated_methods? - !generated_methods.empty? + @generated_methods ||= begin + mod = Module.new + include mod + mod + end end # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods - return if generated_methods? + return unless generated_methods.instance_methods.empty? columns_hash.keys.each do |name| attribute_method_suffixes.each do |suffix| method_name = "#{name}#{suffix}" @@ -96,7 +95,7 @@ module ActiveRecord if respond_to?(generate_method) send(generate_method, name) else - evaluate_attribute_method("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", method_name) + generated_methods.module_eval("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", __FILE__, __LINE__) end end end @@ -104,8 +103,9 @@ module ActiveRecord end def undefine_attribute_methods - generated_methods.each { |name| undef_method(name) } - @generated_methods = nil + generated_methods.module_eval do + instance_methods.each { |m| undef_method(m) } + end end # Checks whether the method is defined in the model or any of its subclasses @@ -129,22 +129,6 @@ module ActiveRecord def attribute_method_suffixes @@attribute_method_suffixes ||= [] end - - # Evaluate the definition for an attribute related method - def evaluate_attribute_method(method_definition, method_name) - generated_methods << method_name.to_s - - begin - class_eval(method_definition, __FILE__, __LINE__) - rescue SyntaxError => err - generated_methods.delete(method_name.to_s) - if logger - logger.warn "Exception occurred during reader method compilation." - logger.warn "Maybe #{method_name} is not a valid Ruby identifier?" - logger.warn err.message - end - end - end end # Allows access to the object attributes, which are held in the @attributes hash, as though they @@ -160,10 +144,10 @@ module ActiveRecord # 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? + if self.class.generated_methods.instance_methods.empty? self.class.define_attribute_methods guard_private_attribute_method!(method_name, args) - if self.class.generated_methods.include?(method_name) + if self.class.generated_methods.instance_methods.include?(method_name) return self.send(method_id, *args, &block) end end @@ -190,9 +174,9 @@ module ActiveRecord # If we're here than we haven't found among non-private methods # but found among all methods. Which means that given method is private. return false - elsif !self.class.generated_methods? + elsif self.class.generated_methods.instance_methods.empty? self.class.define_attribute_methods - if self.class.generated_methods.include?(method_name) + if self.class.generated_methods.instance_methods.include?(method_name) return true end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index bea332ef26..6c0bf072e9 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -51,7 +51,7 @@ module ActiveRecord private # Define read method for serialized attribute. def define_read_method_for_serialized_attribute(attr_name) - evaluate_attribute_method "def #{attr_name}; unserialize_attribute('#{attr_name}'); end", attr_name + generated_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) end # Define an attribute reader method. Cope with nil column. @@ -66,7 +66,7 @@ module ActiveRecord if cache_attribute?(attr_name) access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" end - evaluate_attribute_method "def #{symbol}; #{access_code}; end", symbol + generated_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__) end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 9e2c6174c6..4438c7543e 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -25,7 +25,7 @@ module ActiveRecord @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time end EOV - evaluate_attribute_method method_body, attr_name + generated_methods.module_eval(method_body, __FILE__, __LINE__) else super end @@ -44,7 +44,7 @@ module ActiveRecord write_attribute(:#{attr_name}, time) end EOV - evaluate_attribute_method method_body, "#{attr_name}=" + generated_methods.module_eval(method_body, __FILE__, __LINE__) else super end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 497e72ee4a..c75745c00d 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,7 +10,7 @@ module ActiveRecord module ClassMethods protected def define_attribute_method=(attr_name) - evaluate_attribute_method "def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", "#{attr_name}=" + generated_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) end end -- cgit v1.2.3 From 6f97ad07ded847f29159baf71050c63f04282170 Mon Sep 17 00:00:00 2001 From: Geoff Buesing Date: Mon, 3 Aug 2009 21:54:40 -0500 Subject: quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc [#2946 state:resolved] --- activerecord/CHANGELOG | 2 + .../connection_adapters/abstract/quoting.rb | 7 ++- activerecord/test/cases/base_test.rb | 69 ++++++++++++++++++++++ activerecord/test/cases/finder_test.rb | 50 ++++++++++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 5515cf6b1a..9adc6b887f 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing] + * SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] * Added :primary_key option to belongs_to associations. #765 [Szymon Nowak, Philip Hallstrom, Noel Rocha] diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 720fba29e9..8649f96498 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -60,7 +60,12 @@ module ActiveRecord end def quoted_date(value) - value.to_s(:db) + if value.acts_like?(:time) + zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal + value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value + else + value + end.to_s(:db) end def quoted_string_prefix diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 7169d93841..82eba81549 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -464,6 +464,60 @@ class BasicsTest < ActiveRecord::TestCase end end + def test_preserving_time_objects_with_local_time_conversion_to_default_timezone_utc + with_env_tz 'America/New_York' do + with_active_record_default_timezone :utc do + time = Time.local(2000) + topic = Topic.create('written_on' => time) + saved_time = Topic.find(topic.id).written_on + assert_equal time, saved_time + assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "EST"], time.to_a + assert_equal [0, 0, 5, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a + end + end + end + + def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_utc + with_env_tz 'America/New_York' do + with_active_record_default_timezone :utc do + Time.use_zone 'Central Time (US & Canada)' do + time = Time.zone.local(2000) + topic = Topic.create('written_on' => time) + saved_time = Topic.find(topic.id).written_on + assert_equal time, saved_time + assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a + assert_equal [0, 0, 6, 1, 1, 2000, 6, 1, false, "UTC"], saved_time.to_a + end + end + end + end + + def test_preserving_time_objects_with_utc_time_conversion_to_default_timezone_local + with_env_tz 'America/New_York' do + time = Time.utc(2000) + topic = Topic.create('written_on' => time) + saved_time = Topic.find(topic.id).written_on + assert_equal time, saved_time + assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "UTC"], time.to_a + assert_equal [0, 0, 19, 31, 12, 1999, 5, 365, false, "EST"], saved_time.to_a + end + end + + def test_preserving_time_objects_with_time_with_zone_conversion_to_default_timezone_local + with_env_tz 'America/New_York' do + with_active_record_default_timezone :local do + Time.use_zone 'Central Time (US & Canada)' do + time = Time.zone.local(2000) + topic = Topic.create('written_on' => time) + saved_time = Topic.find(topic.id).written_on + assert_equal time, saved_time + assert_equal [0, 0, 0, 1, 1, 2000, 6, 1, false, "CST"], time.to_a + assert_equal [0, 0, 1, 1, 1, 2000, 6, 1, false, "EST"], saved_time.to_a + end + end + end + end + def test_custom_mutator topic = Topic.find(1) # This mutator is protected in the class definition @@ -2115,4 +2169,19 @@ class BasicsTest < ActiveRecord::TestCase def test_dup assert !Minimalistic.new.freeze.dup.frozen? end + + protected + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield + ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + end + + def with_active_record_default_timezone(zone) + old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone + yield + ensure + ActiveRecord::Base.default_timezone = old_zone + end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d8f5695a0f..55ef0d45eb 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -423,6 +423,42 @@ class FinderTest < ActiveRecord::TestCase assert_equal customers(:david), found_customer end + def test_condition_utc_time_interpolation_with_default_timezone_local + with_env_tz 'America/New_York' do + with_active_record_default_timezone :local do + topic = Topic.first + assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getutc]) + end + end + end + + def test_hash_condition_utc_time_interpolation_with_default_timezone_local + with_env_tz 'America/New_York' do + with_active_record_default_timezone :local do + topic = Topic.first + assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getutc}) + end + end + end + + def test_condition_local_time_interpolation_with_default_timezone_utc + with_env_tz 'America/New_York' do + with_active_record_default_timezone :utc do + topic = Topic.first + assert_equal topic, Topic.find(:first, :conditions => ['written_on = ?', topic.written_on.getlocal]) + end + end + end + + def test_hash_condition_local_time_interpolation_with_default_timezone_utc + with_env_tz 'America/New_York' do + with_active_record_default_timezone :utc do + topic = Topic.first + assert_equal topic, Topic.find(:first, :conditions => {:written_on => topic.written_on.getlocal}) + end + end + end + def test_bind_variables assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"]) assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"]) @@ -1087,4 +1123,18 @@ class FinderTest < ActiveRecord::TestCase ActiveRecord::Base.send(:replace_bind_variables, statement, vars) end end + + def with_env_tz(new_tz = 'US/Eastern') + old_tz, ENV['TZ'] = ENV['TZ'], new_tz + yield + ensure + old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') + end + + def with_active_record_default_timezone(zone) + old_zone, ActiveRecord::Base.default_timezone = ActiveRecord::Base.default_timezone, zone + yield + ensure + ActiveRecord::Base.default_timezone = old_zone + end end -- cgit v1.2.3 From 55d1d12c32a1b99f3f07d2346b49a63650ba2e9d Mon Sep 17 00:00:00 2001 From: Matt Ganderup Date: Mon, 1 Jun 2009 10:24:15 -0700 Subject: fallback_string_to_date sets Date._parse comp arg to true, so that strings with two-digit years, e.g. '1/1/09', are interpreted as modern years [#2019 state:resolved] --- activerecord/CHANGELOG | 2 ++ .../connection_adapters/abstract/schema_definitions.rb | 2 +- activerecord/test/cases/date_time_test.rb | 6 ++++++ 3 files changed, 9 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 9adc6b887f..3f350be3e9 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* fallback_string_to_date sets Date._parse comp arg to true, so that strings with two-digit years, e.g. '1/1/09', are interpreted as modern years #2019 [Matt Ganderup] + * quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing] * SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] 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 24c734cddb..f4e8fe6a38 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -201,7 +201,7 @@ module ActiveRecord end def fallback_string_to_date(string) - new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) + new_date(*::Date._parse(string, true).values_at(:year, :mon, :mday)) end def fallback_string_to_time(string) diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index 36e1caa0b6..f7527c0c04 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -34,4 +34,10 @@ class DateTimeTest < ActiveRecord::TestCase topic.bonus_time = '' assert_nil topic.bonus_time end + + def test_two_digit_year + topic = Topic.new + topic.last_read = '1/1/09' + assert_equal Date.new(2009,1,1), topic.last_read + end end -- cgit v1.2.3 From aad5a30bf25d8a3167afd685fc91c99f4f09cc57 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 4 Aug 2009 11:03:57 -0500 Subject: Add simple support for ActiveModel's StateMachine for ActiveRecord --- activerecord/lib/active_record.rb | 1 + activerecord/lib/active_record/state_machine.rb | 24 ++++++++++++++ activerecord/test/cases/state_machine_test.rb | 42 +++++++++++++++++++++++++ activerecord/test/models/traffic_light.rb | 27 ++++++++++++++++ activerecord/test/schema/schema.rb | 7 +++++ 5 files changed, 101 insertions(+) create mode 100644 activerecord/lib/active_record/state_machine.rb create mode 100644 activerecord/test/cases/state_machine_test.rb create mode 100644 activerecord/test/models/traffic_light.rb (limited to 'activerecord') diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 5e9ce0600a..68b0251982 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -65,6 +65,7 @@ module ActiveRecord autoload :SchemaDumper, 'active_record/schema_dumper' autoload :Serialization, 'active_record/serialization' autoload :SessionStore, 'active_record/session_store' + autoload :StateMachine, 'active_record/state_machine' autoload :TestCase, 'active_record/test_case' autoload :Timestamp, 'active_record/timestamp' autoload :Transactions, 'active_record/transactions' diff --git a/activerecord/lib/active_record/state_machine.rb b/activerecord/lib/active_record/state_machine.rb new file mode 100644 index 0000000000..aebd03344a --- /dev/null +++ b/activerecord/lib/active_record/state_machine.rb @@ -0,0 +1,24 @@ +module ActiveRecord + module StateMachine #:nodoc: + extend ActiveSupport::Concern + include ActiveModel::StateMachine + + included do + before_validation :set_initial_state + validates_presence_of :state + end + + protected + def write_state(state_machine, state) + update_attributes! :state => state.to_s + end + + def read_state(state_machine) + self.state.to_sym + end + + def set_initial_state + self.state ||= self.class.state_machine.initial_state.to_s + end + end +end diff --git a/activerecord/test/cases/state_machine_test.rb b/activerecord/test/cases/state_machine_test.rb new file mode 100644 index 0000000000..5d13668bab --- /dev/null +++ b/activerecord/test/cases/state_machine_test.rb @@ -0,0 +1,42 @@ +require 'cases/helper' +require 'models/traffic_light' + +class StateMachineTest < ActiveRecord::TestCase + def setup + @light = TrafficLight.create! + end + + test "states initial state" do + assert @light.off? + assert_equal :off, @light.current_state + end + + test "transition to a valid state" do + @light.reset + assert @light.red? + assert_equal :red, @light.current_state + + @light.green_on + assert @light.green? + assert_equal :green, @light.current_state + end + + test "transition does not persist state" do + @light.reset + assert_equal :red, @light.current_state + @light.reload + assert_equal "off", @light.state + end + + test "transition does persists state" do + @light.reset! + assert_equal :red, @light.current_state + @light.reload + assert_equal "red", @light.state + end + + test "transition to an invalid state" do + assert_raise(ActiveModel::StateMachine::InvalidTransition) { @light.yellow_on } + assert_equal :off, @light.current_state + end +end diff --git a/activerecord/test/models/traffic_light.rb b/activerecord/test/models/traffic_light.rb new file mode 100644 index 0000000000..f8cfddbef9 --- /dev/null +++ b/activerecord/test/models/traffic_light.rb @@ -0,0 +1,27 @@ +class TrafficLight < ActiveRecord::Base + include ActiveRecord::StateMachine + + state_machine do + state :off + + state :red + state :green + state :yellow + + event :red_on do + transitions :to => :red, :from => [:yellow] + end + + event :green_on do + transitions :to => :green, :from => [:red] + end + + event :yellow_on do + transitions :to => :yellow, :from => [:green] + end + + event :reset do + transitions :to => :red, :from => [:off] + end + end +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 2b7d3856b7..1e47cdbaf6 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -448,6 +448,13 @@ ActiveRecord::Schema.define do t.integer :pet_id, :integer end + create_table :traffic_lights, :force => true do |t| + t.string :location + t.string :state + t.datetime :created_at + t.datetime :updated_at + end + create_table :treasures, :force => true do |t| t.column :name, :string t.column :looter_id, :integer -- cgit v1.2.3 From c30a0ce3c8f88baebd369180a6e221706e2b5cbf Mon Sep 17 00:00:00 2001 From: Paul Gillard Date: Tue, 4 Aug 2009 16:19:19 -0500 Subject: Modified ActiveRecord::AttributeMethods to allow classes to specify attribute method prefixes and/or suffixes. Previously only suffixes were allowed. Signed-off-by: Joshua Peek --- .../lib/active_record/attribute_methods.rb | 154 ++++++++++++++++----- .../lib/active_record/attribute_methods/read.rb | 2 +- .../attribute_methods/time_zone_conversion.rb | 4 +- .../lib/active_record/attribute_methods/write.rb | 2 +- activerecord/test/cases/attribute_methods_test.rb | 94 +++++++++++-- 5 files changed, 200 insertions(+), 56 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 211b77f514..89a92cd7f7 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -4,10 +4,62 @@ module ActiveRecord module AttributeMethods #:nodoc: extend ActiveSupport::Concern + class AttributeMethodMatcher + attr_reader :prefix, :suffix + + AttributeMethodMatch = Struct.new(:prefix, :base, :suffix) + + def initialize(options = {}) + options.symbolize_keys! + @prefix, @suffix = options[:prefix] || '', options[:suffix] || '' + @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/ + end + + def match(method_name) + if matchdata = @regex.match(method_name) + AttributeMethodMatch.new(matchdata[1], matchdata[2], matchdata[3]) + else + nil + end + end + end + # Declare and check for suffixed attribute methods. module ClassMethods + # Declares a method available for all attributes with the given prefix. + # Uses +method_missing+ and respond_to? to rewrite the method. + # + # #{prefix}#{attr}(*args, &block) + # + # to + # + # #{prefix}attribute(#{attr}, *args, &block) + # + # An #{prefix}attribute instance method must exist and accept at least + # the +attr+ argument. + # + # For example: + # + # class Person < ActiveRecord::Base + # attribute_method_prefix 'clear_' + # + # private + # def clear_attribute(attr) + # ... + # end + # end + # + # person = Person.find(1) + # person.name # => 'Gem' + # person.clear_name + # person.name # => '' + def attribute_method_prefix(*prefixes) + attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }) + undefine_attribute_methods + end + # Declares a method available for all attributes with the given suffix. - # Uses +method_missing+ and respond_to? to rewrite the method + # Uses +method_missing+ and respond_to? to rewrite the method. # # #{attr}#{suffix}(*args, &block) # @@ -21,24 +73,59 @@ module ActiveRecord # For example: # # class Person < ActiveRecord::Base - # attribute_method_suffix '_changed?' + # attribute_method_suffix '_short?' # # private - # def attribute_changed?(attr) + # def attribute_short?(attr) # ... # end # end # # person = Person.find(1) - # person.name_changed? # => false - # person.name = 'Hubert' - # person.name_changed? # => true + # person.name # => 'Gem' + # person.name_short? # => true def attribute_method_suffix(*suffixes) - attribute_method_suffixes.concat(suffixes) - rebuild_attribute_method_regexp + attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }) undefine_attribute_methods end + # Declares a method available for all attributes with the given prefix + # and suffix. Uses +method_missing+ and respond_to? to rewrite + # the method. + # + # #{prefix}#{attr}#{suffix}(*args, &block) + # + # to + # + # #{prefix}attribute#{suffix}(#{attr}, *args, &block) + # + # An #{prefix}attribute#{suffix} instance method must exist and + # accept at least the +attr+ argument. + # + # For example: + # + # class Person < ActiveRecord::Base + # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' + # + # private + # def reset_attribute_to_default!(attr) + # ... + # end + # end + # + # person = Person.find(1) + # person.name # => 'Gem' + # person.reset_name_to_default! + # person.name # => 'Gemma' + def attribute_method_affix(*affixes) + attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }) + undefine_attribute_methods + end + + def matching_attribute_methods(method_name) + attribute_method_matchers.collect { |method| method.match(method_name) }.compact + end + # Defines an "attribute" method (like +inheritance_column+ or # +table_name+). A new (class) method will be created with the # given name. If a value is specified, the new method will @@ -69,12 +156,6 @@ module ActiveRecord end end - # Returns MatchData if method_name is an attribute method. - def match_attribute_method?(method_name) - rebuild_attribute_method_regexp unless defined?(@@attribute_method_regexp) && @@attribute_method_regexp - @@attribute_method_regexp.match(method_name) - end - def generated_methods #:nodoc: @generated_methods ||= begin mod = Module.new @@ -88,14 +169,15 @@ module ActiveRecord def define_attribute_methods return unless generated_methods.instance_methods.empty? columns_hash.keys.each do |name| - attribute_method_suffixes.each do |suffix| - method_name = "#{name}#{suffix}" + attribute_method_matchers.each do |method| + method_name = "#{method.prefix}#{name}#{method.suffix}" unless instance_method_already_implemented?(method_name) - generate_method = "define_attribute_method#{suffix}" + generate_method = "define_method_#{method.prefix}attribute#{method.suffix}" + if respond_to?(generate_method) send(generate_method, name) else - generated_methods.module_eval("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", __FILE__, __LINE__) + generated_methods.module_eval("def #{method_name}(*args); send(:#{method.prefix}attribute#{method.suffix}, '#{name}', *args); end", __FILE__, __LINE__) end end end @@ -120,17 +202,20 @@ module ActiveRecord end private - # Suffixes a, ?, c become regexp /(a|\?|c)$/ - def rebuild_attribute_method_regexp - suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) } - @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze - end - - def attribute_method_suffixes - @@attribute_method_suffixes ||= [] + # Default to *=, *? and *_before_type_cast + def attribute_method_matchers + @@attribute_method_matchers ||= [] end end + # Returns a struct representing the matching attribute method. + # The struct's attributes are prefix, base and suffix. + def match_attribute_method?(method_name) + self.class.matching_attribute_methods(method_name).find do |match| + match.base == 'id' || @attributes.include?(match.base) + end + end + # Allows access to the object attributes, which are held in the @attributes hash, as though they # were first-class methods. So a Person class with a name attribute can use Person#name and # Person#name= and never directly use the attributes hash -- except for multiple assigns with @@ -152,12 +237,9 @@ module ActiveRecord end end - if md = self.class.match_attribute_method?(method_name) - attribute_name, method_type = md.pre_match, md.to_s - if attribute_name == 'id' || @attributes.include?(attribute_name) - guard_private_attribute_method!(method_name, args) - return __send__("attribute#{method_type}", attribute_name, *args, &block) - end + if match = match_attribute_method?(method_name) + guard_private_attribute_method!(method_name, args) + return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block) end super end @@ -171,7 +253,7 @@ module ActiveRecord if super return true elsif !include_private_methods && super(method, true) - # If we're here than we haven't found among non-private methods + # If we're here then we haven't found among non-private methods # but found among all methods. Which means that given method is private. return false elsif self.class.generated_methods.instance_methods.empty? @@ -179,10 +261,8 @@ module ActiveRecord if self.class.generated_methods.instance_methods.include?(method_name) return true end - end - - if md = self.class.match_attribute_method?(method_name) - return true if md.pre_match == 'id' || @attributes.include?(md.pre_match) + elsif match_attribute_method?(method_name) + return true end super end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 6c0bf072e9..90acb769a9 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -36,7 +36,7 @@ module ActiveRecord end protected - def define_attribute_method(attr_name) + def define_method_attribute(attr_name) if self.serialized_attributes[attr_name] define_read_method_for_serialized_attribute(attr_name) else diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 4438c7543e..b9cfe59971 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -15,7 +15,7 @@ module ActiveRecord protected # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. # This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone. - def define_attribute_method(attr_name) + def define_method_attribute(attr_name) if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body = <<-EOV def #{attr_name}(reload = false) @@ -33,7 +33,7 @@ module ActiveRecord # Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled. # This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone. - def define_attribute_method=(attr_name) + def define_method_attribute=(attr_name) if create_time_zone_conversion_attribute?(attr_name, columns_hash[attr_name]) method_body = <<-EOV def #{attr_name}=(time) diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index c75745c00d..79118855cf 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -9,7 +9,7 @@ module ActiveRecord module ClassMethods protected - def define_attribute_method=(attr_name) + def define_method_attribute=(attr_name) generated_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 168b617fbc..a5f4a67200 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -4,40 +4,90 @@ require 'models/minimalistic' class AttributeMethodsTest < ActiveRecord::TestCase fixtures :topics + def setup - @old_suffixes = ActiveRecord::Base.send(:attribute_method_suffixes).dup + @old_matchers = ActiveRecord::Base.send(:attribute_method_matchers).dup @target = Class.new(ActiveRecord::Base) @target.table_name = 'topics' end def teardown - ActiveRecord::Base.send(:attribute_method_suffixes).clear - ActiveRecord::Base.attribute_method_suffix *@old_suffixes + ActiveRecord::Base.send(:attribute_method_matchers).clear + ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end - def test_match_attribute_method_query_returns_match_data - assert_not_nil md = @target.match_attribute_method?('title=') - assert_equal 'title', md.pre_match - assert_equal ['='], md.captures - - %w(_hello_world ist! _maybe?).each do |suffix| + def test_match_attribute_method_query_returns_default_match_data + topic = @target.new(:title => 'Budget') + assert_not_nil match = topic.match_attribute_method?('title=') + assert_equal '', match.prefix + assert_equal 'title', match.base + assert_equal '=', match.suffix + end + + def test_match_attribute_method_query_returns_match_data_for_prefixes + topic = @target.new(:title => 'Budget') + %w(default_ title_).each do |prefix| + @target.class_eval "def #{prefix}attribute(*args) args end" + @target.attribute_method_prefix prefix + + assert_not_nil match = topic.match_attribute_method?("#{prefix}title") + assert_equal prefix, match.prefix + assert_equal 'title', match.base + assert_equal '', match.suffix + end + end + + def test_match_attribute_method_query_returns_match_data_for_suffixes + topic = @target.new(:title => 'Budget') + %w(_default _title_default it! _candidate= _maybe?).each do |suffix| @target.class_eval "def attribute#{suffix}(*args) args end" @target.attribute_method_suffix suffix - assert_not_nil md = @target.match_attribute_method?("title#{suffix}") - assert_equal 'title', md.pre_match - assert_equal [suffix], md.captures + assert_not_nil match = topic.match_attribute_method?("title#{suffix}") + assert_equal '', match.prefix + assert_equal 'title', match.base + assert_equal suffix, match.suffix end end - - def test_declared_attribute_method_affects_respond_to_and_method_missing + + def test_match_attribute_method_query_returns_match_data_for_affixes + topic = @target.new(:title => 'Budget') + [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix| + @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end" + @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix }) + + assert_not_nil match = topic.match_attribute_method?("#{prefix}title#{suffix}") + assert_equal prefix, match.prefix + assert_equal 'title', match.base + assert_equal suffix, match.suffix + end + end + + def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing topic = @target.new(:title => 'Budget') assert topic.respond_to?('title') assert_equal 'Budget', topic.title assert !topic.respond_to?('title_hello_world') assert_raise(NoMethodError) { topic.title_hello_world } + end - %w(_hello_world _it! _candidate= able?).each do |suffix| + def test_declared_prefixed_attribute_method_affects_respond_to_and_method_missing + topic = @target.new(:title => 'Budget') + %w(default_ title_).each do |prefix| + @target.class_eval "def #{prefix}attribute(*args) args end" + @target.attribute_method_prefix prefix + + meth = "#{prefix}title" + assert topic.respond_to?(meth) + assert_equal ['title'], topic.send(meth) + assert_equal ['title', 'a'], topic.send(meth, 'a') + assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + end + end + + def test_declared_suffixed_attribute_method_affects_respond_to_and_method_missing + topic = @target.new(:title => 'Budget') + %w(_default _title_default _it! _candidate= able?).each do |suffix| @target.class_eval "def attribute#{suffix}(*args) args end" @target.attribute_method_suffix suffix @@ -49,6 +99,20 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_declared_affixed_attribute_method_affects_respond_to_and_method_missing + topic = @target.new(:title => 'Budget') + [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix| + @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end" + @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix }) + + meth = "#{prefix}title#{suffix}" + assert topic.respond_to?(meth) + assert_equal ['title'], topic.send(meth) + assert_equal ['title', 'a'], topic.send(meth, 'a') + assert_equal ['title', 1, 2, 3], topic.send(meth, 1, 2, 3) + end + end + def test_should_unserialize_attributes_for_frozen_records myobj = {:value1 => :value2} topic = Topic.create("content" => myobj) -- cgit v1.2.3 From bada18dc36e3875dea1814ffaab1e8d1ac24b521 Mon Sep 17 00:00:00 2001 From: Paul Gillard Date: Tue, 4 Aug 2009 16:23:08 -0500 Subject: Added reset_attribute! method to ActiveRecord::AttributeMethods::Dirty which will reset an attribute to its original value should it have changed. Signed-off-by: Joshua Peek --- .../lib/active_record/attribute_methods/dirty.rb | 41 +++++++++++++++------- activerecord/test/cases/dirty_test.rb | 10 ++++++ 2 files changed, 39 insertions(+), 12 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index b88c84938d..9ec1fbeee1 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -3,17 +3,17 @@ module ActiveRecord # Track unsaved attribute changes. # # A newly instantiated object is unchanged: - # person = Person.find_by_name('uncle bob') + # person = Person.find_by_name('Uncle Bob') # person.changed? # => false # # Change the name: # person.name = 'Bob' # person.changed? # => true # person.name_changed? # => true - # person.name_was # => 'uncle bob' - # person.name_change # => ['uncle bob', 'Bob'] + # person.name_was # => 'Uncle Bob' + # person.name_change # => ['Uncle Bob', 'Bob'] # person.name = 'Bill' - # person.name_change # => ['uncle bob', 'Bill'] + # person.name_change # => ['Uncle Bob', 'Bill'] # # Save the changes: # person.save @@ -26,21 +26,33 @@ module ActiveRecord # person.name_change # => nil # # Which attributes have changed? - # person.name = 'bob' + # person.name = 'Bob' # person.changed # => ['name'] - # person.changes # => { 'name' => ['Bill', 'bob'] } + # person.changes # => { 'name' => ['Bill', 'Bob'] } + # + # Resetting an attribute returns it to its original state: + # person.reset_name! # => 'Bill' + # person.changed? # => false + # person.name_changed? # => false + # person.name # => 'Bill' # # Before modifying an attribute in-place: # person.name_will_change! - # person.name << 'by' - # person.name_change # => ['uncle bob', 'uncle bobby'] + # person.name << 'y' + # person.name_change # => ['Bill', 'Billy'] module Dirty extend ActiveSupport::Concern - DIRTY_SUFFIXES = ['_changed?', '_change', '_will_change!', '_was'] + DIRTY_AFFIXES = [ + { :suffix => '_changed?' }, + { :suffix => '_change' }, + { :suffix => '_will_change!' }, + { :suffix => '_was' }, + { :prefix => 'reset_', :suffix => '!' } + ] included do - attribute_method_suffix *DIRTY_SUFFIXES + attribute_method_affix *DIRTY_AFFIXES alias_method_chain :save, :dirty alias_method_chain :save!, :dirty @@ -118,6 +130,11 @@ module ActiveRecord attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) end + # Handle reset_*! for +method_missing+. + def reset_attribute!(attr) + self[attr] = changed_attributes[attr] if attribute_changed?(attr) + end + # Handle *_will_change! for +method_missing+. def attribute_will_change!(attr) changed_attributes[attr] = clone_attribute_value(:read_attribute, attr) @@ -175,9 +192,9 @@ module ActiveRecord def alias_attribute_with_dirty(new_name, old_name) alias_attribute_without_dirty(new_name, old_name) - DIRTY_SUFFIXES.each do |suffix| + DIRTY_AFFIXES.each do |affixes| module_eval <<-STR, __FILE__, __LINE__+1 - def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end + def #{affixes[:prefix]}#{new_name}#{affixes[:suffix]}; self.#{affixes[:prefix]}#{old_name}#{affixes[:suffix]}; end # def reset_subject!; self.reset_title!; end STR end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index ac95bac4ad..1441421a80 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -62,6 +62,16 @@ class DirtyTest < ActiveRecord::TestCase assert_equal parrot.name_change, parrot.title_change end + def test_reset_attribute! + pirate = Pirate.create!(:catchphrase => 'Yar!') + pirate.catchphrase = 'Ahoy!' + + pirate.reset_catchphrase! + assert_equal "Yar!", pirate.catchphrase + assert_equal Hash.new, pirate.changes + assert !pirate.catchphrase_changed? + end + def test_nullable_number_not_marked_as_changed_if_new_value_is_blank pirate = Pirate.new -- cgit v1.2.3 From 64eecdd131c93d7d6c8ef9c6a7ae6b9d76c72a8b Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 4 Aug 2009 16:28:44 -0500 Subject: whitespace --- activerecord/lib/active_record/attribute_methods.rb | 10 +++++----- 1 file changed, 5 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 89a92cd7f7..be275f5cb6 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -6,7 +6,7 @@ module ActiveRecord class AttributeMethodMatcher attr_reader :prefix, :suffix - + AttributeMethodMatch = Struct.new(:prefix, :base, :suffix) def initialize(options = {}) @@ -121,11 +121,11 @@ module ActiveRecord attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }) undefine_attribute_methods end - + def matching_attribute_methods(method_name) attribute_method_matchers.collect { |method| method.match(method_name) }.compact end - + # Defines an "attribute" method (like +inheritance_column+ or # +table_name+). A new (class) method will be created with the # given name. If a value is specified, the new method will @@ -173,7 +173,7 @@ module ActiveRecord method_name = "#{method.prefix}#{name}#{method.suffix}" unless instance_method_already_implemented?(method_name) generate_method = "define_method_#{method.prefix}attribute#{method.suffix}" - + if respond_to?(generate_method) send(generate_method, name) else @@ -215,7 +215,7 @@ module ActiveRecord match.base == 'id' || @attributes.include?(match.base) end end - + # Allows access to the object attributes, which are held in the @attributes hash, as though they # were first-class methods. So a Person class with a name attribute can use Person#name and # Person#name= and never directly use the attributes hash -- except for multiple assigns with -- cgit v1.2.3 From f8d3c72c39ad209abca7f3613f91fb3a03805261 Mon Sep 17 00:00:00 2001 From: Joshua Peek Date: Tue, 4 Aug 2009 23:36:05 -0500 Subject: Extract generic attribute method generation to AMo --- .../lib/active_record/attribute_methods.rb | 251 +-------------------- .../lib/active_record/attribute_methods/read.rb | 4 +- .../attribute_methods/time_zone_conversion.rb | 4 +- .../lib/active_record/attribute_methods/write.rb | 2 +- activerecord/lib/active_record/base.rb | 5 - activerecord/test/cases/attribute_methods_test.rb | 47 ---- activerecord/test/cases/finder_test.rb | 2 +- 7 files changed, 16 insertions(+), 299 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index be275f5cb6..ab7ad34b9e 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -3,191 +3,13 @@ require 'active_support/core_ext/enumerable' module ActiveRecord module AttributeMethods #:nodoc: extend ActiveSupport::Concern + include ActiveModel::AttributeMethods - class AttributeMethodMatcher - attr_reader :prefix, :suffix - - AttributeMethodMatch = Struct.new(:prefix, :base, :suffix) - - def initialize(options = {}) - options.symbolize_keys! - @prefix, @suffix = options[:prefix] || '', options[:suffix] || '' - @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/ - end - - def match(method_name) - if matchdata = @regex.match(method_name) - AttributeMethodMatch.new(matchdata[1], matchdata[2], matchdata[3]) - else - nil - end - end - end - - # Declare and check for suffixed attribute methods. module ClassMethods - # Declares a method available for all attributes with the given prefix. - # Uses +method_missing+ and respond_to? to rewrite the method. - # - # #{prefix}#{attr}(*args, &block) - # - # to - # - # #{prefix}attribute(#{attr}, *args, &block) - # - # An #{prefix}attribute instance method must exist and accept at least - # the +attr+ argument. - # - # For example: - # - # class Person < ActiveRecord::Base - # attribute_method_prefix 'clear_' - # - # private - # def clear_attribute(attr) - # ... - # end - # end - # - # person = Person.find(1) - # person.name # => 'Gem' - # person.clear_name - # person.name # => '' - def attribute_method_prefix(*prefixes) - attribute_method_matchers.concat(prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }) - undefine_attribute_methods - end - - # Declares a method available for all attributes with the given suffix. - # Uses +method_missing+ and respond_to? to rewrite the method. - # - # #{attr}#{suffix}(*args, &block) - # - # to - # - # attribute#{suffix}(#{attr}, *args, &block) - # - # An attribute#{suffix} instance method must exist and accept at least - # the +attr+ argument. - # - # For example: - # - # class Person < ActiveRecord::Base - # attribute_method_suffix '_short?' - # - # private - # def attribute_short?(attr) - # ... - # end - # end - # - # person = Person.find(1) - # person.name # => 'Gem' - # person.name_short? # => true - def attribute_method_suffix(*suffixes) - attribute_method_matchers.concat(suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }) - undefine_attribute_methods - end - - # Declares a method available for all attributes with the given prefix - # and suffix. Uses +method_missing+ and respond_to? to rewrite - # the method. - # - # #{prefix}#{attr}#{suffix}(*args, &block) - # - # to - # - # #{prefix}attribute#{suffix}(#{attr}, *args, &block) - # - # An #{prefix}attribute#{suffix} instance method must exist and - # accept at least the +attr+ argument. - # - # For example: - # - # class Person < ActiveRecord::Base - # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!' - # - # private - # def reset_attribute_to_default!(attr) - # ... - # end - # end - # - # person = Person.find(1) - # person.name # => 'Gem' - # person.reset_name_to_default! - # person.name # => 'Gemma' - def attribute_method_affix(*affixes) - attribute_method_matchers.concat(affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }) - undefine_attribute_methods - end - - def matching_attribute_methods(method_name) - attribute_method_matchers.collect { |method| method.match(method_name) }.compact - end - - # Defines an "attribute" method (like +inheritance_column+ or - # +table_name+). A new (class) method will be created with the - # given name. If a value is specified, the new method will - # return that value (as a string). Otherwise, the given block - # will be used to compute the value of the method. - # - # The original method will be aliased, with the new name being - # prefixed with "original_". This allows the new method to - # access the original value. - # - # Example: - # - # class A < ActiveRecord::Base - # define_attr_method :primary_key, "sysid" - # define_attr_method( :inheritance_column ) do - # original_inheritance_column + "_id" - # end - # end - def define_attr_method(name, value=nil, &block) - sing = metaclass - sing.send :alias_method, "original_#{name}", name - if block_given? - sing.send :define_method, name, &block - else - # use eval instead of a block to work around a memory leak in dev - # mode in fcgi - sing.class_eval "def #{name}; #{value.to_s.inspect}; end" - end - end - - def generated_methods #:nodoc: - @generated_methods ||= begin - mod = Module.new - include mod - mod - end - end - # Generates all the attribute related methods for columns in the database # accessors, mutators and query methods. def define_attribute_methods - return unless generated_methods.instance_methods.empty? - columns_hash.keys.each do |name| - attribute_method_matchers.each do |method| - method_name = "#{method.prefix}#{name}#{method.suffix}" - unless instance_method_already_implemented?(method_name) - generate_method = "define_method_#{method.prefix}attribute#{method.suffix}" - - if respond_to?(generate_method) - send(generate_method, name) - else - generated_methods.module_eval("def #{method_name}(*args); send(:#{method.prefix}attribute#{method.suffix}, '#{name}', *args); end", __FILE__, __LINE__) - end - end - end - end - end - - def undefine_attribute_methods - generated_methods.module_eval do - instance_methods.each { |m| undef_method(m) } - end + super(columns_hash.keys) end # Checks whether the method is defined in the model or any of its subclasses @@ -200,83 +22,30 @@ module ActiveRecord raise DangerousAttributeError, "#{method_name} is defined by ActiveRecord" if @@_defined_activerecord_methods.include?(method_name) @_defined_class_methods.include?(method_name) end - - private - # Default to *=, *? and *_before_type_cast - def attribute_method_matchers - @@attribute_method_matchers ||= [] - end - end - - # Returns a struct representing the matching attribute method. - # The struct's attributes are prefix, base and suffix. - def match_attribute_method?(method_name) - self.class.matching_attribute_methods(method_name).find do |match| - match.base == 'id' || @attributes.include?(match.base) - end end - # Allows access to the object attributes, which are held in the @attributes hash, as though they - # were first-class methods. So a Person class with a name attribute can use Person#name and - # Person#name= and never directly use the attributes hash -- except for multiple assigns with - # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that - # the completed attribute is not +nil+ or 0. - # - # It's also possible to instantiate related objects, so a Client class belonging to the clients - # table with a +master_id+ foreign key can instantiate master through Client#master. def method_missing(method_id, *args, &block) - method_name = method_id.to_s - # 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.instance_methods.empty? + if !self.class.attribute_methods_generated? self.class.define_attribute_methods + method_name = method_id.to_s guard_private_attribute_method!(method_name, args) - if self.class.generated_methods.instance_methods.include?(method_name) + if self.class.generated_attribute_methods.instance_methods.include?(method_name) return self.send(method_id, *args, &block) end end - - if match = match_attribute_method?(method_name) - guard_private_attribute_method!(method_name, args) - return __send__("#{match.prefix}attribute#{match.suffix}", match.base, *args, &block) - end super end - # A Person object with a name attribute can ask person.respond_to?(:name), - # 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_private_methods = false) - method_name = method.to_s - if super - return true - elsif !include_private_methods && super(method, true) - # If we're here then we haven't found among non-private methods - # but found among all methods. Which means that given method is private. - return false - elsif self.class.generated_methods.instance_methods.empty? - self.class.define_attribute_methods - if self.class.generated_methods.instance_methods.include?(method_name) - return true - end - elsif match_attribute_method?(method_name) - return true - end + def respond_to?(*args) + self.class.define_attribute_methods super end - private - # prevent method_missing from calling private methods with #send - def guard_private_attribute_method!(method_name, args) - if self.class.private_method_defined?(method_name) - raise NoMethodError.new("Attempt to call private method", method_name, args) - end - end - - def missing_attribute(attr_name, stack) - raise ActiveRecord::MissingAttributeError, "missing attribute: #{attr_name}", stack + protected + def attribute_method?(attr_name) + attr_name == 'id' || attributes.include?(attr_name) end end end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 90acb769a9..0b7d6d9094 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -51,7 +51,7 @@ module ActiveRecord private # Define read method for serialized attribute. def define_read_method_for_serialized_attribute(attr_name) - generated_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) + generated_attribute_methods.module_eval("def #{attr_name}; unserialize_attribute('#{attr_name}'); end", __FILE__, __LINE__) end # Define an attribute reader method. Cope with nil column. @@ -66,7 +66,7 @@ module ActiveRecord if cache_attribute?(attr_name) access_code = "@attributes_cache['#{attr_name}'] ||= (#{access_code})" end - generated_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__) + generated_attribute_methods.module_eval("def #{symbol}; #{access_code}; end", __FILE__, __LINE__) end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index b9cfe59971..a8e3e28a7a 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -25,7 +25,7 @@ module ActiveRecord @attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time end EOV - generated_methods.module_eval(method_body, __FILE__, __LINE__) + generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__) else super end @@ -44,7 +44,7 @@ module ActiveRecord write_attribute(:#{attr_name}, time) end EOV - generated_methods.module_eval(method_body, __FILE__, __LINE__) + generated_attribute_methods.module_eval(method_body, __FILE__, __LINE__) else super end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 79118855cf..e31acac050 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,7 +10,7 @@ module ActiveRecord module ClassMethods protected def define_method_attribute=(attr_name) - generated_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) + generated_attribute_methods.module_eval("def #{attr_name}=(new_value); write_attribute('#{attr_name}', new_value); end", __FILE__, __LINE__) end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ce93ea8eee..e358564ead 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -148,11 +148,6 @@ module ActiveRecord #:nodoc: class DangerousAttributeError < ActiveRecordError end - # Raised when you've tried to access a column which wasn't loaded by your finder. - # Typically this is because :select has been specified. - class MissingAttributeError < NoMethodError - end - # Raised when unknown attributes are supplied via mass assignment. class UnknownAttributeError < NoMethodError end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index a5f4a67200..ab8768ea3e 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -16,53 +16,6 @@ class AttributeMethodsTest < ActiveRecord::TestCase ActiveRecord::Base.send(:attribute_method_matchers).concat(@old_matchers) end - def test_match_attribute_method_query_returns_default_match_data - topic = @target.new(:title => 'Budget') - assert_not_nil match = topic.match_attribute_method?('title=') - assert_equal '', match.prefix - assert_equal 'title', match.base - assert_equal '=', match.suffix - end - - def test_match_attribute_method_query_returns_match_data_for_prefixes - topic = @target.new(:title => 'Budget') - %w(default_ title_).each do |prefix| - @target.class_eval "def #{prefix}attribute(*args) args end" - @target.attribute_method_prefix prefix - - assert_not_nil match = topic.match_attribute_method?("#{prefix}title") - assert_equal prefix, match.prefix - assert_equal 'title', match.base - assert_equal '', match.suffix - end - end - - def test_match_attribute_method_query_returns_match_data_for_suffixes - topic = @target.new(:title => 'Budget') - %w(_default _title_default it! _candidate= _maybe?).each do |suffix| - @target.class_eval "def attribute#{suffix}(*args) args end" - @target.attribute_method_suffix suffix - - assert_not_nil match = topic.match_attribute_method?("title#{suffix}") - assert_equal '', match.prefix - assert_equal 'title', match.base - assert_equal suffix, match.suffix - end - end - - def test_match_attribute_method_query_returns_match_data_for_affixes - topic = @target.new(:title => 'Budget') - [['mark_', '_for_update'], ['reset_', '!'], ['default_', '_value?']].each do |prefix, suffix| - @target.class_eval "def #{prefix}attribute#{suffix}(*args) args end" - @target.attribute_method_affix({ :prefix => prefix, :suffix => suffix }) - - assert_not_nil match = topic.match_attribute_method?("#{prefix}title#{suffix}") - assert_equal prefix, match.prefix - assert_equal 'title', match.base - assert_equal suffix, match.suffix - end - end - def test_undeclared_attribute_method_does_not_affect_respond_to_and_method_missing topic = @target.new(:title => 'Budget') assert topic.respond_to?('title') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 55ef0d45eb..893fc34c36 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -251,7 +251,7 @@ class FinderTest < ActiveRecord::TestCase def test_find_only_some_columns topic = Topic.find(1, :select => "author_name") - assert_raise(ActiveRecord::MissingAttributeError) {topic.title} + assert_raise(ActiveModel::MissingAttributeError) {topic.title} assert_equal "David", topic.author_name assert !topic.attribute_present?("title") #assert !topic.respond_to?("title") -- cgit v1.2.3 From bfafe8c4055bcb8dcf7440015d95a32c9773f40b Mon Sep 17 00:00:00 2001 From: Geoff Buesing Date: Wed, 5 Aug 2009 08:19:21 -0500 Subject: Revert "fallback_string_to_date sets Date._parse comp arg to true, so that strings with two-digit years, e.g. '1/1/09', are interpreted as modern years" [#2019 state:wontfix] This reverts commit 55d1d12c32a1b99f3f07d2346b49a63650ba2e9d. --- activerecord/CHANGELOG | 2 -- .../connection_adapters/abstract/schema_definitions.rb | 2 +- activerecord/test/cases/date_time_test.rb | 6 ------ 3 files changed, 1 insertion(+), 9 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 3f350be3e9..9adc6b887f 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,7 +1,5 @@ *Edge* -* fallback_string_to_date sets Date._parse comp arg to true, so that strings with two-digit years, e.g. '1/1/09', are interpreted as modern years #2019 [Matt Ganderup] - * quoted_date converts time-like objects to ActiveRecord::Base.default_timezone before serialization. This allows you to use Time.now in find conditions and have it correctly be serialized as the current time in UTC when default_timezone == :utc. #2946 [Geoff Buesing] * SQLite: drop support for 'dbfile' option in favor of 'database.' #2363 [Paul Hinze, Jeremy Kemper] 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 f4e8fe6a38..24c734cddb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -201,7 +201,7 @@ module ActiveRecord end def fallback_string_to_date(string) - new_date(*::Date._parse(string, true).values_at(:year, :mon, :mday)) + new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday)) end def fallback_string_to_time(string) diff --git a/activerecord/test/cases/date_time_test.rb b/activerecord/test/cases/date_time_test.rb index f7527c0c04..36e1caa0b6 100644 --- a/activerecord/test/cases/date_time_test.rb +++ b/activerecord/test/cases/date_time_test.rb @@ -34,10 +34,4 @@ class DateTimeTest < ActiveRecord::TestCase topic.bonus_time = '' assert_nil topic.bonus_time end - - def test_two_digit_year - topic = Topic.new - topic.last_read = '1/1/09' - assert_equal Date.new(2009,1,1), topic.last_read - end end -- cgit v1.2.3 From 230d43fbf5dee569ea031c8c394ba9ce70804cae Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 6 Aug 2009 08:34:23 +0900 Subject: Ruby 1.9.2 compat: Array#* uses to_str instead of to_s to join values since Ruby 1.9.2 [#2959 state:committed] Signed-off-by: Jeremy Kemper --- .../active_record/connection_adapters/abstract/schema_definitions.rb | 3 +-- 1 file changed, 1 insertion(+), 2 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 24c734cddb..f346e3ebc8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -277,7 +277,6 @@ module ActiveRecord add_column_options!(column_sql, column_options) unless type.to_sym == :primary_key column_sql end - alias to_s :to_sql private @@ -508,7 +507,7 @@ module ActiveRecord # concatenated together. This string can then be prepended and appended to # to generate the final SQL to create the table. def to_sql - @columns * ', ' + @columns.map(&:to_sql) * ', ' end private -- cgit v1.2.3 From 7e560d0b0d9414d5712f068f6eb5f8bde1980558 Mon Sep 17 00:00:00 2001 From: Rob Christie Date: Wed, 14 Jan 2009 01:14:02 -0500 Subject: Fixed adapter test cases that were failing in oracle because the asserts were looking for the presence of offset and limit which are not available in oracle. Changed the tests to check that the sql injection is not present in the output so that the tests are database adapter agnostic. --- activerecord/test/cases/adapter_test.rb | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 80530194ff..65c5fc2fe9 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -112,23 +112,14 @@ class AdapterTest < ActiveRecord::TestCase def test_add_limit_offset_should_sanitize_sql_injection_for_limit_without_comas sql_inject = "1 select * from schema" - assert_equal " LIMIT 1", @connection.add_limit_offset!("", :limit=>sql_inject) - if current_adapter?(:MysqlAdapter) - assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - else - assert_equal " LIMIT 1 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - end + assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject) + assert_no_match /schema/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) end def test_add_limit_offset_should_sanitize_sql_injection_for_limit_with_comas sql_inject = "1, 7 procedure help()" - if current_adapter?(:MysqlAdapter) - assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject) - assert_equal " LIMIT 7, 1", @connection.add_limit_offset!("", :limit=> '1 ; DROP TABLE USERS', :offset=>7) - else - assert_equal " LIMIT 1,7", @connection.add_limit_offset!("", :limit=>sql_inject) - assert_equal " LIMIT 1,7 OFFSET 7", @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) - end + assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject) + assert_no_match /procedure/, @connection.add_limit_offset!("", :limit=>sql_inject, :offset=>7) end def test_uniqueness_violations_are_translated_to_specific_exception -- cgit v1.2.3 From 04fea8a07ba94d381b03fae4a657077a7b966229 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 15 Mar 2009 14:40:07 +0200 Subject: modified native_oracle/connection.rb to run it with oracle_enhanced adapter --- .../test/connections/native_oracle/connection.rb | 25 ++++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 0954b27f87..1be4e23b84 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,25 +1,36 @@ +# gem "rsim-activerecord-oracle_enhanced-adapter" +# gem "activerecord-oracle_enhanced-adapter" +# uses local copy of oracle_enhanced adapter +$:.unshift("../../oracle-enhanced/lib") +require 'active_record/connection_adapters/oracle_enhanced_adapter' + print "Using Oracle\n" require_dependency 'models/course' require 'logger' -ActiveRecord::Base.logger = Logger.new STDOUT -ActiveRecord::Base.logger.level = Logger::WARN +# ActiveRecord::Base.logger = Logger.new STDOUT +# ActiveRecord::Base.logger.level = Logger::WARN +ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB'] || 'activerecord_unittest' +db = ENV['ARUNIT_DB'] || 'XE' ActiveRecord::Base.configurations = { 'arunit' => { - :adapter => 'oracle', + :adapter => 'oracle_enhanced', + :database => db, + :host => "arunit", # used just by JRuby to construct JDBC connect string :username => 'arunit', :password => 'arunit', - :database => db, + :emulate_oracle_adapter => true }, 'arunit2' => { - :adapter => 'oracle', + :adapter => 'oracle_enhanced', + :database => db, + :host => "arunit", # used just by JRuby to construct JDBC connect string :username => 'arunit2', :password => 'arunit2', - :database => db + :emulate_oracle_adapter => true } } -- cgit v1.2.3 From 963570b51ce4d95b046a441dd1c413dc6fcec8b4 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 22 Mar 2009 23:50:05 +0200 Subject: added additional objects necessary for OracleAdapter specific tests if OracleAdapter is used then use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in Oracle SELECT WHERE clause which causes many unit test failures --- activerecord/test/schema/oracle_specific_schema.rb | 27 ++++++++++++++++++++++ activerecord/test/schema/schema.rb | 24 ++++++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/schema/oracle_specific_schema.rb b/activerecord/test/schema/oracle_specific_schema.rb index 2d87f34625..3314687445 100644 --- a/activerecord/test/schema/oracle_specific_schema.rb +++ b/activerecord/test/schema/oracle_specific_schema.rb @@ -2,6 +2,10 @@ ActiveRecord::Schema.define do execute "drop table test_oracle_defaults" rescue nil execute "drop sequence test_oracle_defaults_seq" rescue nil + execute "drop sequence companies_nonstd_seq" rescue nil + execute "drop synonym subjects" rescue nil + execute "drop table defaults" rescue nil + execute "drop sequence defaults_seq" rescue nil execute <<-SQL create table test_oracle_defaults ( @@ -16,4 +20,27 @@ create table test_oracle_defaults ( create sequence test_oracle_defaults_seq minvalue 10000 SQL + execute "create sequence companies_nonstd_seq minvalue 10000" + + execute "create synonym subjects for topics" + + execute <<-SQL + CREATE TABLE defaults ( + id integer not null, + modified_date date default sysdate, + modified_date_function date default sysdate, + fixed_date date default to_date('2004-01-01', 'YYYY-MM-DD'), + modified_time date default sysdate, + modified_time_function date default sysdate, + fixed_time date default TO_DATE('2004-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS'), + char1 varchar2(1) default 'Y', + char2 varchar2(50) default 'a varchar field', + char3 clob default 'a text field', + positive_integer integer default 1, + negative_integer integer default -1, + decimal_number number(3,2) default 2.78 + ) + SQL + execute "create sequence defaults_seq minvalue 10000" + end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 1e47cdbaf6..5752d6fa40 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -104,7 +104,13 @@ ActiveRecord::Schema.define do create_table :comments, :force => true do |t| t.integer :post_id, :null => false - t.text :body, :null => false + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :body, :null => false, :limit => 4000 + else + t.text :body, :null => false + end t.string :type end @@ -350,7 +356,13 @@ ActiveRecord::Schema.define do create_table :posts, :force => true do |t| t.integer :author_id t.string :title, :null => false - t.text :body, :null => false + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :body, :null => false, :limit => 4000 + else + t.text :body, :null => false + end t.string :type t.integer :comments_count, :default => 0 t.integer :taggings_count, :default => 0 @@ -423,7 +435,13 @@ ActiveRecord::Schema.define do t.datetime :written_on t.time :bonus_time t.date :last_read - t.text :content + # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in + # Oracle SELECT WHERE clause which causes many unit test failures + if current_adapter?(:OracleAdapter) + t.string :content, :limit => 4000 + else + t.text :content + end t.boolean :approved, :default => true t.integer :replies_count, :default => 0 t.integer :parent_id -- cgit v1.2.3 From 5666a3ad065469f12e5b3a4de0be823c9ae4ff7d Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 22 Mar 2009 23:57:24 +0200 Subject: added :order option to find :first methods and associations as otherwise Oracle tests were failing Oracle stores '' string as NULL Oracle cannot have identifiers larger than 30 characters added missing fixtures to test setup method --- .../associations/belongs_to_associations_test.rb | 6 +- activerecord/test/cases/associations/eager_test.rb | 7 +- .../has_and_belongs_to_many_associations_test.rb | 6 +- .../associations/has_many_associations_test.rb | 80 ++++++++++++---------- .../test/cases/associations/join_model_test.rb | 10 ++- activerecord/test/models/company.rb | 8 ++- 6 files changed, 69 insertions(+), 48 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index ab6f752243..784c484178 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -293,7 +293,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase def test_new_record_with_foreign_key_but_no_object c = Client.new("firm_id" => 1) - assert_equal Firm.find(:first), c.firm_with_basic_id + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id end def test_forgetting_the_load_when_foreign_key_enters_late @@ -301,7 +302,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_nil c.firm_with_basic_id c.firm_id = 1 - assert_equal Firm.find(:first), c.firm_with_basic_id + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert_equal Firm.find(:first, :order => "id"), c.firm_with_basic_id end def test_field_name_same_as_foreign_key diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 4cf49be668..811ebfbe3f 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -813,7 +813,12 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_include_has_many_using_primary_key expected = Firm.find(1).clients_using_primary_key.sort_by &:name - firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + # Oracle adapter truncates alias to 30 characters + if current_adapter?(:OracleAdapter) + firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies'[0,30]+'.name' + else + firm = Firm.find 1, :include => :clients_using_primary_key, :order => 'clients_using_primary_keys_companies.name' + end assert_no_queries do assert_equal expected, firm.clients_using_primary_key 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 14b96caaae..11a159686e 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 @@ -284,12 +284,14 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_creation_respects_hash_condition - post = categories(:general).post_with_conditions.build(:body => '') + # in Oracle '' is saved as null therefore need to save ' ' in not null column + post = categories(:general).post_with_conditions.build(:body => ' ') assert post.save assert_equal 'Yet Another Testing Title', post.title - another_post = categories(:general).post_with_conditions.create(:body => '') + # in Oracle '' is saved as null therefore need to save ' ' in not null column + another_post = categories(:general).post_with_conditions.create(:body => ' ') assert !another_post.new_record? assert_equal 'Yet Another Testing Title', another_post.title diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 15919e2289..a3d92c3bdb 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -24,28 +24,29 @@ class HasManyAssociationsTest < ActiveRecord::TestCase companies(:first_firm).clients_of_firm.each {|f| } end + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first def test_counting_with_counter_sql - assert_equal 2, Firm.find(:first).clients.count + assert_equal 2, Firm.find(:first, :order => "id").clients.count end def test_counting - assert_equal 2, Firm.find(:first).plain_clients.count + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count end def test_counting_with_empty_hash_conditions - assert_equal 2, Firm.find(:first).plain_clients.count(:conditions => {}) + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {}) end def test_counting_with_single_conditions - assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => ['name=?', "Microsoft"]) + assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => ['name=?', "Microsoft"]) end def test_counting_with_single_hash - assert_equal 1, Firm.find(:first).plain_clients.count(:conditions => {:name => "Microsoft"}) + assert_equal 1, Firm.find(:first, :order => "id").plain_clients.count(:conditions => {:name => "Microsoft"}) end def test_counting_with_column_name_and_hash - assert_equal 2, Firm.find(:first).plain_clients.count(:name) + assert_equal 2, Firm.find(:first, :order => "id").plain_clients.count(:name) end def test_counting_with_association_limit @@ -55,12 +56,12 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_finding - assert_equal 2, Firm.find(:first).clients.length + assert_equal 2, Firm.find(:first, :order => "id").clients.length end def test_find_with_blank_conditions [[], {}, nil, ""].each do |blank| - assert_equal 2, Firm.find(:first).clients.find(:all, :conditions => blank).size + assert_equal 2, Firm.find(:first, :order => "id").clients.find(:all, :conditions => blank).size end end @@ -115,52 +116,53 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_triple_equality - assert !(Array === Firm.find(:first).clients) - assert Firm.find(:first).clients === Array + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + assert !(Array === Firm.find(:first, :order => "id").clients) + assert Firm.find(:first, :order => "id").clients === Array end def test_finding_default_orders - assert_equal "Summit", Firm.find(:first).clients.first.name + assert_equal "Summit", Firm.find(:first, :order => "id").clients.first.name end def test_finding_with_different_class_name_and_order - assert_equal "Microsoft", Firm.find(:first).clients_sorted_desc.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_sorted_desc.first.name end def test_finding_with_foreign_key - assert_equal "Microsoft", Firm.find(:first).clients_of_firm.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_of_firm.first.name end def test_finding_with_condition - assert_equal "Microsoft", Firm.find(:first).clients_like_ms.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms.first.name end def test_finding_with_condition_hash - assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name + assert_equal "Microsoft", Firm.find(:first, :order => "id").clients_like_ms_with_hash_conditions.first.name end def test_finding_using_primary_key - assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name + assert_equal "Summit", Firm.find(:first, :order => "id").clients_using_primary_key.first.name end def test_finding_using_sql - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") first_client = firm.clients_using_sql.first assert_not_nil first_client assert_equal "Microsoft", first_client.name assert_equal 1, firm.clients_using_sql.size - assert_equal 1, Firm.find(:first).clients_using_sql.size + assert_equal 1, Firm.find(:first, :order => "id").clients_using_sql.size end def test_counting_using_sql - assert_equal 1, Firm.find(:first).clients_using_counter_sql.size - assert Firm.find(:first).clients_using_counter_sql.any? - assert_equal 0, Firm.find(:first).clients_using_zero_counter_sql.size - assert !Firm.find(:first).clients_using_zero_counter_sql.any? + assert_equal 1, Firm.find(:first, :order => "id").clients_using_counter_sql.size + assert Firm.find(:first, :order => "id").clients_using_counter_sql.any? + assert_equal 0, Firm.find(:first, :order => "id").clients_using_zero_counter_sql.size + assert !Firm.find(:first, :order => "id").clients_using_zero_counter_sql.any? end def test_counting_non_existant_items_using_sql - assert_equal 0, Firm.find(:first).no_clients_using_counter_sql.size + assert_equal 0, Firm.find(:first, :order => "id").no_clients_using_counter_sql.size end def test_counting_using_finder_sql @@ -183,7 +185,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_ids - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") assert_raise(ActiveRecord::RecordNotFound) { firm.clients.find } @@ -203,7 +205,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_string_ids_when_using_finder_sql - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client = firm.clients_using_finder_sql.find("2") assert_kind_of Client, client @@ -219,7 +221,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") assert_equal 2, firm.clients.find(:all, :conditions => "#{QUOTED_TYPE} = 'Client'").length assert_equal 1, firm.clients.find(:all, :conditions => "name = 'Summit'").length end @@ -264,24 +266,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_find_all_sanitized - firm = Firm.find(:first) + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + firm = Firm.find(:first, :order => "id") summit = firm.clients.find(:all, :conditions => "name = 'Summit'") assert_equal summit, firm.clients.find(:all, :conditions => ["name = ?", "Summit"]) assert_equal summit, firm.clients.find(:all, :conditions => ["name = :name", { :name => "Summit" }]) end def test_find_first - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client2 = Client.find(2) - assert_equal firm.clients.first, firm.clients.find(:first) - assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'") + assert_equal firm.clients.first, firm.clients.find(:first, :order => "id") + assert_equal client2, firm.clients.find(:first, :conditions => "#{QUOTED_TYPE} = 'Client'", :order => "id") end def test_find_first_sanitized - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client2 = Client.find(2) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client']) - assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }]) + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = ?", 'Client'], :order => "id") + assert_equal client2, firm.clients.find(:first, :conditions => ["#{QUOTED_TYPE} = :type", { :type => 'Client' }], :order => "id") end def test_find_in_collection @@ -341,7 +344,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase def test_create_with_bang_on_has_many_raises_when_record_not_saved assert_raise(ActiveRecord::RecordInvalid) do - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.plain_clients.create! end end @@ -731,7 +734,8 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_destroy_dependent_when_deleted_from_association - firm = Firm.find(:first) + # sometimes tests on Oracle fail if ORDER BY is not provided therefore add always :order with :first + firm = Firm.find(:first, :order => "id") assert_equal 2, firm.clients.size client = firm.clients.first @@ -798,7 +802,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_less - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.clients = [companies(:first_client)] assert firm.save, "Could not save firm" firm.reload @@ -812,7 +816,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_replace_with_new - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") firm.clients = [companies(:second_client), Client.new("name" => "New Client")] firm.save firm.reload @@ -1104,7 +1108,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end def test_creating_using_primary_key - firm = Firm.find(:first) + firm = Firm.find(:first, :order => "id") client = firm.clients_using_primary_key.create!(:name => 'test') assert_equal firm.name, client.firm_name end diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index b1060d01af..9da7fc2639 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -14,7 +14,9 @@ require 'models/citation' class AssociationsJoinModelTest < ActiveRecord::TestCase self.use_transactional_fixtures = false - fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books + fixtures :posts, :authors, :categories, :categorizations, :comments, :tags, :taggings, :author_favorites, :vertices, :items, :books, + # Reload edges table from fixtures as otherwise repeated test was failing + :edges def test_has_many assert authors(:david).categories.include?(categories(:general)) @@ -343,14 +345,16 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase end def test_has_many_polymorphic_with_source_type - assert_equal posts(:welcome, :thinking), tags(:general).tagged_posts + # added sort by ID as otherwise Oracle select sometimes returned rows in different order + assert_equal posts(:welcome, :thinking).sort_by(&:id), tags(:general).tagged_posts.sort_by(&:id) end def test_eager_has_many_polymorphic_with_source_type tag_with_include = Tag.find(tags(:general).id, :include => :tagged_posts) desired = posts(:welcome, :thinking) assert_no_queries do - assert_equal desired, tag_with_include.tagged_posts + # added sort by ID as otherwise test using JRuby was failing as array elements were in different order + assert_equal desired.sort_by(&:id), tag_with_include.tagged_posts.sort_by(&:id) end assert_equal 5, tag_with_include.taggings.length end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 22168468a6..1c05e523e0 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -73,12 +73,16 @@ class Firm < Company has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true - has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account" + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account", :order => "id" has_one :deletable_account, :foreign_key => "firm_id", :class_name => "Account", :dependent => :delete end class DependentFirm < Company - has_one :account, :foreign_key => "firm_id", :dependent => :nullify + # added order by id as in fixtures there are two accounts for Rails Core + # Oracle tests were failing because of that as the second fixture was selected + has_one :account, :foreign_key => "firm_id", :dependent => :nullify, :order => "id" has_many :companies, :foreign_key => 'client_of', :order => "id", :dependent => :nullify end -- cgit v1.2.3 From 71c32d3cacb7b0c0f0828caa5555f279777364fa Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:03:34 +0200 Subject: 1=2 is invalid expression in Oracle SELECT --- activerecord/test/cases/attribute_methods_test.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ab8768ea3e..055590da0a 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -75,13 +75,23 @@ class AttributeMethodsTest < ActiveRecord::TestCase def test_typecast_attribute_from_select_to_false topic = Topic.create(:title => 'Budget') - topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + # Oracle does not support boolean expressions in SELECT + if current_adapter?(:OracleAdapter) + topic = Topic.find(:first, :select => "topics.*, 0 as is_test") + else + topic = Topic.find(:first, :select => "topics.*, 1=2 as is_test") + end assert !topic.is_test? end def test_typecast_attribute_from_select_to_true topic = Topic.create(:title => 'Budget') - topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + # Oracle does not support boolean expressions in SELECT + if current_adapter?(:OracleAdapter) + topic = Topic.find(:first, :select => "topics.*, 1 as is_test") + else + topic = Topic.find(:first, :select => "topics.*, 2=2 as is_test") + end assert topic.is_test? end -- cgit v1.2.3 From d40e3ea936fe37f0dba696c611d49c700ffa3542 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:05:41 +0200 Subject: Oracle saves empty string as NULL --- .../test/cases/autosave_association_test.rb | 46 +++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index ddca5e962d..271086af8e 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -154,7 +154,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_save_fails_for_invalid_belongs_to - assert log = AuditLog.create(:developer_id => 0, :message => "") + # Oracle saves empty string as NULL therefore :message changed to one space + assert log = AuditLog.create(:developer_id => 0, :message => " ") log.developer = Developer.new assert !log.developer.valid? @@ -164,7 +165,8 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test end def test_save_succeeds_for_invalid_belongs_to_with_validate_false - assert log = AuditLog.create(:developer_id => 0, :message=> "") + # Oracle saves empty string as NULL therefore :message changed to one space + assert log = AuditLog.create(:developer_id => 0, :message=> " ") log.unvalidated_developer = Developer.new assert !log.unvalidated_developer.valid? @@ -666,7 +668,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.catchphrase = '' @pirate.ship.name = '' @pirate.save(false) - assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil], [@pirate.reload.catchphrase, @pirate.ship.name] + else + assert_equal ['', ''], [@pirate.reload.catchphrase, @pirate.ship.name] + end end def test_should_allow_to_bypass_validations_on_associated_models_at_any_depth @@ -678,7 +685,12 @@ class TestAutosaveAssociationOnAHasOneAssociation < ActiveRecord::TestCase @pirate.save(false) values = [@pirate.reload.catchphrase, @pirate.ship.name, *@pirate.ship.parts.map(&:name)] - assert_equal ['', '', '', ''], values + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil, nil, nil], values + else + assert_equal ['', '', '', ''], values + end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @@ -756,7 +768,12 @@ class TestAutosaveAssociationOnABelongsToAssociation < ActiveRecord::TestCase @ship.pirate.catchphrase = '' @ship.name = '' @ship.save(false) - assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil], [@ship.reload.name, @ship.pirate.catchphrase] + else + assert_equal ['', ''], [@ship.reload.name, @ship.pirate.catchphrase] + end end def test_should_still_raise_an_ActiveRecordRecord_Invalid_exception_if_we_want_that @@ -837,11 +854,20 @@ module AutosaveAssociationOnACollectionAssociationTests @pirate.send(@association_name).each { |child| child.name = '' } assert @pirate.save(false) - assert_equal ['', '', ''], [ - @pirate.reload.catchphrase, - @pirate.send(@association_name).first.name, - @pirate.send(@association_name).last.name - ] + # Oracle saves empty string as NULL + if current_adapter?(:OracleAdapter) + assert_equal [nil, nil, nil], [ + @pirate.reload.catchphrase, + @pirate.send(@association_name).first.name, + @pirate.send(@association_name).last.name + ] + else + assert_equal ['', '', ''], [ + @pirate.reload.catchphrase, + @pirate.send(@association_name).first.name, + @pirate.send(@association_name).last.name + ] + end end def test_should_validation_the_associated_models_on_create -- cgit v1.2.3 From a12358b3a5e69f41079595d5c92677b66ae6e642 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:07:23 +0200 Subject: Oracle adapter returns numeric (not string) value after SUM --- activerecord/test/cases/calculations_test.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 75f52dfa4a..24bc4f71ce 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -298,7 +298,12 @@ class CalculationsTest < ActiveRecord::TestCase end def test_should_sum_expression - assert_equal '636', Account.sum("2 * credit_limit") + # Oracle adapter returns floating point value 636.0 after SUM + if current_adapter?(:OracleAdapter) + assert_equal 636, Account.sum("2 * credit_limit") + else + assert_equal '636', Account.sum("2 * credit_limit") + end end def test_count_with_from_option -- cgit v1.2.3 From c3e1ef0b4051c515fa2e3f49ea47309c79a89aee Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:08:37 +0200 Subject: Oracle needs sequence value for primary key in INSERT statement --- activerecord/test/cases/database_statements_test.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb index 6274d5250f..c689e97d83 100644 --- a/activerecord/test/cases/database_statements_test.rb +++ b/activerecord/test/cases/database_statements_test.rb @@ -6,7 +6,14 @@ class DatabaseStatementsTest < ActiveRecord::TestCase end def test_insert_should_return_the_inserted_id - id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if current_adapter?(:OracleAdapter) + sequence_name = "accounts_seq" + id_value = @connection.next_sequence_value(sequence_name) + id = @connection.insert("INSERT INTO accounts (id, firm_id,credit_limit) VALUES (accounts_seq.nextval,42,5000)", nil, :id, id_value, sequence_name) + else + id = @connection.insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") + end assert_not_nil id end end -- cgit v1.2.3 From 42fd2a3b162e8f93e9c39b07253561bde3c00e35 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:14:42 +0200 Subject: added :order to find :all as otherwise Oracle tests were failing --- activerecord/test/cases/inheritance_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 167d3abad9..5cd11e9799 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -137,7 +137,8 @@ class InheritanceTest < ActiveRecord::TestCase def test_update_all_within_inheritance Client.update_all "name = 'I am a client'" assert_equal "I am a client", Client.find(:all).first.name - assert_equal "37signals", Firm.find(:all).first.name + # Order by added as otherwise Oracle tests were failing because of different order of results + assert_equal "37signals", Firm.find(:all, :order => "id").first.name end def test_alt_update_all_within_inheritance -- cgit v1.2.3 From 3a1cbc5c3b3bcb2de4be6e4469bb87b99759dc59 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:15:59 +0200 Subject: Oracle adapter returns Time value for DATE columns --- activerecord/test/cases/invalid_date_test.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/invalid_date_test.rb b/activerecord/test/cases/invalid_date_test.rb index e2bb17c37f..99af7d2986 100644 --- a/activerecord/test/cases/invalid_date_test.rb +++ b/activerecord/test/cases/invalid_date_test.rb @@ -11,13 +11,23 @@ class InvalidDateTest < Test::Unit::TestCase valid_dates.each do |date_src| topic = Topic.new("last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s) - assert_equal(topic.last_read, Date.new(*date_src)) + # Oracle DATE columns are datetime columns and Oracle adapter returns Time value + if current_adapter?(:OracleAdapter) + assert_equal(topic.last_read.to_date, Date.new(*date_src)) + else + assert_equal(topic.last_read, Date.new(*date_src)) + end end invalid_dates.each do |date_src| assert_nothing_raised do topic = Topic.new({"last_read(1i)" => date_src[0].to_s, "last_read(2i)" => date_src[1].to_s, "last_read(3i)" => date_src[2].to_s}) - assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + # Oracle DATE columns are datetime columns and Oracle adapter returns Time value + if current_adapter?(:OracleAdapter) + assert_equal(topic.last_read.to_date, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + else + assert_equal(topic.last_read, Time.local(*date_src).to_date, "The date should be modified according to the behaviour of the Time object") + end end end end -- cgit v1.2.3 From ee654e54c40ef41cf8bfa4c25324faeb2bf59de0 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:17:05 +0200 Subject: Oracle generates different ORDER BY fragment --- activerecord/test/cases/method_scoping_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index d8246f49b8..35f7bc5443 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -379,7 +379,8 @@ class NestedScopingTest < ActiveRecord::TestCase poor_jamis = developers(:poor_jamis) Developer.with_scope(:find => { :conditions => "salary < 100000" }) do Developer.with_scope(:find => { :offset => 1, :order => 'id asc' }) do - assert_sql /ORDER BY id asc / do + # Oracle adapter does not generated space after asc therefore trailing space removed from regex + assert_sql /ORDER BY id asc/ do assert_equal(poor_jamis, Developer.find(:first, :order => 'id asc')) end end -- cgit v1.2.3 From 5d0dece6a69c31437b29396a3d4d04f092a9fc1f Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:18:21 +0200 Subject: Oracle adapter gets Time or DateTime value already with timezone --- activerecord/test/cases/migration_test.rb | 63 ++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 17 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 215b5a427a..03788bf69e 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -446,18 +446,22 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal Date, bob.favorite_day.class end - # Test DateTime column and defaults, including timezone. - # FIXME: moment of truth may be Time on 64-bit platforms. - if bob.moment_of_truth.is_a?(DateTime) - - with_env_tz 'US/Eastern' do - assert_equal DateTime.local_offset, bob.moment_of_truth.offset - assert_not_equal 0, bob.moment_of_truth.offset - assert_not_equal "Z", bob.moment_of_truth.zone - # US/Eastern is -5 hours from GMT - assert_equal Rational(-5, 24), bob.moment_of_truth.offset - assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM - assert_equal DateTime::ITALY, bob.moment_of_truth.start + # Oracle adapter stores Time or DateTime with timezone value already in _before_type_cast column + # therefore no timezone change is done afterwards when default timezone is changed + unless current_adapter?(:OracleAdapter) + # Test DateTime column and defaults, including timezone. + # FIXME: moment of truth may be Time on 64-bit platforms. + if bob.moment_of_truth.is_a?(DateTime) + + with_env_tz 'US/Eastern' do + assert_equal DateTime.local_offset, bob.moment_of_truth.offset + assert_not_equal 0, bob.moment_of_truth.offset + assert_not_equal "Z", bob.moment_of_truth.zone + # US/Eastern is -5 hours from GMT + assert_equal Rational(-5, 24), bob.moment_of_truth.offset + assert_match /\A-05:?00\Z/, bob.moment_of_truth.zone #ruby 1.8.6 uses HH:MM, prior versions use HHMM + assert_equal DateTime::ITALY, bob.moment_of_truth.start + end end end @@ -571,7 +575,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Base.connection.create_table(:hats) do |table| table.column :hat_name, :string, :default => nil end - exception = if current_adapter?(:PostgreSQLAdapter) + exception = if current_adapter?(:PostgreSQLAdapter, :OracleAdapter) ActiveRecord::StatementInvalid else ActiveRecord::ActiveRecordError @@ -625,7 +629,13 @@ if ActiveRecord::Base.connection.supports_migrations? table.column :hat_size, :integer table.column :hat_style, :string, :limit => 100 end - ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true + # Oracle index names should be 30 or less characters + if current_adapter?(:OracleAdapter) + ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true, + :name => 'index_hats_on_hat_style_size' + else + ActiveRecord::Base.connection.add_index "hats", ["hat_style", "hat_size"], :unique => true + end assert_nothing_raised { Person.connection.remove_column("hats", "hat_size") } ensure @@ -783,7 +793,12 @@ if ActiveRecord::Base.connection.supports_migrations? assert_nothing_raised { Person.connection.change_column :testings, :select, :string, :limit => 10 } - assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised { Person.connection.execute "insert into testings (id, #{Person.connection.quote_column_name('select')}) values (testings_seq.nextval, '7 chars')" } + else + assert_nothing_raised { Person.connection.execute "insert into testings (#{Person.connection.quote_column_name('select')}) values ('7 chars')" } + end ensure Person.connection.drop_table :testings rescue nil end @@ -799,7 +814,12 @@ if ActiveRecord::Base.connection.supports_migrations? person_klass.reset_column_information assert_equal 99, person_klass.columns_hash["wealth"].default assert_equal false, person_klass.columns_hash["wealth"].null - assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + # Oracle needs primary key value from sequence + if current_adapter?(:OracleAdapter) + assert_nothing_raised {person_klass.connection.execute("insert into testings (id, title) values (testings_seq.nextval, 'tester')")} + else + assert_nothing_raised {person_klass.connection.execute("insert into testings (title) values ('tester')")} + end # change column default to see that column doesn't lose its not null definition person_klass.connection.change_column_default "testings", "wealth", 100 @@ -1054,7 +1074,12 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_migrator_db_has_no_schema_migrations_table - ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") + # Oracle adapter raises error if semicolon is present as last character + if current_adapter?(:OracleAdapter) + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations") + else + ActiveRecord::Base.connection.execute("DROP TABLE schema_migrations;") + end assert_nothing_raised do ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) end @@ -1412,6 +1437,8 @@ if ActiveRecord::Base.connection.supports_migrations? def string_column if current_adapter?(:PostgreSQLAdapter) "character varying(255)" + elsif current_adapter?(:OracleAdapter) + 'VARCHAR2(255)' else 'varchar(255)' end @@ -1420,6 +1447,8 @@ if ActiveRecord::Base.connection.supports_migrations? def integer_column if current_adapter?(:MysqlAdapter) 'int(11)' + elsif current_adapter?(:OracleAdapter) + 'NUMBER(38)' else 'integer' end -- cgit v1.2.3 From 04e6bc1134c7165d455106767c5caabb5993e52b Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:19:27 +0200 Subject: Oracle adapter recourns count() as numeric (not string) --- activerecord/test/cases/query_cache_test.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index f90a66d1dc..2af6a56b6a 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -50,7 +50,12 @@ class QueryCacheTest < ActiveRecord::TestCase def test_cache_does_not_wrap_string_results_in_arrays Task.cache do - assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + # Oracle adapter returns count() as Fixnum or Float + if current_adapter?(:OracleAdapter) + assert Task.connection.select_value("SELECT count(*) AS count_all FROM tasks").is_a?(Numeric) + else + assert_instance_of String, Task.connection.select_value("SELECT count(*) AS count_all FROM tasks") + end end end end -- cgit v1.2.3 From 01a4e07c3625fc2985ca7f2cce40c9d34b1728f8 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:20:42 +0200 Subject: Oracle can store integers with any :limit --- activerecord/test/cases/schema_dumper_test.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9612b0beb6..bf9446a474 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -114,6 +114,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_6.*:limit => 6}, output assert_match %r{c_int_7.*:limit => 7}, output assert_match %r{c_int_8.*:limit => 8}, output + elsif current_adapter?(:OracleAdapter) + assert_match %r{c_int_5.*:limit => 5}, output + assert_match %r{c_int_6.*:limit => 6}, output + assert_match %r{c_int_7.*:limit => 7}, output + assert_match %r{c_int_8.*:limit => 8}, output else assert_match %r{c_int_5.*:limit => 8}, output assert_match %r{c_int_6.*:limit => 8}, output -- cgit v1.2.3 From 090ec47bec21261ea867fbca686bba132f01834d Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:25:58 +0200 Subject: added Subject#after_initialize to be the same as Topic#after_initialize --- activerecord/test/models/subject.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/models/subject.rb b/activerecord/test/models/subject.rb index 3502943f3a..1b9d8107f8 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -1,4 +1,12 @@ -# used for OracleSynonymTest, see test/synonym_test_oci.rb +# used for OracleSynonymTest, see test/synonym_test_oracle.rb # class Subject < ActiveRecord::Base + protected + # added initialization of author_email_address in the same way as in Topic class + # as otherwise synonym test was failing + def after_initialize + if self.new_record? + self.author_email_address = 'test@test.com' + end + end end -- cgit v1.2.3 From 8f34c966141bbbd0bd8da83e8ce7d8fa322bcc91 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 23 Mar 2009 00:27:48 +0200 Subject: support for assert_queries when using Oracle adapter initialize $KCODE to UTF8 when JRuby is used --- .../test/connections/native_oracle/connection.rb | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 1be4e23b84..01d86aba93 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,8 +1,8 @@ # gem "rsim-activerecord-oracle_enhanced-adapter" -# gem "activerecord-oracle_enhanced-adapter" +gem "activerecord-oracle_enhanced-adapter", ">=1.2.0" # uses local copy of oracle_enhanced adapter -$:.unshift("../../oracle-enhanced/lib") -require 'active_record/connection_adapters/oracle_enhanced_adapter' +# $:.unshift("../../oracle-enhanced/lib") +# require 'active_record/connection_adapters/oracle_enhanced_adapter' print "Using Oracle\n" require_dependency 'models/course' @@ -13,7 +13,7 @@ require 'logger' ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB'] || 'XE' +db = ENV['ARUNIT_DB_NAME'] = 'XE' ActiveRecord::Base.configurations = { 'arunit' => { @@ -36,3 +36,19 @@ ActiveRecord::Base.configurations = { ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' + +# for assert_queries test helper +ActiveRecord::Base.connection.class.class_eval do + IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im] + + def select_with_query_record(sql, name = nil, return_column_names = false) + $queries_executed ||= [] + $queries_executed << sql unless IGNORED_SELECT_SQL.any? { |r| sql =~ r } + select_without_query_record(sql, name, return_column_names) + end + + alias_method_chain :select, :query_record +end + +# For JRuby Set default $KCODE to UTF8 +$KCODE = "UTF8" if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' -- cgit v1.2.3 From 8afab34a7699abe5c4eed552815276df01510370 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sun, 12 Apr 2009 20:11:40 +0300 Subject: always sort lists by id before comparison to avoid errors because of different sorting of same results (on Oracle database) --- activerecord/test/cases/named_scope_test.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index f4fdc9a39d..2a729f0678 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -154,7 +154,8 @@ class NamedScopeTest < ActiveRecord::TestCase assert !authors(:david).posts.ranked_by_comments.limit(5).empty? assert_not_equal Post.ranked_by_comments.limit(5), authors(:david).posts.ranked_by_comments.limit(5) assert_not_equal Post.top(5), authors(:david).posts.top(5) - assert_equal authors(:david).posts.ranked_by_comments.limit(5), authors(:david).posts.top(5) + # Oracle sometimes sorts differently if WHERE condition is changed + assert_equal authors(:david).posts.ranked_by_comments.limit(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) assert_equal Post.ranked_by_comments.limit(5), Post.top(5) end -- cgit v1.2.3 From 94e761551b884604b01b43de3da2c873715e9b4b Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Sat, 2 May 2009 20:11:19 +0300 Subject: changed default connection to localhost orcl database --- activerecord/test/connections/native_oracle/connection.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 01d86aba93..23e9f8f9c6 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -13,13 +13,13 @@ require 'logger' ActiveRecord::Base.logger = Logger.new("debug.log") # Set these to your database connection strings -db = ENV['ARUNIT_DB_NAME'] = 'XE' +db = ENV['ARUNIT_DB_NAME'] = 'orcl' ActiveRecord::Base.configurations = { 'arunit' => { :adapter => 'oracle_enhanced', :database => db, - :host => "arunit", # used just by JRuby to construct JDBC connect string + :host => "localhost", # used just by JRuby to construct JDBC connect string :username => 'arunit', :password => 'arunit', :emulate_oracle_adapter => true @@ -27,7 +27,7 @@ ActiveRecord::Base.configurations = { 'arunit2' => { :adapter => 'oracle_enhanced', :database => db, - :host => "arunit", # used just by JRuby to construct JDBC connect string + :host => "localhost", # used just by JRuby to construct JDBC connect string :username => 'arunit2', :password => 'arunit2', :emulate_oracle_adapter => true -- cgit v1.2.3 From 9b2309c4a8a8c8ad46658c170ccfdb828b30c443 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Fri, 5 Jun 2009 18:43:11 +0300 Subject: fix schema_dumper_test for Oracle as it supports precision up to 38 --- activerecord/test/cases/schema_dumper_test.rb | 7 ++++++- activerecord/test/schema/schema.rb | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index bf9446a474..4f8e20b3ba 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -198,6 +198,11 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_keeps_large_precision_integer_columns_as_decimal output = standard_dump - assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output + # Oracle supports precision up to 38 and it identifies decimals with scale 0 as integers + if current_adapter?(:OracleAdapter) + assert_match %r{t.integer\s+"atoms_in_universe",\s+:precision => 38,\s+:scale => 0}, output + else + assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output + end end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 5752d6fa40..5f60d5e137 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -285,7 +285,12 @@ ActiveRecord::Schema.define do t.decimal :my_house_population, :precision => 2, :scale => 0 t.decimal :decimal_number_with_default, :precision => 3, :scale => 2, :default => 2.78 t.float :temperature - t.decimal :atoms_in_universe, :precision => 55, :scale => 0 + # Oracle supports precision up to 38 + if current_adapter?(:OracleAdapter) + t.decimal :atoms_in_universe, :precision => 38, :scale => 0 + else + t.decimal :atoms_in_universe, :precision => 55, :scale => 0 + end end create_table :orders, :force => true do |t| -- cgit v1.2.3 From 44a7ef85eba5491aaef5a890ef2b44b111888d35 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Fri, 5 Jun 2009 18:45:09 +0300 Subject: modifications to Oracle connection.rb setup --- .../test/connections/native_oracle/connection.rb | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/connections/native_oracle/connection.rb b/activerecord/test/connections/native_oracle/connection.rb index 23e9f8f9c6..c8183dc0fb 100644 --- a/activerecord/test/connections/native_oracle/connection.rb +++ b/activerecord/test/connections/native_oracle/connection.rb @@ -1,8 +1,13 @@ # gem "rsim-activerecord-oracle_enhanced-adapter" -gem "activerecord-oracle_enhanced-adapter", ">=1.2.0" +# gem "activerecord-oracle_enhanced-adapter", ">=1.2.1" # uses local copy of oracle_enhanced adapter -# $:.unshift("../../oracle-enhanced/lib") -# require 'active_record/connection_adapters/oracle_enhanced_adapter' +$:.unshift("../../oracle-enhanced/lib") +require 'active_record/connection_adapters/oracle_enhanced_adapter' +# gem "activerecord-jdbc-adapter" +# require 'active_record/connection_adapters/jdbc_adapter' + +# otherwise failed with silence_warnings method missing exception +require 'active_support/core_ext/kernel/reporting' print "Using Oracle\n" require_dependency 'models/course' @@ -20,6 +25,9 @@ ActiveRecord::Base.configurations = { :adapter => 'oracle_enhanced', :database => db, :host => "localhost", # used just by JRuby to construct JDBC connect string + # :adapter => "jdbc", + # :driver => "oracle.jdbc.driver.OracleDriver", + # :url => "jdbc:oracle:thin:@localhost:1521:#{db}", :username => 'arunit', :password => 'arunit', :emulate_oracle_adapter => true @@ -28,6 +36,9 @@ ActiveRecord::Base.configurations = { :adapter => 'oracle_enhanced', :database => db, :host => "localhost", # used just by JRuby to construct JDBC connect string + # :adapter => "jdbc", + # :driver => "oracle.jdbc.driver.OracleDriver", + # :url => "jdbc:oracle:thin:@localhost:1521:#{db}", :username => 'arunit2', :password => 'arunit2', :emulate_oracle_adapter => true @@ -37,6 +48,9 @@ ActiveRecord::Base.configurations = { ActiveRecord::Base.establish_connection 'arunit' Course.establish_connection 'arunit2' +# ActiveRecord::Base.connection.execute %q{alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'} +# ActiveRecord::Base.connection.execute %q{alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS'} rescue nil + # for assert_queries test helper ActiveRecord::Base.connection.class.class_eval do IGNORED_SELECT_SQL = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^\s*select .* from all_tab_columns/im] -- cgit v1.2.3 From 53be10c5e6939c9093c4ae863fd380a6dbf1e50e Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Thu, 23 Jul 2009 20:02:04 +0300 Subject: fixed test_foreign_key_violations_are_translated_to_specific_exception to work with Oracle --- activerecord/test/cases/adapter_test.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 65c5fc2fe9..88136597e3 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -132,7 +132,13 @@ class AdapterTest < ActiveRecord::TestCase def test_foreign_key_violations_are_translated_to_specific_exception unless @connection.adapter_name == 'SQLite' assert_raises(ActiveRecord::InvalidForeignKey) do - @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method + if @connection.prefetch_primary_key? + id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) + @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" + else + @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" + end end end end -- cgit v1.2.3 From 5f0c425e8d106df4cdf844ac4859fc373f9c43e1 Mon Sep 17 00:00:00 2001 From: Raimonds Simanovskis Date: Mon, 3 Aug 2009 14:13:03 +0300 Subject: Some databases (e.g. Oracle) does not allow "AS" between table name and table alias name, for others it is optional --- activerecord/test/cases/finder_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 893fc34c36..7f3be1fa67 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1027,7 +1027,7 @@ class FinderTest < ActiveRecord::TestCase def test_joins_dont_clobber_id first = Firm.find( :first, - :joins => 'INNER JOIN companies AS clients ON clients.firm_id = companies.id', + :joins => 'INNER JOIN companies clients ON clients.firm_id = companies.id', :conditions => 'companies.id = 1' ) assert_equal 1, first.id -- cgit v1.2.3 From 7034272354ad41dd4d1c90138a79e7f14ebc2bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 1 Aug 2009 16:47:44 +0200 Subject: Add destroyed? to ActiveRecord, include tests for polymorphic urls for destroyed objects and refactor mime responds tests and documentation. --- activerecord/lib/active_record/base.rb | 7 +++++++ activerecord/test/cases/base_test.rb | 17 +++++++++++++++++ 2 files changed, 24 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index e358564ead..531a698f77 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2492,6 +2492,11 @@ module ActiveRecord #:nodoc: @new_record || false end + # Returns true if this object has been destroyed, otherwise returns false. + def destroyed? + @destroyed || false + end + # :call-seq: # save(perform_validation = true) # @@ -2542,6 +2547,7 @@ module ActiveRecord #:nodoc: # options, use #destroy. def delete self.class.delete(id) unless new_record? + @destroyed = true freeze end @@ -2556,6 +2562,7 @@ module ActiveRecord #:nodoc: ) end + @destroyed = true freeze end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 82eba81549..16364141df 100755 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1271,6 +1271,23 @@ class BasicsTest < ActiveRecord::TestCase assert_equal Topic.find(1).new_record?, false end + def test_destroyed_returns_boolean + developer = Developer.new + assert_equal developer.destroyed?, false + developer.destroy + assert_equal developer.destroyed?, true + + developer = Developer.first + assert_equal developer.destroyed?, false + developer.destroy + assert_equal developer.destroyed?, true + + developer = Developer.last + assert_equal developer.destroyed?, false + developer.delete + assert_equal developer.destroyed?, true + end + def test_clone topic = Topic.find(1) cloned_topic = nil -- cgit v1.2.3 From 482a6f716fab5ea6431e15ee2603b62a1b2f0790 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Aug 2009 16:41:04 -0700 Subject: Ruby 1.9.2: Object#id is gone now --- activerecord/lib/active_record/attribute_methods/read.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 0b7d6d9094..3da3d9d8cc 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -12,7 +12,7 @@ module ActiveRecord self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT # Undefine id so it can be used as an attribute name - undef_method :id + undef_method(:id) if method_defined?(:id) end module ClassMethods -- cgit v1.2.3 From 019ed5a7c0aac4f1a5924b4a575438f12be18f11 Mon Sep 17 00:00:00 2001 From: Jeremy Kemper Date: Fri, 7 Aug 2009 17:04:19 -0700 Subject: Don't rely on T::U::TestCase#name --- activerecord/test/cases/migration_test.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 03788bf69e..72d4892435 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -737,19 +737,20 @@ if ActiveRecord::Base.connection.supports_migrations? def test_change_column Person.connection.add_column 'people', 'age', :integer - old_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + label = "test_change_column Columns" + old_columns = Person.connection.columns(Person.table_name, label) assert old_columns.find { |c| c.name == 'age' and c.type == :integer } assert_nothing_raised { Person.connection.change_column "people", "age", :string } - new_columns = Person.connection.columns(Person.table_name, "#{name} Columns") + new_columns = Person.connection.columns(Person.table_name, label) assert_nil new_columns.find { |c| c.name == 'age' and c.type == :integer } assert new_columns.find { |c| c.name == 'age' and c.type == :string } - old_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + old_columns = Topic.connection.columns(Topic.table_name, label) assert old_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => false } - new_columns = Topic.connection.columns(Topic.table_name, "#{name} Columns") + new_columns = Topic.connection.columns(Topic.table_name, label) assert_nil new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == true } assert new_columns.find { |c| c.name == 'approved' and c.type == :boolean and c.default == false } assert_nothing_raised { Topic.connection.change_column :topics, :approved, :boolean, :default => true } -- cgit v1.2.3 From 54e7f5ba435b7573c68c7acd65c9d2537427de7e Mon Sep 17 00:00:00 2001 From: Josh Sharpe Date: Fri, 7 Aug 2009 21:12:27 -0400 Subject: Tidy up the AR tests, removing duplicates and making tests clearer / more focussed. Signed-off-by: Michael Koziarski [#2774 state:committed] --- activerecord/test/cases/finder_test.rb | 57 +++++++--------------------------- 1 file changed, 11 insertions(+), 46 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 7f3be1fa67..7b6bf597a8 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -156,10 +156,8 @@ class FinderTest < ActiveRecord::TestCase end def test_find_all_with_limit - entrants = Entrant.find(:all, :order => "id ASC", :limit => 2) - - assert_equal(2, entrants.size) - assert_equal(entrants(:first).name, entrants.first.name) + assert_equal(2, Entrant.find(:all, :limit => 2).size) + assert_equal(0, Entrant.find(:all, :limit => 0).size) end def test_find_all_with_prepared_limit_and_offset @@ -168,22 +166,23 @@ class FinderTest < ActiveRecord::TestCase assert_equal(2, entrants.size) assert_equal(entrants(:second).name, entrants.first.name) + assert_equal 3, Entrant.count entrants = Entrant.find(:all, :order => "id ASC", :limit => 2, :offset => 2) assert_equal(1, entrants.size) assert_equal(entrants(:third).name, entrants.first.name) end - def test_find_all_with_limit_and_offset_and_multiple_orderings - developers = Developer.find(:all, :order => "salary ASC, id DESC", :limit => 3, :offset => 1) - assert_equal ["David", "fixture_10", "fixture_9"], developers.collect {|d| d.name} - end + def test_find_all_with_limit_and_offset_and_multiple_order_clauses + first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 + second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 + last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - def test_find_with_limit_and_condition - developers = Developer.find(:all, :order => "id DESC", :conditions => "salary = 100000", :limit => 3, :offset =>7) - assert_equal(1, developers.size) - assert_equal("fixture_3", developers.first.name) + assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } end + def test_find_with_group developers = Developer.find(:all, :group => "salary", :select => "salary") assert_equal 4, developers.size @@ -978,40 +977,6 @@ class FinderTest < ActiveRecord::TestCase assert_raise(ArgumentError) { Topic.find_by_title 'No Title', :join => "It should be `joins'" } end - def test_find_all_with_limit - first_five_developers = Developer.find :all, :order => 'id ASC', :limit => 5 - assert_equal 5, first_five_developers.length - assert_equal 'David', first_five_developers.first.name - assert_equal 'fixture_5', first_five_developers.last.name - - no_developers = Developer.find :all, :order => 'id ASC', :limit => 0 - assert_equal 0, no_developers.length - end - - def test_find_all_with_limit_and_offset - first_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 0 - second_three_developers = Developer.find :all, :order => 'id ASC', :limit => 3, :offset => 3 - last_two_developers = Developer.find :all, :order => 'id ASC', :limit => 2, :offset => 8 - - assert_equal 3, first_three_developers.length - assert_equal 3, second_three_developers.length - assert_equal 2, last_two_developers.length - - assert_equal 'David', first_three_developers.first.name - assert_equal 'fixture_4', second_three_developers.first.name - assert_equal 'fixture_9', last_two_developers.first.name - end - - def test_find_all_with_limit_and_offset_and_multiple_order_clauses - first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 - second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 - - assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7]], last_posts.map { |p| [p.author_id, p.id] } - end - def test_find_all_with_join developers_on_project_one = Developer.find( :all, -- cgit v1.2.3 From 73f2d37505025a446bb5314a090f412d0fceb8ca Mon Sep 17 00:00:00 2001 From: Wolfram Arnold Date: Mon, 29 Jun 2009 14:20:15 -0700 Subject: Add test to verify that the new :inverse_of association option will indeed fix the validation problem for a belongs_to relationship that validates_presence_of the parent, when both the parent and the child are new (in-memory) records. Also check that this works when the parents adds child via nested_attributes_for. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lastly, add a require 'models/pet' to association_validation_test.rb, so that test can be run independently (was failing due to that missing dependency). [#2815 status:resolved] Signed-off-by: José Valim --- activerecord/test/cases/nested_attributes_test.rb | 37 ++++++++++++++++++++++ .../validations/association_validation_test.rb | 23 ++++++++++++++ 2 files changed, 60 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index f31275163d..d033c1e760 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -4,6 +4,8 @@ require "models/ship" require "models/bird" require "models/parrot" require "models/treasure" +require "models/man" +require "models/interest" require 'active_support/hash_with_indifferent_access' module AssertRaiseWithMessage @@ -470,6 +472,41 @@ module NestedAttributesOnACollectionAssociationTests assert Pirate.reflect_on_association(@association_name).options[:autosave] end + def test_validate_presence_of_parent__works_with_inverse_of + Man.accepts_nested_attributes_for(:interests) + assert_equal :man, Man.reflect_on_association(:interests).options[:inverse_of] + assert_equal :interests, Interest.reflect_on_association(:man).options[:inverse_of] + + repair_validations(Interest) do + Interest.validates_presence_of(:man) + assert_difference 'Man.count' do + assert_difference 'Interest.count', 2 do + man = Man.create!(:name => 'John', + :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) + assert_equal 2, man.interests.count + end + end + end + end + + def test_validate_presence_of_parent__fails_without_inverse_of + Man.accepts_nested_attributes_for(:interests) + Man.reflect_on_association(:interests).options.delete(:inverse_of) + Interest.reflect_on_association(:man).options.delete(:inverse_of) + + repair_validations(Interest) do + Interest.validates_presence_of(:man) + assert_no_difference ['Man.count', 'Interest.count'] do + man = Man.create(:name => 'John', + :interests_attributes => [{:topic=>'Cars'}, {:topic=>'Sports'}]) + assert !man.errors[:interests_man].empty? + end + end + # restore :inverse_of + Man.reflect_on_association(:interests).options[:inverse_of] = :man + Interest.reflect_on_association(:man).options[:inverse_of] = :interests + end + private def association_setter diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb index b1203c12ed..278a7a6a06 100644 --- a/activerecord/test/cases/validations/association_validation_test.rb +++ b/activerecord/test/cases/validations/association_validation_test.rb @@ -3,6 +3,9 @@ require "cases/helper" require 'models/topic' require 'models/reply' require 'models/owner' +require 'models/pet' +require 'models/man' +require 'models/interest' class AssociationValidationTest < ActiveRecord::TestCase fixtures :topics, :owners @@ -98,4 +101,24 @@ class AssociationValidationTest < ActiveRecord::TestCase end end end + + def test_validates_presence_of_belongs_to_association__parent_is_new_record + repair_validations(Interest) do + # Note that Interest and Man have the :inverse_of option set + Interest.validates_presence_of(:man) + man = Man.new(:name => 'John') + interest = man.interests.build(:topic => 'Airplanes') + assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" + end + end + + def test_validates_presence_of_belongs_to_association__existing_parent + repair_validations(Interest) do + Interest.validates_presence_of(:man) + man = Man.create!(:name => 'John') + interest = man.interests.build(:topic => 'Airplanes') + assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated" + end + end + end -- cgit v1.2.3 From 7f84f14efabf3e342a231b8aa9650cb484c802d6 Mon Sep 17 00:00:00 2001 From: Cristi Balan Date: Sat, 8 Aug 2009 17:39:31 +0100 Subject: Add rake db:forward - opposite of db:rollback [#768 state:resolved] Example: rake db:forward # performs the next migration rake db:forward STEP=4 # performs the next 4 migrations Signed-off-by: Pratik Naik --- activerecord/lib/active_record/migration.rb | 10 ++++++++++ activerecord/test/cases/migration_test.rb | 11 +++++++++++ 2 files changed, 21 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 3963baa6b8..d205e57db3 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -397,6 +397,16 @@ module ActiveRecord down(migrations_path, finish ? finish.version : 0) end + def forward(migrations_path, steps=1) + migrator = self.new(:up, migrations_path) + start_index = migrator.migrations.index(migrator.current_migration) + + return unless start_index + + finish = migrator.migrations[start_index + steps] + up(migrations_path, finish ? finish.version : 0) + end + def up(migrations_path, target_version = nil) self.new(:up, migrations_path, target_version).migrate end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 72d4892435..f0f21615e0 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -1136,6 +1136,17 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal(0, ActiveRecord::Migrator.current_version) end + def test_migrator_forward + ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", 1) + assert_equal(1, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid", 2) + assert_equal(3, ActiveRecord::Migrator.current_version) + + ActiveRecord::Migrator.forward(MIGRATIONS_ROOT + "/valid") + assert_equal(3, ActiveRecord::Migrator.current_version) + end + def test_schema_migrations_table_name ActiveRecord::Base.table_name_prefix = "prefix_" ActiveRecord::Base.table_name_suffix = "_suffix" -- cgit v1.2.3 From 6464d76feb94c7547fc6c046c010ea5be9bb6fe6 Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 8 Aug 2009 20:47:14 +0100 Subject: DRY migration's rollback/forward methods --- activerecord/lib/active_record/migration.rb | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index d205e57db3..adb3a3f75e 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -388,23 +388,11 @@ module ActiveRecord end def rollback(migrations_path, steps=1) - migrator = self.new(:down, migrations_path) - start_index = migrator.migrations.index(migrator.current_migration) - - return unless start_index - - finish = migrator.migrations[start_index + steps] - down(migrations_path, finish ? finish.version : 0) + move(:down, migrations_path, steps) end def forward(migrations_path, steps=1) - migrator = self.new(:up, migrations_path) - start_index = migrator.migrations.index(migrator.current_migration) - - return unless start_index - - finish = migrator.migrations[start_index + steps] - up(migrations_path, finish ? finish.version : 0) + move(:up, migrations_path, steps) end def up(migrations_path, target_version = nil) @@ -440,6 +428,19 @@ module ActiveRecord # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}" end + + private + + def move(direction, migrations_path, steps) + migrator = self.new(direction, migrations_path) + start_index = migrator.migrations.index(migrator.current_migration) + + if start_index + finish = migrator.migrations[start_index + steps] + version = finish ? finish.version : 0 + send(direction, migrations_path, version) + end + end end def initialize(direction, migrations_path, target_version = nil) -- cgit v1.2.3 From 761283ffdb5750f8a38e2ed67891d2b2b9152d7f Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Sat, 8 Aug 2009 21:51:33 +0100 Subject: Ensure hm:t#create/create! throws ActiveRecord::RecordNotSaved when the owner is new --- .../lib/active_record/associations/has_many_through_association.rb | 4 ++++ .../test/cases/associations/has_many_through_associations_test.rb | 7 +++++++ 2 files changed, 11 insertions(+) (limited to 'activerecord') 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 e21ef90391..ed7c3a6e08 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -8,6 +8,8 @@ module ActiveRecord alias_method :new, :build def create!(attrs = nil) + ensure_owner_is_not_new + transaction do self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association! } : @reflection.create_association!) object @@ -15,6 +17,8 @@ module ActiveRecord end def create(attrs = nil) + ensure_owner_is_not_new + transaction do self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.create_association } : @reflection.create_association) object 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 8529ff0285..f6b4a42377 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -169,6 +169,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_equal peeps + 1, posts(:thinking).people.count end + def test_create_on_new_record + p = Post.new + + assert_raises(ActiveRecord::RecordNotSaved) { p.people.create(:first_name => "mew") } + assert_raises(ActiveRecord::RecordNotSaved) { p.people.create!(:first_name => "snow") } + end + def test_clear_associations assert_queries(2) { posts(:welcome);posts(:welcome).people(true) } -- cgit v1.2.3 From 3b3798506b403911665c3c24fd055b75d6f6a44f Mon Sep 17 00:00:00 2001 From: Matt Duncan Date: Sat, 8 Aug 2009 20:10:01 -0400 Subject: Adding :from scoping to ActiveRecord calculations Signed-off-by: Michael Koziarski [#1229 state:committed] --- activerecord/lib/active_record/calculations.rb | 2 ++ activerecord/test/cases/calculations_test.rb | 6 ++++++ activerecord/test/models/organization.rb | 2 ++ 3 files changed, 10 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 727f4c1dc6..4a88c43dff 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -197,6 +197,8 @@ module ActiveRecord sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] if options[:from] sql << " FROM #{options[:from]} " + elsif scope && scope[:from] + sql << " FROM #{scope[:from]} " else sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround sql << " FROM #{connection.quote_table_name(table_name)} " diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 24bc4f71ce..c2e02763f6 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -2,6 +2,8 @@ require "cases/helper" require 'models/company' require 'models/topic' require 'models/edge' +require 'models/club' +require 'models/organization' Company.has_many :accounts @@ -223,6 +225,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 15, companies(:rails_core).companies.sum(:id) end + def test_should_sum_scoped_field_with_from + assert_equal Club.count, Organization.clubs.count + end + def test_should_sum_scoped_field_with_conditions assert_equal 8, companies(:rails_core).companies.sum(:id, :conditions => 'id > 7') end diff --git a/activerecord/test/models/organization.rb b/activerecord/test/models/organization.rb index d79d5037c8..c85726169e 100644 --- a/activerecord/test/models/organization.rb +++ b/activerecord/test/models/organization.rb @@ -1,4 +1,6 @@ class Organization < ActiveRecord::Base has_many :member_details has_many :members, :through => :member_details + + named_scope :clubs, { :from => 'clubs' } end \ No newline at end of file -- cgit v1.2.3 From d8041538dd1fed038c1ad30ddca5c9ce8254ee30 Mon Sep 17 00:00:00 2001 From: Hugo Peixoto Date: Sun, 9 Aug 2009 03:49:57 +0100 Subject: MySQL: fix diacritic uniqueness test by setting the default character set and collation to utf8/utf8_unicode_ci [#2883 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/Rakefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 0d33b9d516..09dbc5ad6d 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -64,8 +64,8 @@ end namespace :mysql do desc 'Build the MySQL test databases' task :build_databases do - %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest ) - %x( mysqladmin --user=#{MYSQL_DB_USER} create activerecord_unittest2 ) + %x( echo "create DATABASE activerecord_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) + %x( echo "create DATABASE activerecord_unittest2 DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=#{MYSQL_DB_USER}) end desc 'Drop the MySQL test databases' -- cgit v1.2.3 From 4b7f95eb8b45a32c470a414c7f536fb8cb029bda Mon Sep 17 00:00:00 2001 From: Rob Date: Fri, 1 May 2009 23:22:09 +0100 Subject: Fix binary fixture test on Windows [#2597 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/test/cases/fixtures_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index b07d4f3521..eb3f03c91d 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -185,7 +185,7 @@ class FixturesTest < ActiveRecord::TestCase def test_binary_in_fixtures assert_equal 1, @binaries.size - data = File.read(ASSETS_ROOT + "/flowers.jpg") + data = File.open(ASSETS_ROOT + "/flowers.jpg", 'rb') { |f| f.read } data.force_encoding('ASCII-8BIT') if data.respond_to?(:force_encoding) data.freeze assert_equal data, @flowers.data -- cgit v1.2.3 From 791cccaedab9c3e975b00135db76047ad4435611 Mon Sep 17 00:00:00 2001 From: Tristan Dunn Date: Sat, 8 Aug 2009 18:29:20 -0400 Subject: Don't define a default primary key in the schema dumper. [#1908 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/schema_dumper.rb | 1 - activerecord/test/cases/schema_dumper_test.rb | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 5d88012e4f..c8e1b4f53a 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -84,7 +84,6 @@ HEADER elsif @connection.respond_to?(:primary_key) pk = @connection.primary_key(table) end - pk ||= 'id' tbl.print " create_table #{table.inspect}" if columns.detect { |c| c.name == pk } diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 4f8e20b3ba..e6a77f626b 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -205,4 +205,12 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t.decimal\s+"atoms_in_universe",\s+:precision => 55,\s+:scale => 0}, output end end + + def test_schema_dump_keeps_id_column_when_id_is_false_and_id_column_added + output = standard_dump + match = output.match(%r{create_table "goofy_string_id"(.*)do.*\n(.*)\n}) + assert_not_nil(match, "goofy_string_id table not found") + assert_match %r(:id => false), match[1], "no table id not preserved" + assert_match %r{t.string[[:space:]]+"id",[[:space:]]+:null => false$}, match[2], "non-primary key id column not preserved" + end end -- cgit v1.2.3 From 9bb8ef9edebf5c27b8a1c67ca3776d52afbc1dc4 Mon Sep 17 00:00:00 2001 From: Rich Bradley Date: Sat, 8 Aug 2009 23:29:38 -0700 Subject: Fix for nested :include with namespaced models. [#260 state:committed] --- activerecord/lib/active_record/associations.rb | 2 +- activerecord/test/cases/modules_test.rb | 13 +++++++++++++ activerecord/test/models/company_in_module.rb | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 66aa9332c8..cce7777039 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1912,7 +1912,7 @@ module ActiveRecord descendant end.flatten.compact - remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty? + remove_duplicate_results!(reflection.klass, parent_records, associations[name]) unless parent_records.empty? end end end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 283333fc04..8360416aa2 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -36,4 +36,17 @@ class ModulesTest < ActiveRecord::TestCase assert_equal 'companies', MyApplication::Business::Client.table_name, 'table_name for ActiveRecord model subclass' assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' end + + def test_eager_loading_in_modules + # need to add an eager loading condition to force the eager loading model into + # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 + client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) + + [client_join_loaded, client_sequential_loaded].each do |client| + assert_no_queries do + assert_not_nil(client.firm.account) + end + end + end end diff --git a/activerecord/test/models/company_in_module.rb b/activerecord/test/models/company_in_module.rb index 8b84c2fb5e..cdda7a44d4 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -13,7 +13,7 @@ module MyApplication has_many :clients_like_ms, :conditions => "name = 'Microsoft'", :class_name => "Client", :order => "id" has_many :clients_using_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE client_of = #{id}' - has_one :account, :dependent => :destroy + has_one :account, :class_name => 'MyApplication::Billing::Account', :dependent => :destroy end class Client < Company -- cgit v1.2.3 From f16008afddaf7d80d6bac0ace380e864b78106fe Mon Sep 17 00:00:00 2001 From: Dmitry Ratnikov Date: Sun, 9 Aug 2009 03:35:45 -0500 Subject: Modified Rich Bradley's test-case to fail as part of suite and with a reasonable message Signed-off-by: Jeremy Kemper --- activerecord/test/cases/modules_test.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 8360416aa2..9c44bea74f 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -4,6 +4,16 @@ require 'models/company_in_module' class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers + def setup + # need to make sure Object::Firm is not defined, so that constantize will not be able to cheat when having to load namespaced classes + @firm_const = Object.send(:remove_const, :Firm) if Object.const_defined?(:Firm) + end + + def teardown + # reinstate the Object::Firm constant for further tests + Object.send :const_set, :Firm, @firm_const unless @firm_const.nil? + end + def test_module_spanning_associations firm = MyApplication::Business::Firm.find(:first) assert !firm.clients.empty?, "Firm should have clients" @@ -40,8 +50,12 @@ class ModulesTest < ActiveRecord::TestCase def test_eager_loading_in_modules # need to add an eager loading condition to force the eager loading model into # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 - client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') - client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) + begin + client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) + rescue NameError => nE + flunk "Should be able to resolve all classes via reflections" + end [client_join_loaded, client_sequential_loaded].each do |client| assert_no_queries do -- cgit v1.2.3 From 314ba0433f03b66022ad41d55cc75d2bd9809fe3 Mon Sep 17 00:00:00 2001 From: Dmitry Ratnikov Date: Sun, 9 Aug 2009 03:48:49 -0500 Subject: Changed to use klass instead of constantizing in assign_ids generated method [#260 state:committed] Signed-off-by: Jeremy Kemper --- activerecord/lib/active_record/associations.rb | 2 +- activerecord/test/cases/modules_test.rb | 39 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 13 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index cce7777039..7f299b2aa5 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1367,7 +1367,7 @@ module ActiveRecord define_method("#{reflection.name.to_s.singularize}_ids=") do |new_value| ids = (new_value || []).reject { |nid| nid.blank? } - send("#{reflection.name}=", reflection.class_name.constantize.find(ids)) + send("#{reflection.name}=", reflection.klass.find(ids)) end end end diff --git a/activerecord/test/cases/modules_test.rb b/activerecord/test/cases/modules_test.rb index 9c44bea74f..4f559bcaa5 100644 --- a/activerecord/test/cases/modules_test.rb +++ b/activerecord/test/cases/modules_test.rb @@ -5,13 +5,20 @@ class ModulesTest < ActiveRecord::TestCase fixtures :accounts, :companies, :projects, :developers def setup - # need to make sure Object::Firm is not defined, so that constantize will not be able to cheat when having to load namespaced classes - @firm_const = Object.send(:remove_const, :Firm) if Object.const_defined?(:Firm) + # need to make sure Object::Firm and Object::Client are not defined, + # so that constantize will not be able to cheat when having to load namespaced classes + @undefined_consts = {} + + [:Firm, :Client].each do |const| + @undefined_consts.merge! const => Object.send(:remove_const, const) if Object.const_defined?(const) + end end def teardown - # reinstate the Object::Firm constant for further tests - Object.send :const_set, :Firm, @firm_const unless @firm_const.nil? + # reinstate the constants that we undefined in the setup + @undefined_consts.each do |constant, value| + Object.send :const_set, constant, value unless value.nil? + end end def test_module_spanning_associations @@ -47,17 +54,25 @@ class ModulesTest < ActiveRecord::TestCase assert_equal 'company_contacts', MyApplication::Business::Client::Contact.table_name, 'table_name for ActiveRecord model enclosed by another ActiveRecord model' end + def test_assign_ids + firm = MyApplication::Business::Firm.first + + assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + firm.client_ids = [MyApplication::Business::Client.first.id] + end + end + + # need to add an eager loading condition to force the eager loading model into + # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 def test_eager_loading_in_modules - # need to add an eager loading condition to force the eager loading model into - # the old join model, to test that. See http://dev.rubyonrails.org/ticket/9640 - begin - client_join_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') - client_sequential_loaded = MyApplication::Business::Client.find(3, :include => {:firm => :account}) - rescue NameError => nE - flunk "Should be able to resolve all classes via reflections" + clients = [] + + assert_nothing_raised NameError, "Should be able to resolve all class constants via reflection" do + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}, :conditions => 'accounts.id IS NOT NULL') + clients << MyApplication::Business::Client.find(3, :include => {:firm => :account}) end - [client_join_loaded, client_sequential_loaded].each do |client| + clients.each do |client| assert_no_queries do assert_not_nil(client.firm.account) end -- cgit v1.2.3 From 7d254b5d74144a1e217125e7be21882ce380a3f8 Mon Sep 17 00:00:00 2001 From: Mike Breen Date: Sun, 9 Aug 2009 16:34:05 +0100 Subject: Serialized attributes should only be saved with partial_updates when the serialized attribute is present [#2397 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/attribute_methods/dirty.rb | 2 +- activerecord/test/cases/dirty_test.rb | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 9ec1fbeee1..911c908c8b 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -161,7 +161,7 @@ module ActiveRecord if partial_updates? # Serialized attributes should always be written in case they've been # changed in place. - update_without_dirty(changed | self.class.serialized_attributes.keys) + update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys)) else update_without_dirty end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 1441421a80..74571d923a 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -298,6 +298,16 @@ class DirtyTest < ActiveRecord::TestCase end end + def test_save_should_not_save_serialized_attribute_with_partial_updates_if_not_present + with_partial_updates(Topic) do + Topic.create!(:author_name => 'Bill', :content => {:a => "a"}) + topic = Topic.first(:select => 'id, author_name') + topic.update_attribute :author_name, 'John' + topic = Topic.first + assert_not_nil topic.content + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? -- cgit v1.2.3 From 076ca48bd649ddea4dd1a320879c03a9fe7a0a6d Mon Sep 17 00:00:00 2001 From: railsbob Date: Sun, 9 Aug 2009 13:01:42 +0100 Subject: Ensure hm:t#find does not assign nil to :include [#1845 state:resolved] Signed-off-by: Pratik Naik --- .../lib/active_record/associations/has_many_through_association.rb | 2 +- .../test/cases/associations/has_many_through_associations_test.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'activerecord') 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 ed7c3a6e08..f4507c979c 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -54,7 +54,7 @@ module ActiveRecord options[:select] = construct_select(options[:select]) options[:from] ||= construct_from options[:joins] = construct_joins(options[:joins]) - options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? + options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil? && @reflection.source_reflection.options[:include] end def insert_record(record, force = true, validate = true) 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 f6b4a42377..799ab52025 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -299,4 +299,9 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_has_many_association_through_a_has_many_association_with_nonstandard_primary_keys assert_equal 1, owners(:blackbeard).toys.count end + + def test_find_on_has_many_association_collection_with_include_and_conditions + post_with_no_comments = people(:michael).posts_with_no_comments.first + assert_equal post_with_no_comments, posts(:authorless) + end end -- cgit v1.2.3 From 1c9b3aabdd1ff1e5b0e2b9bf6d5149baf6f23d1b Mon Sep 17 00:00:00 2001 From: Matt Conway Date: Wed, 22 Apr 2009 22:25:46 -0400 Subject: Allow connect_timeout, read_timeout and write_timeout settings for MySQL [#2544 state:resolved] Signed-off-by: Pratik Naik --- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 83cb9cff15..2b882a1f25 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -587,6 +587,10 @@ module ActiveRecord @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher]) end + @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout] + @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout] + @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout] + @connection.real_connect(*@connection_options) # reconnect must be set after real_connect is called, because real_connect sets it to false internally -- cgit v1.2.3