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.rb156
1 files changed, 117 insertions, 39 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ec49d40a12..56f3bd9faa 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -400,7 +400,7 @@ module ActiveRecord #:nodoc:
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
# instances in the current object space.
class Base
- ##
+ ##
# :singleton-method:
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
# on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
@@ -434,11 +434,11 @@ module ActiveRecord #:nodoc:
# as a Hash.
#
# For example, the following database.yml...
- #
+ #
# development:
# adapter: sqlite3
# database: db/development.sqlite3
- #
+ #
# production:
# adapter: sqlite3
# database: db/production.sqlite3
@@ -909,9 +909,7 @@ module ActiveRecord #:nodoc:
# 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)
- sql = "DELETE FROM #{quoted_table_name} "
- add_conditions!(sql, conditions, scope(:find))
- connection.delete(sql, "#{name} Delete all")
+ arel_table.where(construct_conditions(conditions, scope(:find))).delete
end
# Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
@@ -1084,7 +1082,7 @@ module ActiveRecord #:nodoc:
# Returns an array of all the attributes that have been specified as readonly.
def readonly_attributes
- read_inheritable_attribute(:attr_readonly)
+ read_inheritable_attribute(:attr_readonly) || []
end
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
@@ -1356,7 +1354,7 @@ module ActiveRecord #:nodoc:
def self_and_descendants_from_active_record#nodoc:
klass = self
classes = [klass]
- while klass != klass.base_class
+ while klass != klass.base_class
classes << klass = klass.superclass
end
classes
@@ -1390,7 +1388,7 @@ module ActiveRecord #:nodoc:
def human_name(options = {})
defaults = self_and_descendants_from_active_record.map do |klass|
:"#{klass.name.underscore}"
- end
+ end
defaults << self.name.humanize
I18n.translate(defaults.shift, {:scope => [:activerecord, :models], :count => 1, :default => defaults}.merge(options))
end
@@ -1692,22 +1690,92 @@ module ActiveRecord #:nodoc:
end
end
- def construct_finder_sql(options)
+ def arel_table(table = table_name)
+ @arel_table = Arel(table)
+ end
+
+ def construct_finder_arel(options)
scope = scope(:find)
- sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
- sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
- add_joins!(sql, options[:joins], scope)
- add_conditions!(sql, options[:conditions], scope)
+ # TODO add lock to Arel
+ arel_table(table_name).
+ join(construct_join(options[:joins], scope)).
+ where(construct_conditions(options[:conditions], scope)).
+ project(options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))).
+ group(construct_group(options[:group], options[:having], scope)).
+ order(construct_order(options[:order], scope)).
+ take(construct_limit(options, scope)).
+ skip(construct_offset(options, scope)
+ )
+ end
- add_group!(sql, options[:group], options[:having], scope)
- add_order!(sql, options[:order], scope)
- add_limit!(sql, options, scope)
- add_lock!(sql, options, scope)
+ def construct_finder_sql(options)
+ construct_finder_arel(options).to_sql
+ end
+
+ def construct_join(joins, scope = :auto)
+ merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
+ case merged_joins
+ when Symbol, Hash, Array
+ if array_of_strings?(merged_joins)
+ merged_joins.join(' ') + " "
+ else
+ join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
+ " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
+ end
+ when String
+ " #{merged_joins} "
+ else
+ ""
+ end
+ end
+ def construct_group(group, having, scope = :auto)
+ sql = ''
+ if group
+ sql << group.to_s
+ sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having
+ else
+ scope = scope(:find) if :auto == scope
+ if scope && (scoped_group = scope[:group])
+ sql << scoped_group.to_s
+ sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having]
+ end
+ end
sql
end
+ def construct_order(order, scope = :auto)
+ sql = ''
+ scoped_order = scope[:order] if scope
+ if order
+ sql << order.to_s
+ if scoped_order && scoped_order != order
+ sql << ", #{scoped_order}"
+ end
+ else
+ sql << scoped_order.to_s if scoped_order
+ end
+ sql
+ end
+
+ def construct_limit(options, scope = :auto)
+ options[:limit] ||= scope[:limit] if scope
+ options[:limit]
+ end
+
+ def construct_offset(options, scope = :auto)
+ options[:offset] ||= scope[:offset] if scope
+ options[:offset]
+ end
+
+ def construct_conditions(conditions, scope = :auto)
+ conditions = [conditions]
+ conditions << scope[:conditions] if scope
+ conditions << type_condition if finder_needs_type_condition?
+ merge_conditions(*conditions)
+ end
+
# Merges includes so that the result is a valid +include+
def merge_includes(first, second)
(safe_to_array(first) + safe_to_array(second)).uniq
@@ -1963,7 +2031,7 @@ module ActiveRecord #:nodoc:
attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
) # )
- #
+ #
scoped(:conditions => attributes) # scoped(:conditions => attributes)
end # end
}, __FILE__, __LINE__
@@ -2483,7 +2551,7 @@ module ActiveRecord #:nodoc:
# name
# end
# end
- #
+ #
# user = User.find_by_name('Phusion')
# user_path(user) # => "/users/Phusion"
def to_param
@@ -2538,12 +2606,12 @@ module ActiveRecord #:nodoc:
# If +perform_validation+ is true validations run. If any of them fail
# the action is cancelled and +save+ returns +false+. If the flag is
# false validations are bypassed altogether. See
- # ActiveRecord::Validations for more information.
+ # ActiveRecord::Validations for more information.
#
# There's a series of callbacks associated with +save+. If any of the
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
# +save+ returns +false+. See ActiveRecord::Callbacks for further
- # details.
+ # details.
def save
create_or_update
end
@@ -2555,12 +2623,12 @@ module ActiveRecord #:nodoc:
#
# With <tt>save!</tt> validations always run. If any of them fail
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
- # for more information.
+ # for more information.
#
# There's a series of callbacks associated with <tt>save!</tt>. If any of
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
- # ActiveRecord::Callbacks for further details.
+ # ActiveRecord::Callbacks for further details.
def save!
create_or_update || raise(RecordNotSaved)
end
@@ -2584,11 +2652,7 @@ module ActiveRecord #:nodoc:
# be made (since they can't be persisted).
def destroy
unless new_record?
- connection.delete(
- "DELETE FROM #{self.class.quoted_table_name} " +
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id}",
- "#{self.class.name} Destroy"
- )
+ arel_table.where(arel_table[self.class.primary_key].eq(id)).delete
end
freeze
@@ -2731,12 +2795,12 @@ module ActiveRecord #:nodoc:
# class User < ActiveRecord::Base
# attr_protected :is_admin
# end
- #
+ #
# user = User.new
# user.attributes = { :username => 'Phusion', :is_admin => true }
# user.username # => "Phusion"
# user.is_admin? # => false
- #
+ #
# user.send(:attributes=, { :username => 'Phusion', :is_admin => true }, false)
# user.is_admin? # => true
def attributes=(new_attributes, guard_protected_attributes = true)
@@ -2881,14 +2945,9 @@ module ActiveRecord #:nodoc:
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update(attribute_names = @attributes.keys)
- quoted_attributes = attributes_with_quotes(false, false, attribute_names)
- return 0 if quoted_attributes.empty?
- connection.update(
- "UPDATE #{self.class.quoted_table_name} " +
- "SET #{quoted_comma_pair_list(connection, quoted_attributes)} " +
- "WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quote_value(id)}",
- "#{self.class.name} Update"
- )
+ attributes_with_values = arel_attributes_values(false, false, attribute_names)
+ return 0 if attributes_with_values.empty?
+ arel_table.where(arel_table[self.class.primary_key].eq(id)).update(attributes_with_values)
end
# Creates a record with values matching those of the instance attributes
@@ -2998,6 +3057,25 @@ module ActiveRecord #:nodoc:
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
end
+ def arel_table
+ @arel_table ||= Arel(self.class.table_name)
+ end
+
+ def arel_attributes_values(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
+ attrs = {}
+ connection = self.class.connection
+ attribute_names.each do |name|
+ if (column = column_for_attribute(name)) && (include_primary_key || !column.primary)
+ value = read_attribute(name)
+
+ if include_readonly_attributes || (!include_readonly_attributes && !self.class.readonly_attributes.include?(name))
+ attrs[arel_table[name]] = value
+ end
+ end
+ end
+ attrs
+ end
+
# Quote strings appropriately for SQL statements.
def quote_value(value, column = nil)
self.class.connection.quote(value, column)