aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record/base.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record/base.rb')
-rwxr-xr-xactiverecord/lib/active_record/base.rb438
1 files changed, 45 insertions, 393 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 06244d1132..12feef4849 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -556,122 +556,9 @@ module ActiveRecord #:nodoc:
end
alias :colorize_logging= :colorize_logging
- # Find operates with four different retrieval approaches:
- #
- # * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
- # If no record can be found for all of the listed ids, then RecordNotFound will be raised.
- # * Find first - This will return the first record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
- # * Find last - This will return the last record matched by the options used. These options can either be specific
- # conditions or merely an order. If no record can be matched, +nil+ is returned. Use
- # <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
- # * Find all - This will return all the records matched by the options used.
- # If no records are found, an empty array is returned. Use
- # <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
- #
- # All approaches accept an options hash as their last parameter.
- #
- # ==== Parameters
- #
- # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
- # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
- # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
- # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
- # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
- # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
- # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed),
- # named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s),
- # or an array containing a mixture of both strings and named associations.
- # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
- # Pass <tt>:readonly => false</tt> to override.
- # * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
- # to already defined associations. See eager loading under Associations.
- # * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
- # include the joined columns. Takes a string with the SELECT SQL fragment (e.g. "id, name").
- # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
- # of a database view).
- # * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
- # * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
- # <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
- #
- # ==== Examples
- #
- # # find by id
- # Person.find(1) # returns the object for ID = 1
- # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
- # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
- # Person.find([1]) # returns an array for the object with ID = 1
- # Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
- #
- # Note that returned records may not be in the same order as the ids you
- # provide since database rows are unordered. Give an explicit <tt>:order</tt>
- # to ensure the results are sorted.
- #
- # ==== Examples
- #
- # # find first
- # Person.find(:first) # returns the first object fetched by SELECT * FROM people
- # Person.find(:first, :conditions => [ "user_name = ?", user_name])
- # Person.find(:first, :conditions => [ "user_name = :u", { :u => user_name }])
- # Person.find(:first, :order => "created_on DESC", :offset => 5)
- #
- # # find last
- # Person.find(:last) # returns the last object fetched by SELECT * FROM people
- # Person.find(:last, :conditions => [ "user_name = ?", user_name])
- # Person.find(:last, :order => "created_on DESC", :offset => 5)
- #
- # # find all
- # Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
- # Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
- # Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
- # Person.find(:all, :offset => 10, :limit => 10)
- # Person.find(:all, :include => [ :account, :friends ])
- # Person.find(:all, :group => "category")
- #
- # Example for find with a lock: Imagine two concurrent transactions:
- # each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
- # in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
- # transaction has to wait until the first is finished; we get the
- # expected <tt>person.visits == 4</tt>.
- #
- # Person.transaction do
- # person = Person.find(1, :lock => true)
- # person.visits += 1
- # person.save!
- # end
- def find(*args)
- options = args.extract_options!
-
- relation = construct_finder_arel(options, current_scoped_methods)
-
- case args.first
- when :first, :last, :all
- relation.send(args.first)
- else
- relation.find(*args)
- end
- end
-
+ delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
-
- # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:first)</tt>.
- def first(*args)
- find(:first, *args)
- end
-
- # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:last)</tt>.
- def last(*args)
- find(:last, *args)
- end
-
- # A convenience wrapper for <tt>find(:all, *args)</tt>. You can pass in all the
- # same arguments to this method as you can to <tt>find(:all)</tt>.
- def all(*args)
- find(:all, *args)
- end
+ delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
# Executes a custom SQL query against your database and returns all the results. The results will
# be returned as an array with columns requested encapsulated as attributes of the model you call
@@ -699,40 +586,6 @@ module ActiveRecord #:nodoc:
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
end
- # Returns true if a record exists in the table that matches the +id+ or
- # conditions given, or false otherwise. The argument can take five forms:
- #
- # * Integer - Finds the record with this primary key.
- # * String - Finds the record with a primary key corresponding to this
- # string (such as <tt>'5'</tt>).
- # * Array - Finds the record that matches these +find+-style conditions
- # (such as <tt>['color = ?', 'red']</tt>).
- # * Hash - Finds the record that matches these +find+-style conditions
- # (such as <tt>{:color => 'red'}</tt>).
- # * No args - Returns false if the table is empty, true otherwise.
- #
- # For more information about specifying conditions as a Hash or Array,
- # see the Conditions section in the introduction to ActiveRecord::Base.
- #
- # Note: You can't pass in a condition as a string (like <tt>name =
- # 'Jamie'</tt>), since it would be sanitized and then queried against
- # the primary key column, like <tt>id = 'name = \'Jamie\''</tt>.
- #
- # ==== Examples
- # Person.exists?(5)
- # Person.exists?('5')
- # Person.exists?(:name => "David")
- # Person.exists?(['name LIKE ?', "%#{query}%"])
- # Person.exists?
- def exists?(id_or_conditions = nil)
- case id_or_conditions
- when Array, Hash
- where(id_or_conditions).exists?
- else
- scoped.exists?(id_or_conditions)
- end
- end
-
# Creates an object (or multiple objects) and saves it to the database, if validations pass.
# The resulting object is returned whether the object was saved successfully to the database or not.
#
@@ -766,177 +619,6 @@ module ActiveRecord #:nodoc:
end
end
- # Updates an object (or multiple objects) and saves it to the database, if validations pass.
- # The resulting object is returned whether the object was saved successfully to the database or not.
- #
- # ==== Parameters
- #
- # * +id+ - This should be the id or an array of ids to be updated.
- # * +attributes+ - This should be a hash of attributes to be set on the object, or an array of hashes.
- #
- # ==== Examples
- #
- # # Updating one record:
- # Person.update(15, :user_name => 'Samuel', :group => 'expert')
- #
- # # Updating multiple records:
- # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
- # Person.update(people.keys, people.values)
- def update(id, attributes)
- if id.is_a?(Array)
- idx = -1
- id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
- else
- object = find(id)
- object.update_attributes(attributes)
- object
- end
- end
-
- # Deletes the row with a primary key matching the +id+ argument, using a
- # SQL +DELETE+ statement, and returns the number of rows deleted. Active
- # Record objects are not instantiated, so the object's callbacks are not
- # executed, including any <tt>:dependent</tt> association options or
- # Observer methods.
- #
- # You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
- #
- # Note: Although it is often much faster than the alternative,
- # <tt>#destroy</tt>, skipping callbacks might bypass business logic in
- # your application that ensures referential integrity or performs other
- # essential jobs.
- #
- # ==== Examples
- #
- # # Delete a single row
- # Todo.delete(1)
- #
- # # Delete multiple rows
- # Todo.delete([2,3,4])
- def delete(id_or_array)
- scoped.delete(id_or_array)
- end
-
- # Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
- # therefore all callbacks and filters are fired off before the object is deleted. This method is
- # less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
- #
- # This essentially finds the object (or multiple objects) with the given id, creates a new object
- # from the attributes, and then calls destroy on it.
- #
- # ==== Parameters
- #
- # * +id+ - Can be either an Integer or an Array of Integers.
- #
- # ==== Examples
- #
- # # Destroy a single object
- # Todo.destroy(1)
- #
- # # Destroy multiple objects
- # todos = [1,2,3]
- # Todo.destroy(todos)
- def destroy(id)
- if id.is_a?(Array)
- id.map { |one_id| destroy(one_id) }
- else
- find(id).destroy
- end
- end
-
- # Updates all records with details given if they match a set of conditions supplied, limits and order can
- # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
- # or validations.
- #
- # ==== Parameters
- #
- # * +updates+ - A string, array, or hash representing the SET part of an SQL statement.
- # * +conditions+ - A string, array, or hash representing the WHERE part of an SQL statement. See conditions in the intro.
- # * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
- #
- # ==== Examples
- #
- # # Update all customers with the given attributes
- # Customer.update_all :wants_email => true
- #
- # # Update all books with 'Rails' in their title
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'"
- #
- # # Update all avatars migrated more than a week ago
- # Avatar.update_all ['migrated_at = ?', Time.now.utc], ['migrated_at > ?', 1.week.ago]
- #
- # # Update all books that match our conditions, but limit it to 5 ordered by date
- # Book.update_all "author = 'David'", "title LIKE '%Rails%'", :order => 'created_at', :limit => 5
- def update_all(updates, conditions = nil, options = {})
- relation = unscoped
-
- relation = relation.where(conditions) if conditions
- relation = relation.limit(options[:limit]) if options[:limit].present?
- relation = relation.order(options[:order]) if options[:order].present?
-
- if current_scoped_methods && current_scoped_methods.limit_value.present? && current_scoped_methods.order_values.present?
- # Only take order from scope if limit is also provided by scope, this
- # is useful for updating a has_many association with a limit.
- relation = current_scoped_methods.merge(relation) if current_scoped_methods
- else
- relation = current_scoped_methods.except(:limit, :order).merge(relation) if current_scoped_methods
- end
-
- relation.update(sanitize_sql_for_assignment(updates))
- end
-
- # Destroys the records matching +conditions+ by instantiating each
- # record and calling its +destroy+ method. Each object's callbacks are
- # executed (including <tt>:dependent</tt> association options and
- # +before_destroy+/+after_destroy+ Observer methods). Returns the
- # collection of objects that were destroyed; each will be frozen, to
- # reflect that no changes should be made (since they can't be
- # persisted).
- #
- # Note: Instantiation, callback execution, and deletion of each
- # record can be time consuming when you're removing many records at
- # once. It generates at least one SQL +DELETE+ query per record (or
- # possibly more, to enforce your callbacks). If you want to delete many
- # rows quickly, without concern for their associations or callbacks, use
- # +delete_all+ instead.
- #
- # ==== Parameters
- #
- # * +conditions+ - A string, array, or hash that specifies which records
- # to destroy. If omitted, all records are destroyed. See the
- # Conditions section in the introduction to ActiveRecord::Base for
- # more information.
- #
- # ==== Examples
- #
- # Person.destroy_all("last_login < '2004-04-04'")
- # Person.destroy_all(:status => "inactive")
- def destroy_all(conditions = nil)
- where(conditions).destroy_all
- end
-
- # Deletes the records matching +conditions+ without instantiating the records first, and hence not
- # calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
- # goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
- # though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
- # the number of rows affected.
- #
- # ==== Parameters
- #
- # * +conditions+ - Conditions are specified the same way as with +find+ method.
- #
- # ==== Example
- #
- # Post.delete_all("person_id = 5 AND (category = 'Something' OR category = 'Else')")
- # Post.delete_all(["person_id = ? AND (category = ? OR category = ?)", 5, 'Something', 'Else'])
- #
- # Both calls delete the affected posts all at once with a single DELETE statement. If you need to destroy dependent
- # associations or call your <tt>before_*</tt> or +after_destroy+ callbacks, use the +destroy_all+ method instead.
- def delete_all(conditions = nil)
- where(conditions).delete_all
- end
-
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
# The use of this method should be restricted to complicated SQL queries that can't be executed
# using the ActiveRecord::Calculations class methods. Look into those before using this.
@@ -1224,6 +906,10 @@ module ActiveRecord #:nodoc:
reset_table_name
end
+ def quoted_table_name
+ @quoted_table_name ||= connection.quote_table_name(table_name)
+ end
+
def reset_table_name #:nodoc:
base = base_class
@@ -1241,6 +927,7 @@ module ActiveRecord #:nodoc:
name = "#{table_name_prefix}#{contained}#{undecorated_table_name(base.name)}#{table_name_suffix}"
end
+ @quoted_table_name = nil
set_table_name(name)
name
end
@@ -1487,20 +1174,6 @@ module ActiveRecord #:nodoc:
store_full_sti_class ? name : name.demodulize
end
- # Merges conditions so that the result is a valid +condition+
- def merge_conditions(*conditions)
- segments = []
-
- conditions.each do |condition|
- unless condition.blank?
- sql = sanitize_sql(condition)
- segments << sql unless sql.blank?
- end
- end
-
- "(#{segments.join(') AND (')})" unless segments.empty?
- end
-
def unscoped
@unscoped ||= Relation.new(self, arel_table)
finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
@@ -1527,7 +1200,7 @@ module ActiveRecord #:nodoc:
def instantiate(record)
object = find_sti_class(record[inheritance_column]).allocate
- object.send(:initialize_attribute_store, record)
+ object.instance_variable_set(:'@attributes', record)
object.instance_variable_set(:'@attributes_cache', {})
object.send(:_run_find_callbacks)
@@ -1563,43 +1236,11 @@ module ActiveRecord #:nodoc:
end
def construct_finder_arel(options = {}, scope = nil)
- relation = unscoped.apply_finder_options(options)
+ relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : unscoped.merge(options)
relation = scope.merge(relation) if scope
relation
end
- def construct_join(joins)
- case joins
- when Symbol, Hash, Array
- if array_of_strings?(joins)
- joins.join(' ') + " "
- else
- build_association_joins(joins)
- end
- when String
- " #{joins} "
- else
- ""
- end
- end
-
- def build_association_joins(joins)
- join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, joins, nil)
- relation = unscoped.table
- join_dependency.join_associations.map { |association|
- if (association_relation = association.relation).is_a?(Array)
- [Arel::InnerJoin.new(relation, association_relation.first, *association.association_join.first).joins(relation),
- Arel::InnerJoin.new(relation, association_relation.last, *association.association_join.last).joins(relation)].join()
- else
- Arel::InnerJoin.new(relation, association_relation, *association.association_join).joins(relation)
- end
- }.join(" ")
- end
-
- def array_of_strings?(o)
- o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
- end
-
def type_condition
sti_column = arel_table[inheritance_column]
condition = sti_column.eq(sti_name)
@@ -1762,11 +1403,8 @@ module ActiveRecord #:nodoc:
relation = construct_finder_arel(method_scoping[:find] || {})
if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create]
- scope_for_create = case action
- when :merge
+ scope_for_create = if action == :merge
current_scoped_methods.create_with_value.merge(method_scoping[:create])
- when :reverse_merge
- method_scoping[:create].merge(current_scoped_methods.create_with_value)
else
method_scoping[:create]
end
@@ -1781,15 +1419,7 @@ module ActiveRecord #:nodoc:
method_scoping = relation
end
- if current_scoped_methods
- case action
- when :merge
- method_scoping = current_scoped_methods.merge(method_scoping)
- when :reverse_merge
- method_scoping = current_scoped_methods.except(:where).merge(method_scoping)
- method_scoping = method_scoping.merge(current_scoped_methods.only(:where))
- end
- end
+ method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge
self.scoped_methods << method_scoping
begin
@@ -1820,7 +1450,8 @@ module ActiveRecord #:nodoc:
end
def scoped_methods #:nodoc:
- Thread.current[:"#{self}_scoped_methods"] ||= self.default_scoping.dup
+ key = :"#{self}_scoped_methods"
+ Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
end
def current_scoped_methods #:nodoc:
@@ -2033,7 +1664,7 @@ module ActiveRecord #:nodoc:
# In both instances, valid attribute keys are determined by the column names of the associated table --
# hence you can't have attributes that aren't part of the table columns.
def initialize(attributes = nil)
- initialize_attribute_store(attributes_from_column_definition)
+ @attributes = attributes_from_column_definition
@attributes_cache = {}
@new_record = true
ensure_proper_type
@@ -2064,7 +1695,7 @@ module ActiveRecord #:nodoc:
callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
cloned_attributes.delete(self.class.primary_key)
- initialize_attribute_store(cloned_attributes)
+ @attributes = cloned_attributes
clear_aggregation_cache
@attributes_cache = {}
@new_record = true
@@ -2294,11 +1925,21 @@ module ActiveRecord #:nodoc:
def reload(options = nil)
clear_aggregation_cache
clear_association_cache
- _attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
@attributes_cache = {}
self
end
+ # Returns true if the given attribute is in the attributes hash
+ def has_attribute?(attr_name)
+ @attributes.has_key?(attr_name.to_s)
+ end
+
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
+ def attribute_names
+ @attributes.keys.sort
+ end
+
# Returns the value of the attribute identified by <tt>attr_name</tt> 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)).
# (Alias for the protected read_attribute method).
@@ -2480,7 +2121,7 @@ module ActiveRecord #:nodoc:
def update(attribute_names = @attributes.keys)
attributes_with_values = arel_attributes_values(false, false, attribute_names)
return 0 if attributes_with_values.empty?
- self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
+ self.class.unscoped.where(self.class.arel_table[self.class.primary_key].eq(id)).arel.update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2632,7 +2273,7 @@ module ActiveRecord #:nodoc:
end
def instantiate_time_object(name, values)
- if self.class.send(:time_zone_aware?, name)
+ if self.class.send(:create_time_zone_conversion_attribute?, name, column_for_attribute(name))
Time.zone.local(*values)
else
Time.time_with_datetime_fallback(@@default_timezone, *values)
@@ -2704,10 +2345,6 @@ module ActiveRecord #:nodoc:
hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
end
- def self.quoted_table_name
- self.connection.quote_table_name(self.table_name)
- end
-
def quote_columns(quoter, hash)
hash.inject({}) do |quoted, (name, value)|
quoted[quoter.quote_column_name(name)] = value
@@ -2719,6 +2356,22 @@ module ActiveRecord #:nodoc:
comma_pair_list(quote_columns(quoter, hash))
end
+ def convert_number_column_value(value)
+ if value == false
+ 0
+ elsif value == true
+ 1
+ elsif value.is_a?(String) && value.blank?
+ nil
+ else
+ value
+ end
+ end
+
+ def object_from_yaml(string)
+ return string unless string.is_a?(String) && string =~ /^---/
+ YAML::load(string) rescue string
+ end
end
Base.class_eval do
@@ -2733,7 +2386,6 @@ module ActiveRecord #:nodoc:
include AttributeMethods::PrimaryKey
include AttributeMethods::TimeZoneConversion
include AttributeMethods::Dirty
- include Attributes, Types
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
include ActiveModel::Conversion
@@ -2742,7 +2394,7 @@ module ActiveRecord #:nodoc:
# #save_with_autosave_associations to be wrapped inside a transaction.
include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Batches, Calculations, Serialization
+ include Aggregations, Transactions, Reflection, Batches, Serialization
end
end