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/lib') 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 ---- 3 files changed, 12 insertions(+), 18 deletions(-) (limited to 'activerecord/lib') 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 -- 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/lib') 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/lib') 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/lib') 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 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) (limited to 'activerecord/lib') 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 -- 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/lib') 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/lib') 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/lib') 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/lib') 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 ++----------- 2 files changed, 6 insertions(+), 15 deletions(-) (limited to 'activerecord/lib') 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] -- 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/lib') 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/lib') 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/lib') 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/lib') 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/lib') 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/lib/active_record/connection_adapters/sqlite_adapter.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 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/lib') 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] --- .../lib/active_record/connection_adapters/abstract/quoting.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 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 -- 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] --- .../active_record/connection_adapters/abstract/schema_definitions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 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) -- 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 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 activerecord/lib/active_record/state_machine.rb (limited to 'activerecord/lib') 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 -- 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 +- 4 files changed, 121 insertions(+), 41 deletions(-) (limited to 'activerecord/lib') 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 -- 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 +++++++++++++++------- 1 file changed, 29 insertions(+), 12 deletions(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 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 -- 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/lib') 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 - 5 files changed, 15 insertions(+), 251 deletions(-) (limited to 'activerecord/lib') 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 -- 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. --- .../active_record/connection_adapters/abstract/schema_definitions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 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) -- 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/lib') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 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 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 +++++++ 1 file changed, 7 insertions(+) (limited to 'activerecord/lib') 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 -- 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/lib') 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 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 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'activerecord/lib') 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 -- 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/lib') 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 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 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 -- 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 ++ 1 file changed, 2 insertions(+) (limited to 'activerecord/lib') 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)} " -- 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 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib') 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 } -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 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 -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 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 -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 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 -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib') diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 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) -- 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/lib') 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