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 --- .../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 ++++ 6 files changed, 420 insertions(+) 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 (limited to 'activerecord/lib/active_record/attribute_methods') 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 -- 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/read.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record/attribute_methods') 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 -- 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 --- .../lib/active_record/attribute_methods/read.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'activerecord/lib/active_record/attribute_methods') 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 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 --- .../lib/active_record/attribute_methods/before_type_cast.rb | 12 ++++++++++++ activerecord/lib/active_record/attribute_methods/read.rb | 11 +++++++++++ activerecord/lib/active_record/attribute_methods/write.rb | 5 +++++ 3 files changed, 28 insertions(+) (limited to 'activerecord/lib/active_record/attribute_methods') 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) -- 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 --- .../lib/active_record/attribute_methods/before_type_cast.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record/attribute_methods') 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/write.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'activerecord/lib/active_record/attribute_methods') 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/read.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record/attribute_methods') 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/read.rb | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) (limited to 'activerecord/lib/active_record/attribute_methods') 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/read.rb | 4 ++-- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 4 ++-- activerecord/lib/active_record/attribute_methods/write.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record/attribute_methods') 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/read.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'activerecord/lib/active_record/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 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 --- .../active_record/attribute_methods/primary_key.rb | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 activerecord/lib/active_record/attribute_methods/primary_key.rb (limited to 'activerecord/lib/active_record/attribute_methods') 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 -- cgit v1.2.3