module ActiveRecord::AttributeMethods::ClassMethods
def attribute_method_suffix(*suffixes)
person.name = 'Hubert'
person.name_changed? # => false
person = Person.find(1)
end
end
...
def attribute_changed?(attr)
private
attribute_method_suffix '_changed?'
class Person < ActiveRecord::Base
For example:
the +attr+ argument.
An attribute#{suffix} instance method must exist and accept at least
attribute#{suffix}(#{attr}, *args, &block)
to
#{attr}#{suffix}(*args, &block)
Uses +method_missing+ and respond_to? to rewrite the method
Declares a method available for all attributes with the given suffix.
def attribute_method_suffix(*suffixes) attribute_method_suffixes.concat suffixes rebuild_attribute_method_regexp end
def attribute_method_suffixes
def attribute_method_suffixes @@attribute_method_suffixes ||= [] end
def cache_attribute?(attr_name)
def cache_attribute?(attr_name) cached_attributes.include?(attr_name) end
def cache_attributes(*attribute_names)
be cached. Usually caching only pays off for attributes with expensive conversion
+cache_attributes+ allows you to declare which converted attribute values should
def cache_attributes(*attribute_names) attribute_names.each {|attr| cached_attributes << attr.to_s} end
def cached_attributes
Returns the attributes which are cached. By default time related columns
def cached_attributes @cached_attributes ||= columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set end
def create_time_zone_conversion_attribute?(name, column)
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
def define_attribute_methods
Generates all the attribute related methods for columns in the database
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) end end unless instance_method_already_implemented?("#{name}?") define_question_method(name) end end end
def define_question_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_read_method(symbol, attr_name, 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
def define_read_method_for_serialized_attribute(attr_name)
def define_read_method_for_serialized_attribute(attr_name) evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end" end
def define_read_method_for_time_zone_conversion(attr_name)
Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
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
def define_write_method(attr_name)
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
def define_write_method_for_time_zone_conversion(attr_name)
Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
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
def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name) unless method_name.to_s == primary_key.to_s generated_methods << method_name end begin class_eval(method_definition, __FILE__) rescue SyntaxError => err generated_methods.delete(attr_name) if logger logger.warn "Exception occurred during reader method compilation." logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?" logger.warn err.message end end end
def generated_methods #:nodoc:
Contains the names of the generated attribute methods.
def generated_methods #:nodoc: @generated_methods ||= Set.new end
def generated_methods?
def generated_methods? !generated_methods.empty? end
def instance_method_already_implemented?(method_name)
that also derive from Active Record. Raises DangerousAttributeError if the
Checks whether the method is defined in the model or any of its subclasses
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(&: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(&: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
def match_attribute_method?(method_name)
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 rebuild_attribute_method_regexp
def rebuild_attribute_method_regexp suffixes = attribute_method_suffixes.map { |s| Regexp.escape(s) } @@attribute_method_regexp = /(#{suffixes.join('|')})$/.freeze end