diff options
Diffstat (limited to 'activerecord/lib/active_record/base.rb')
-rwxr-xr-x | activerecord/lib/active_record/base.rb | 244 |
1 files changed, 127 insertions, 117 deletions
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 39fac0b7e7..2453d38aca 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -401,7 +401,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+. @@ -435,11 +435,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 @@ -840,26 +840,23 @@ module ActiveRecord #:nodoc: # # 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 = {}) - sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} " - scope = scope(:find) - select_sql = "" - add_conditions!(select_sql, conditions, scope) + arel = arel_table + + if conditions = construct_conditions(conditions, scope) + arel = arel.where(Arel::SqlLiteral.new(conditions)) + end if options.has_key?(:limit) || (scope && scope[:limit]) # Only take order from scope if limit is also provided by scope, this # is useful for updating a has_many association with a limit. - add_order!(select_sql, options[:order], scope) - - add_limit!(select_sql, options, scope) - sql.concat(connection.limited_update_conditions(select_sql, quoted_table_name, connection.quote_column_name(primary_key))) + arel = arel.order(construct_order(options[:order], scope)).take(construct_limit(options, scope)) else - add_order!(select_sql, options[:order], nil) - sql.concat(select_sql) + arel = arel.order(construct_order(options[:order], nil)) end - connection.update(sql, "#{name} Update") + arel.update(sanitize_sql_for_assignment(updates)) end # Destroys the records matching +conditions+ by instantiating each @@ -910,9 +907,11 @@ 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") + if conditions + arel_table.where(Arel::SqlLiteral.new(construct_conditions(conditions, scope(:find)))).delete + else + arel_table.delete + end end # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part. @@ -1085,7 +1084,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, @@ -1357,7 +1356,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 @@ -1391,7 +1390,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 @@ -1693,22 +1692,89 @@ module ActiveRecord #:nodoc: end end - def construct_finder_sql(options) + def arel_table(table = table_name) + @arel_table = Arel::Table.new(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 + + def construct_finder_sql(options) + construct_finder_arel(options).to_sql + 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_join(joins, scope) + 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) + sql = '' + if group + sql << group.to_s + sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having + else + 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) + orders = [] + scoped_order = scope[:order] if scope + if order + orders << order + orders << scoped_order if scoped_order && scoped_order != order + else + orders << scoped_order if scoped_order + end + orders + end + + def construct_limit(options, scope) + options[:limit] ||= scope[:limit] if scope + options[:limit] + end + + def construct_offset(options, scope) + options[:offset] ||= scope[:offset] if scope + options[:offset] + end + + def construct_conditions(conditions, scope) + 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 @@ -1746,44 +1812,6 @@ module ActiveRecord #:nodoc: o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)} end - def add_order!(sql, order, scope = :auto) - scope = scope(:find) if :auto == scope - scoped_order = scope[:order] if scope - if order - sql << " ORDER BY #{order}" - if scoped_order && scoped_order != order - sql << ", #{scoped_order}" - end - else - sql << " ORDER BY #{scoped_order}" if scoped_order - end - end - - def add_group!(sql, group, having, scope = :auto) - if group - sql << " GROUP BY #{group}" - sql << " HAVING #{sanitize_sql_for_conditions(having)}" if having - else - scope = scope(:find) if :auto == scope - if scope && (scoped_group = scope[:group]) - sql << " GROUP BY #{scoped_group}" - sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having] - end - end - end - - # The optional scope argument is for the current <tt>:find</tt> scope. - def add_limit!(sql, options, scope = :auto) - scope = scope(:find) if :auto == scope - - if scope - options[:limit] ||= scope[:limit] - options[:offset] ||= scope[:offset] - end - - connection.add_limit_offset!(sql, options) - end - # The optional scope argument is for the current <tt>:find</tt> scope. # The <tt>:lock</tt> option has precedence over a scoped <tt>:lock</tt>. def add_lock!(sql, options, scope = :auto) @@ -1792,38 +1820,10 @@ module ActiveRecord #:nodoc: connection.add_lock!(sql, options) end - # The optional scope argument is for the current <tt>:find</tt> scope. - def add_joins!(sql, joins, scope = :auto) - scope = scope(:find) if :auto == scope - 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) - sql << merged_joins.join(' ') + " " - else - join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil) - sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} " - end - when String - sql << " #{merged_joins} " - end - end - - # Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed. - # The optional scope argument is for the current <tt>:find</tt> scope. - def add_conditions!(sql, conditions, scope = :auto) - scope = scope(:find) if :auto == scope - conditions = [conditions] - conditions << scope[:conditions] if scope - conditions << type_condition if finder_needs_type_condition? - merged_conditions = merge_conditions(*conditions) - sql << "WHERE #{merged_conditions} " unless merged_conditions.blank? - end - def type_condition(table_alias=nil) quoted_table_alias = self.connection.quote_table_name(table_alias || table_name) quoted_inheritance_column = connection.quote_column_name(inheritance_column) - type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass| + type_condition = subclasses.inject("#{quoted_table_alias}.#{quoted_inheritance_column} = '#{sti_name}' " ) do |condition, subclass| condition << "OR #{quoted_table_alias}.#{quoted_inheritance_column} = '#{subclass.sti_name}' " end @@ -1964,7 +1964,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__ @@ -2484,7 +2484,7 @@ module ActiveRecord #:nodoc: # name # end # end - # + # # user = User.find_by_name('Phusion') # user_path(user) # => "/users/Phusion" def to_param @@ -2539,12 +2539,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 @@ -2556,12 +2556,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 @@ -2585,11 +2585,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 @@ -2732,12 +2728,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) @@ -2882,14 +2878,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 @@ -2999,6 +2990,25 @@ module ActiveRecord #:nodoc: include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) end + def arel_table + @arel_table = Arel::Table.new(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.is_a?(Hash) ? value.to_yaml : value + end + end + end + attrs + end + # Quote strings appropriately for SQL statements. def quote_value(value, column = nil) self.class.connection.quote(value, column) |