aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb187
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb41
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb6
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/write.rb4
-rwxr-xr-xactiverecord/lib/active_record/base.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb1
-rw-r--r--activerecord/lib/active_record/state_machine.rb24
10 files changed, 79 insertions, 207 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 5cb536af1f..ab7ad34b9e 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -3,109 +3,13 @@ require 'active_support/core_ext/enumerable'
module ActiveRecord
module AttributeMethods #:nodoc:
extend ActiveSupport::Concern
+ include ActiveModel::AttributeMethods
- # Declare and check for suffixed attribute methods.
module ClassMethods
- # Declares a method available for all attributes with the given suffix.
- # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method
- #
- # #{attr}#{suffix}(*args, &block)
- #
- # to
- #
- # attribute#{suffix}(#{attr}, *args, &block)
- #
- # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
- # the +attr+ argument.
- #
- # For example:
- #
- # class Person < ActiveRecord::Base
- # attribute_method_suffix '_changed?'
- #
- # private
- # def attribute_changed?(attr)
- # ...
- # end
- # end
- #
- # person = Person.find(1)
- # person.name_changed? # => false
- # person.name = 'Hubert'
- # person.name_changed? # => true
- def attribute_method_suffix(*suffixes)
- attribute_method_suffixes.concat(suffixes)
- rebuild_attribute_method_regexp
- 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
- @@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.keys.each do |name|
- attribute_method_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("def #{method_name}(*args); send(:attribute#{suffix}, '#{name}', *args); end", method_name)
- end
- end
- end
- end
- end
-
- def undefine_attribute_methods
- generated_methods.each { |name| undef_method(name) }
- @generated_methods = nil
+ super(columns_hash.keys)
end
# Checks whether the method is defined in the model or any of its subclasses
@@ -118,101 +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
- # 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 ||= []
- 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 <tt>@attributes</tt> 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?
+ 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.include?(method_name)
+ if self.class.generated_attribute_methods.instance_methods.include?(method_name)
return self.send(method_id, *args, &block)
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
- end
super
end
- # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
- # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # 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 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?
- self.class.define_attribute_methods
- if self.class.generated_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)
- 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/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 <tt>reset_*!</tt> for +method_missing+.
+ def reset_attribute!(attr)
+ self[attr] = changed_attributes[attr] if attribute_changed?(attr)
+ end
+
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
changed_attributes[attr] = clone_attribute_value(:read_attribute, attr)
@@ -175,9 +192,9 @@ module ActiveRecord
def alias_attribute_with_dirty(new_name, old_name)
alias_attribute_without_dirty(new_name, old_name)
- DIRTY_SUFFIXES.each do |suffix|
+ DIRTY_AFFIXES.each do |affixes|
module_eval <<-STR, __FILE__, __LINE__+1
- def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
+ def #{affixes[:prefix]}#{new_name}#{affixes[:suffix]}; self.#{affixes[:prefix]}#{old_name}#{affixes[:suffix]}; end # def reset_subject!; self.reset_title!; end
STR
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index bea332ef26..0b7d6d9094 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
@@ -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_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
- evaluate_attribute_method "def #{symbol}; #{access_code}; end", symbol
+ 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 9e2c6174c6..a8e3e28a7a 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)
@@ -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_attribute_methods.module_eval(method_body, __FILE__, __LINE__)
else
super
end
@@ -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)
@@ -44,7 +44,7 @@ module ActiveRecord
write_attribute(:#{attr_name}, time)
end
EOV
- evaluate_attribute_method method_body, "#{attr_name}="
+ 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 497e72ee4a..e31acac050 100644
--- a/activerecord/lib/active_record/attribute_methods/write.rb
+++ b/activerecord/lib/active_record/attribute_methods/write.rb
@@ -9,8 +9,8 @@ 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}="
+ def define_method_attribute=(attr_name)
+ 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 <tt>:select</tt> has been specified.
- class MissingAttributeError < NoMethodError
- end
-
# Raised when unknown attributes are supplied via mass assignment.
class UnknownAttributeError < NoMethodError
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 720fba29e9..8649f96498 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -60,7 +60,12 @@ module ActiveRecord
end
def quoted_date(value)
- value.to_s(:db)
+ if value.acts_like?(:time)
+ zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
+ value.respond_to?(zone_conversion_method) ? value.send(zone_conversion_method) : value
+ else
+ value
+ end.to_s(:db)
end
def quoted_string_prefix
diff --git a/activerecord/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
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"
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