Instiki 0.16.5
Update to Rails 2.3.2 (the stable Rails 2.3 release). Add audio/speex support Update CHANGELOG Bump version number
This commit is contained in:
parent
801d307405
commit
e2ccdfd812
264 changed files with 4850 additions and 1906 deletions
|
@ -51,6 +51,12 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
class HasAndBelongsToManyAssociationForeignKeyNeeded < ActiveRecordError #:nodoc:
|
||||
def initialize(reflection)
|
||||
super("Cannot create self referential has_and_belongs_to_many association on '#{reflection.class_name rescue nil}##{reflection.name rescue nil}'. :association_foreign_key cannot be the same as the :foreign_key.")
|
||||
end
|
||||
end
|
||||
|
||||
class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
|
||||
def initialize(reflection)
|
||||
super("Can not eagerly load the polymorphic association #{reflection.name.inspect}")
|
||||
|
@ -65,7 +71,7 @@ module ActiveRecord
|
|||
|
||||
# See ActiveRecord::Associations::ClassMethods for documentation.
|
||||
module Associations # :nodoc:
|
||||
# These classes will be loaded when associatoins are created.
|
||||
# These classes will be loaded when associations are created.
|
||||
# So there is no need to eager load them.
|
||||
autoload :AssociationCollection, 'active_record/associations/association_collection'
|
||||
autoload :AssociationProxy, 'active_record/associations/association_proxy'
|
||||
|
@ -997,9 +1003,7 @@ module ActiveRecord
|
|||
|
||||
# Create the callbacks to update counter cache
|
||||
if options[:counter_cache]
|
||||
cache_column = options[:counter_cache] == true ?
|
||||
"#{self.to_s.demodulize.underscore.pluralize}_count" :
|
||||
options[:counter_cache]
|
||||
cache_column = reflection.counter_cache_column
|
||||
|
||||
method_name = "belongs_to_counter_cache_after_create_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
|
@ -1526,6 +1530,10 @@ module ActiveRecord
|
|||
options[:extend] = create_extension_modules(association_id, extension, options[:extend])
|
||||
|
||||
reflection = create_reflection(:has_and_belongs_to_many, association_id, options, self)
|
||||
|
||||
if reflection.association_foreign_key == reflection.primary_key_name
|
||||
raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
|
||||
end
|
||||
|
||||
reflection.options[:join_table] ||= join_table_name(undecorated_table_name(self.to_s), undecorated_table_name(reflection.class_name))
|
||||
|
||||
|
@ -1848,9 +1856,10 @@ module ActiveRecord
|
|||
def construct(parent, associations, joins, row)
|
||||
case associations
|
||||
when Symbol, String
|
||||
while (join = joins.shift).reflection.name.to_s != associations.to_s
|
||||
raise ConfigurationError, "Not Enough Associations" if joins.empty?
|
||||
end
|
||||
join = joins.detect{|j| j.reflection.name.to_s == associations.to_s && j.parent_table_name == parent.class.table_name }
|
||||
raise(ConfigurationError, "No such association") if join.nil?
|
||||
|
||||
joins.delete(join)
|
||||
construct_association(parent, join, row)
|
||||
when Array
|
||||
associations.each do |association|
|
||||
|
@ -1858,7 +1867,11 @@ module ActiveRecord
|
|||
end
|
||||
when Hash
|
||||
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
||||
association = construct_association(parent, joins.shift, row)
|
||||
join = joins.detect{|j| j.reflection.name.to_s == name.to_s && j.parent_table_name == parent.class.table_name }
|
||||
raise(ConfigurationError, "No such association") if join.nil?
|
||||
|
||||
association = construct_association(parent, join, row)
|
||||
joins.delete(join)
|
||||
construct(association, associations[name], joins, row) if association
|
||||
end
|
||||
else
|
||||
|
|
|
@ -60,7 +60,7 @@ module ActiveRecord
|
|||
@reflection.klass.find(*args)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Fetches the first one using SQL if possible.
|
||||
def first(*args)
|
||||
if fetch_first_or_last_using_find?(args)
|
||||
|
@ -143,6 +143,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
# Remove all records from this association
|
||||
#
|
||||
# See delete for more info.
|
||||
def delete_all
|
||||
load_target
|
||||
delete(@target)
|
||||
|
@ -186,7 +188,6 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
# Removes +records+ from this association calling +before_remove+ and
|
||||
# +after_remove+ callbacks.
|
||||
#
|
||||
|
@ -195,22 +196,25 @@ module ActiveRecord
|
|||
# are actually removed from the database, that depends precisely on
|
||||
# +delete_records+. They are in any case removed from the collection.
|
||||
def delete(*records)
|
||||
records = flatten_deeper(records)
|
||||
records.each { |record| raise_on_type_mismatch(record) }
|
||||
|
||||
transaction do
|
||||
records.each { |record| callback(:before_remove, record) }
|
||||
|
||||
old_records = records.reject {|r| r.new_record? }
|
||||
remove_records(records) do |records, old_records|
|
||||
delete_records(old_records) if old_records.any?
|
||||
|
||||
records.each do |record|
|
||||
@target.delete(record)
|
||||
callback(:after_remove, record)
|
||||
end
|
||||
records.each { |record| @target.delete(record) }
|
||||
end
|
||||
end
|
||||
|
||||
# Destroy +records+ and remove them from this association calling
|
||||
# +before_remove+ and +after_remove+ callbacks.
|
||||
#
|
||||
# Note that this method will _always_ remove records from the database
|
||||
# ignoring the +:dependent+ option.
|
||||
def destroy(*records)
|
||||
remove_records(records) do |records, old_records|
|
||||
old_records.each { |record| record.destroy }
|
||||
end
|
||||
|
||||
load_target
|
||||
end
|
||||
|
||||
# Removes all records from this association. Returns +self+ so method calls may be chained.
|
||||
def clear
|
||||
return self if length.zero? # forces load_target if it hasn't happened already
|
||||
|
@ -223,15 +227,16 @@ module ActiveRecord
|
|||
|
||||
self
|
||||
end
|
||||
|
||||
def destroy_all
|
||||
transaction do
|
||||
each { |record| record.destroy }
|
||||
end
|
||||
|
||||
# Destory all the records from this association.
|
||||
#
|
||||
# See destroy for more info.
|
||||
def destroy_all
|
||||
load_target
|
||||
destroy(@target)
|
||||
reset_target!
|
||||
end
|
||||
|
||||
|
||||
def create(attrs = {})
|
||||
if attrs.is_a?(Array)
|
||||
attrs.collect { |attr| create(attr) }
|
||||
|
@ -431,6 +436,18 @@ module ActiveRecord
|
|||
record
|
||||
end
|
||||
|
||||
def remove_records(*records)
|
||||
records = flatten_deeper(records)
|
||||
records.each { |record| raise_on_type_mismatch(record) }
|
||||
|
||||
transaction do
|
||||
records.each { |record| callback(:before_remove, record) }
|
||||
old_records = records.reject { |r| r.new_record? }
|
||||
yield(records, old_records)
|
||||
records.each { |record| callback(:after_remove, record) }
|
||||
end
|
||||
end
|
||||
|
||||
def callback(method, record)
|
||||
callbacks_for(method).each do |callback|
|
||||
ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record)
|
||||
|
|
|
@ -23,8 +23,8 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
|
||||
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero
|
||||
# and you need to fetch that collection afterwards, it'll take one less SELECT query if you use length.
|
||||
# calling collection.size if it has. If it's more likely than not that the collection does have a size larger than zero,
|
||||
# and you need to fetch that collection afterwards, it'll take one fewer SELECT query if you use #length.
|
||||
def size
|
||||
return @owner.send(:read_attribute, cached_counter_attribute_name) if has_cached_counter?
|
||||
return @target.size if loaded?
|
||||
|
@ -150,7 +150,7 @@ module ActiveRecord
|
|||
end
|
||||
else
|
||||
reflection_primary_key = @reflection.source_reflection.primary_key_name
|
||||
source_primary_key = @reflection.klass.primary_key
|
||||
source_primary_key = @reflection.through_reflection.klass.primary_key
|
||||
if @reflection.source_reflection.options[:as]
|
||||
polymorphic_join = "AND %s.%s = %s" % [
|
||||
@reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
|
||||
|
|
|
@ -29,8 +29,17 @@ module ActiveRecord
|
|||
|
||||
unless @target.nil? || @target == obj
|
||||
if dependent? && !dont_save
|
||||
@target.destroy unless @target.new_record?
|
||||
@owner.clear_association_cache
|
||||
case @reflection.options[:dependent]
|
||||
when :delete
|
||||
@target.delete unless @target.new_record?
|
||||
@owner.clear_association_cache
|
||||
when :destroy
|
||||
@target.destroy unless @target.new_record?
|
||||
@owner.clear_association_cache
|
||||
when :nullify
|
||||
@target[@reflection.primary_key_name] = nil
|
||||
@target.save unless @owner.new_record? || @target.new_record?
|
||||
end
|
||||
else
|
||||
@target[@reflection.primary_key_name] = nil
|
||||
@target.save unless @owner.new_record? || @target.new_record?
|
||||
|
|
|
@ -283,7 +283,7 @@ module ActiveRecord
|
|||
if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
|
||||
records.each do |record|
|
||||
if autosave && record.marked_for_destruction?
|
||||
record.destroy
|
||||
association.destroy(record)
|
||||
elsif @new_record_before_save || record.new_record?
|
||||
if autosave
|
||||
association.send(:insert_record, record, false, false)
|
||||
|
@ -310,7 +310,7 @@ module ActiveRecord
|
|||
# This all happens inside a transaction, _if_ the Transactions module is included into
|
||||
# ActiveRecord::Base after the AutosaveAssociation module, which it does by default.
|
||||
def save_has_one_association(reflection)
|
||||
if association = association_instance_get(reflection.name)
|
||||
if (association = association_instance_get(reflection.name)) && !association.target.nil?
|
||||
if reflection.options[:autosave] && association.marked_for_destruction?
|
||||
association.destroy
|
||||
elsif new_record? || association.new_record? || association[reflection.primary_key_name] != id || reflection.options[:autosave]
|
||||
|
|
|
@ -416,7 +416,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
@@subclasses = {}
|
||||
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Contains the database configuration - as is typically stored in config/database.yml -
|
||||
|
@ -661,7 +661,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:
|
||||
#
|
||||
|
@ -737,12 +736,12 @@ module ActiveRecord #:nodoc:
|
|||
# ==== 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.
|
||||
# * +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' })
|
||||
# Person.update(15, :user_name => 'Samuel', :group => 'expert')
|
||||
#
|
||||
# # Updating multiple records:
|
||||
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
|
||||
|
@ -1003,7 +1002,6 @@ module ActiveRecord #:nodoc:
|
|||
update_counters(id, counter_name => -1)
|
||||
end
|
||||
|
||||
|
||||
# Attributes named in this macro are protected from mass-assignment,
|
||||
# such as <tt>new(attributes)</tt>,
|
||||
# <tt>update_attributes(attributes)</tt>, or
|
||||
|
@ -1104,7 +1102,6 @@ module ActiveRecord #:nodoc:
|
|||
read_inheritable_attribute(:attr_serialized) or write_inheritable_attribute(:attr_serialized, {})
|
||||
end
|
||||
|
||||
|
||||
# Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
|
||||
# directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
|
||||
# to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
|
||||
|
@ -1347,7 +1344,7 @@ module ActiveRecord #:nodoc:
|
|||
subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
|
||||
end
|
||||
|
||||
def self_and_descendents_from_active_record#nodoc:
|
||||
def self_and_descendants_from_active_record#nodoc:
|
||||
klass = self
|
||||
classes = [klass]
|
||||
while klass != klass.base_class
|
||||
|
@ -1367,7 +1364,7 @@ module ActiveRecord #:nodoc:
|
|||
# module now.
|
||||
# Specify +options+ with additional translating options.
|
||||
def human_attribute_name(attribute_key_name, options = {})
|
||||
defaults = self_and_descendents_from_active_record.map do |klass|
|
||||
defaults = self_and_descendants_from_active_record.map do |klass|
|
||||
:"#{klass.name.underscore}.#{attribute_key_name}"
|
||||
end
|
||||
defaults << options[:default] if options[:default]
|
||||
|
@ -1382,7 +1379,7 @@ module ActiveRecord #:nodoc:
|
|||
# Default scope of the translation is activerecord.models
|
||||
# Specify +options+ with additional translating options.
|
||||
def human_name(options = {})
|
||||
defaults = self_and_descendents_from_active_record.map do |klass|
|
||||
defaults = self_and_descendants_from_active_record.map do |klass|
|
||||
:"#{klass.name.underscore}"
|
||||
end
|
||||
defaults << self.name.humanize
|
||||
|
@ -1417,7 +1414,6 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def quote_value(value, column = nil) #:nodoc:
|
||||
connection.quote(value,column)
|
||||
end
|
||||
|
@ -1486,7 +1482,7 @@ module ActiveRecord #:nodoc:
|
|||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
end
|
||||
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -1537,7 +1533,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def reverse_sql_order(order_query)
|
||||
reversed_query = order_query.split(/,/).each { |s|
|
||||
reversed_query = order_query.to_s.split(/,/).each { |s|
|
||||
if s.match(/\s(asc|ASC)$/)
|
||||
s.gsub!(/\s(asc|ASC)$/, ' DESC')
|
||||
elsif s.match(/\s(desc|DESC)$/)
|
||||
|
@ -1690,7 +1686,7 @@ module ActiveRecord #:nodoc:
|
|||
def construct_finder_sql(options)
|
||||
scope = scope(:find)
|
||||
sql = "SELECT #{options[:select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
|
||||
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
||||
sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
|
||||
|
||||
add_joins!(sql, options[:joins], scope)
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
|
@ -1745,7 +1741,9 @@ module ActiveRecord #:nodoc:
|
|||
scoped_order = scope[:order] if scope
|
||||
if order
|
||||
sql << " ORDER BY #{order}"
|
||||
sql << ", #{scoped_order}" if scoped_order
|
||||
if scoped_order && scoped_order != order
|
||||
sql << ", #{scoped_order}"
|
||||
end
|
||||
else
|
||||
sql << " ORDER BY #{scoped_order}" if scoped_order
|
||||
end
|
||||
|
@ -1754,12 +1752,12 @@ module ActiveRecord #:nodoc:
|
|||
def add_group!(sql, group, having, scope = :auto)
|
||||
if group
|
||||
sql << " GROUP BY #{group}"
|
||||
sql << " HAVING #{having}" if having
|
||||
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 #{scope[:having]}" if scope[:having]
|
||||
sql << " HAVING #{sanitize_sql_for_conditions(scope[:having])}" if scope[:having]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -2014,7 +2012,6 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
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
|
||||
|
@ -2111,7 +2108,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
# Merge scopings
|
||||
if action == :merge && current_scoped_methods
|
||||
if [:merge, :reverse_merge].include?(action) && current_scoped_methods
|
||||
method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
|
||||
case hash[method]
|
||||
when Hash
|
||||
|
@ -2133,7 +2130,11 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
else
|
||||
hash[method] = hash[method].merge(params)
|
||||
if action == :reverse_merge
|
||||
hash[method] = hash[method].merge(params)
|
||||
else
|
||||
hash[method] = params.merge(hash[method])
|
||||
end
|
||||
end
|
||||
else
|
||||
hash[method] = params
|
||||
|
@ -2143,7 +2144,6 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
self.scoped_methods << method_scoping
|
||||
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
|
@ -2174,7 +2174,7 @@ module ActiveRecord #:nodoc:
|
|||
# Test whether the given method and optional key are scoped.
|
||||
def scoped?(method, key = nil) #:nodoc:
|
||||
if current_scoped_methods && (scope = current_scoped_methods[method])
|
||||
!key || scope.has_key?(key)
|
||||
!key || !scope[key].nil?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2749,7 +2749,6 @@ module ActiveRecord #:nodoc:
|
|||
assign_multiparameter_attributes(multi_parameter_attributes)
|
||||
end
|
||||
|
||||
|
||||
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
||||
def attributes
|
||||
self.attribute_names.inject({}) do |attrs, name|
|
||||
|
|
|
@ -4,21 +4,24 @@ module ActiveRecord
|
|||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# When processing large numbers of records, it's often a good idea to do so in batches to prevent memory ballooning.
|
||||
# When processing large numbers of records, it's often a good idea to do
|
||||
# so in batches to prevent memory ballooning.
|
||||
module ClassMethods
|
||||
# Yields each record that was found by the find +options+. The find is performed by find_in_batches
|
||||
# with a batch size of 1000 (or as specified by the +batch_size+ option).
|
||||
# Yields each record that was found by the find +options+. The find is
|
||||
# performed by find_in_batches with a batch size of 1000 (or as
|
||||
# specified by the <tt>:batch_size</tt> option).
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Person.each(:conditions => "age > 21") do |person|
|
||||
# Person.find_each(:conditions => "age > 21") do |person|
|
||||
# person.party_all_night!
|
||||
# end
|
||||
#
|
||||
# Note: This method is only intended to use for batch processing of large amounts of records that wouldn't fit in
|
||||
# memory all at once. If you just need to loop over less than 1000 records, it's probably better just to use the
|
||||
# regular find methods.
|
||||
def each(options = {})
|
||||
# Note: This method is only intended to use for batch processing of
|
||||
# large amounts of records that wouldn't fit in memory all at once. If
|
||||
# you just need to loop over less than 1000 records, it's probably
|
||||
# better just to use the regular find methods.
|
||||
def find_each(options = {})
|
||||
find_in_batches(options) do |records|
|
||||
records.each { |record| yield record }
|
||||
end
|
||||
|
@ -26,17 +29,22 @@ module ActiveRecord
|
|||
self
|
||||
end
|
||||
|
||||
# Yields each batch of records that was found by the find +options+ as an array. The size of each batch is
|
||||
# set by the +batch_size+ option; the default is 1000.
|
||||
# Yields each batch of records that was found by the find +options+ as
|
||||
# an array. The size of each batch is set by the <tt>:batch_size</tt>
|
||||
# option; the default is 1000.
|
||||
#
|
||||
# You can control the starting point for the batch processing by supplying the +start+ option. This is especially
|
||||
# useful if you want multiple workers dealing with the same processing queue. You can make worker 1 handle all the
|
||||
# records between id 0 and 10,000 and worker 2 handle from 10,000 and beyond (by setting the +start+ option on that
|
||||
# worker).
|
||||
# You can control the starting point for the batch processing by
|
||||
# supplying the <tt>:start</tt> option. This is especially useful if you
|
||||
# want multiple workers dealing with the same processing queue. You can
|
||||
# make worker 1 handle all the records between id 0 and 10,000 and
|
||||
# worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
|
||||
# option on that worker).
|
||||
#
|
||||
# It's not possible to set the order. That is automatically set to ascending on the primary key ("id ASC")
|
||||
# to make the batch ordering work. This also mean that this method only works with integer-based primary keys.
|
||||
# You can't set the limit either, that's used to control the the batch sizes.
|
||||
# It's not possible to set the order. That is automatically set to
|
||||
# ascending on the primary key ("id ASC") to make the batch ordering
|
||||
# work. This also mean that this method only works with integer-based
|
||||
# primary keys. You can't set the limit either, that's used to control
|
||||
# the the batch sizes.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
|
@ -49,12 +57,15 @@ module ActiveRecord
|
|||
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit]
|
||||
|
||||
start = options.delete(:start).to_i
|
||||
batch_size = options.delete(:batch_size) || 1000
|
||||
|
||||
with_scope(:find => options.merge(:order => batch_order, :limit => options.delete(:batch_size) || 1000)) do
|
||||
with_scope(:find => options.merge(:order => batch_order, :limit => batch_size)) do
|
||||
records = find(:all, :conditions => [ "#{table_name}.#{primary_key} >= ?", start ])
|
||||
|
||||
while records.any?
|
||||
yield records
|
||||
|
||||
break if records.size < batch_size
|
||||
records = find(:all, :conditions => [ "#{table_name}.#{primary_key} > ?", records.last.id ])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -141,22 +141,30 @@ module ActiveRecord
|
|||
def construct_count_options_from_args(*args)
|
||||
options = {}
|
||||
column_name = :all
|
||||
|
||||
|
||||
# We need to handle
|
||||
# count()
|
||||
# count(:column_name=:all)
|
||||
# count(options={})
|
||||
# count(column_name=:all, options={})
|
||||
# selects specified by scopes
|
||||
case args.size
|
||||
when 0
|
||||
column_name = scope(:find)[:select] if scope(:find)
|
||||
when 1
|
||||
args[0].is_a?(Hash) ? options = args[0] : column_name = args[0]
|
||||
if args[0].is_a?(Hash)
|
||||
column_name = scope(:find)[:select] if scope(:find)
|
||||
options = args[0]
|
||||
else
|
||||
column_name = args[0]
|
||||
end
|
||||
when 2
|
||||
column_name, options = args
|
||||
else
|
||||
raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
|
||||
end if args.size > 0
|
||||
|
||||
[column_name, options]
|
||||
end
|
||||
|
||||
[column_name || :all, options]
|
||||
end
|
||||
|
||||
def construct_calculation_sql(operation, column_name, options) #:nodoc:
|
||||
|
@ -214,13 +222,15 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
if options[:group] && options[:having]
|
||||
having = sanitize_sql_for_conditions(options[:having])
|
||||
|
||||
# FrontBase requires identifiers in the HAVING clause and chokes on function calls
|
||||
if connection.adapter_name == 'FrontBase'
|
||||
options[:having].downcase!
|
||||
options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
||||
having.downcase!
|
||||
having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
||||
end
|
||||
|
||||
sql << " HAVING #{options[:having]} "
|
||||
sql << " HAVING #{having} "
|
||||
end
|
||||
|
||||
sql << " ORDER BY #{options[:order]} " if options[:order]
|
||||
|
|
|
@ -18,7 +18,7 @@ module ActiveRecord
|
|||
|
||||
db.busy_timeout(config[:timeout]) unless config[:timeout].nil?
|
||||
|
||||
ConnectionAdapters::SQLite3Adapter.new(db, logger)
|
||||
ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -17,9 +17,9 @@ module ActiveRecord
|
|||
|
||||
# "Downgrade" deprecated sqlite API
|
||||
if SQLite.const_defined?(:Version)
|
||||
ConnectionAdapters::SQLite2Adapter.new(db, logger)
|
||||
ConnectionAdapters::SQLite2Adapter.new(db, logger, config)
|
||||
else
|
||||
ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger)
|
||||
ConnectionAdapters::DeprecatedSQLiteAdapter.new(db, logger, config)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -72,10 +72,31 @@ module ActiveRecord
|
|||
#
|
||||
# * <tt>:database</tt> - Path to the database file.
|
||||
class SQLiteAdapter < AbstractAdapter
|
||||
class Version
|
||||
include Comparable
|
||||
|
||||
def initialize(version_string)
|
||||
@version = version_string.split('.').map(&:to_i)
|
||||
end
|
||||
|
||||
def <=>(version_string)
|
||||
@version <=> version_string.split('.').map(&:to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(connection, logger, config)
|
||||
super(connection, logger)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def adapter_name #:nodoc:
|
||||
'SQLite'
|
||||
end
|
||||
|
||||
def supports_ddl_transactions?
|
||||
sqlite_version >= '2.0.0'
|
||||
end
|
||||
|
||||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
@ -83,6 +104,10 @@ module ActiveRecord
|
|||
def requires_reloading?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_add_column?
|
||||
sqlite_version >= '3.1.6'
|
||||
end
|
||||
|
||||
def disconnect!
|
||||
super
|
||||
|
@ -164,7 +189,6 @@ module ActiveRecord
|
|||
catch_schema_changes { @connection.rollback }
|
||||
end
|
||||
|
||||
|
||||
# SELECT ... FOR UPDATE is redundant since the table is locked.
|
||||
def add_lock!(sql, options) #:nodoc:
|
||||
sql
|
||||
|
@ -213,14 +237,20 @@ module ActiveRecord
|
|||
execute "ALTER TABLE #{name} RENAME TO #{new_name}"
|
||||
end
|
||||
|
||||
# See: http://www.sqlite.org/lang_altertable.html
|
||||
# SQLite has an additional restriction on the ALTER TABLE statement
|
||||
def valid_alter_table_options( type, options)
|
||||
type.to_sym != :primary_key
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
|
||||
raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
|
||||
if supports_add_column? && valid_alter_table_options( type, options )
|
||||
super(table_name, column_name, type, options)
|
||||
else
|
||||
alter_table(table_name) do |definition|
|
||||
definition.column(column_name, type, options)
|
||||
end
|
||||
end
|
||||
|
||||
super(table_name, column_name, type, options)
|
||||
# See last paragraph on http://www.sqlite.org/lang_altertable.html
|
||||
execute "VACUUM"
|
||||
end
|
||||
|
||||
def remove_column(table_name, *column_names) #:nodoc:
|
||||
|
@ -380,7 +410,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def sqlite_version
|
||||
@sqlite_version ||= select_value('select sqlite_version(*)')
|
||||
@sqlite_version ||= SQLiteAdapter::Version.new(select_value('select sqlite_version(*)'))
|
||||
end
|
||||
|
||||
def default_primary_key_type
|
||||
|
@ -393,23 +423,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
class SQLite2Adapter < SQLiteAdapter # :nodoc:
|
||||
def supports_count_distinct? #:nodoc:
|
||||
false
|
||||
end
|
||||
|
||||
def rename_table(name, new_name)
|
||||
move_table(name, new_name)
|
||||
end
|
||||
|
||||
def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
if @connection.respond_to?(:transaction_active?) && @connection.transaction_active?
|
||||
raise StatementInvalid, 'Cannot add columns to a SQLite database while inside a transaction'
|
||||
end
|
||||
|
||||
alter_table(table_name) do |definition|
|
||||
definition.column(column_name, type, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DeprecatedSQLiteAdapter < SQLite2Adapter # :nodoc:
|
||||
|
|
|
@ -23,6 +23,16 @@ module ActiveRecord
|
|||
# p2.first_name = "should fail"
|
||||
# p2.save # Raises a ActiveRecord::StaleObjectError
|
||||
#
|
||||
# Optimistic locking will also check for stale data when objects are destroyed. Example:
|
||||
#
|
||||
# p1 = Person.find(1)
|
||||
# p2 = Person.find(1)
|
||||
#
|
||||
# p1.first_name = "Michael"
|
||||
# p1.save
|
||||
#
|
||||
# p2.destroy # Raises a ActiveRecord::StaleObjectError
|
||||
#
|
||||
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
||||
# or otherwise apply the business logic needed to resolve the conflict.
|
||||
#
|
||||
|
@ -39,6 +49,7 @@ module ActiveRecord
|
|||
base.lock_optimistically = true
|
||||
|
||||
base.alias_method_chain :update, :lock
|
||||
base.alias_method_chain :destroy, :lock
|
||||
base.alias_method_chain :attributes_from_column_definition, :lock
|
||||
|
||||
class << base
|
||||
|
@ -98,6 +109,28 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def destroy_with_lock #:nodoc:
|
||||
return destroy_without_lock unless locking_enabled?
|
||||
|
||||
unless new_record?
|
||||
lock_col = self.class.locking_column
|
||||
previous_value = send(lock_col).to_i
|
||||
|
||||
affected_rows = connection.delete(
|
||||
"DELETE FROM #{self.class.quoted_table_name} " +
|
||||
"WHERE #{connection.quote_column_name(self.class.primary_key)} = #{quoted_id} " +
|
||||
"AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}",
|
||||
"#{self.class.name} Destroy"
|
||||
)
|
||||
|
||||
unless affected_rows == 1
|
||||
raise ActiveRecord::StaleObjectError, "Attempted to delete a stale object"
|
||||
end
|
||||
end
|
||||
|
||||
freeze
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
DEFAULT_LOCKING_COLUMN = 'lock_version'
|
||||
|
||||
|
|
|
@ -89,7 +89,12 @@ module ActiveRecord
|
|||
when Hash
|
||||
options
|
||||
when Proc
|
||||
options.call(*args)
|
||||
case parent_scope
|
||||
when Scope
|
||||
with_scope(:find => parent_scope.proxy_options) { options.call(*args) }
|
||||
else
|
||||
options.call(*args)
|
||||
end
|
||||
end, &block)
|
||||
end
|
||||
(class << self; self end).instance_eval do
|
||||
|
@ -99,7 +104,7 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class Scope
|
||||
attr_reader :proxy_scope, :proxy_options, :current_scoped_methods_when_defined
|
||||
NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?).to_set
|
||||
|
@ -112,6 +117,7 @@ module ActiveRecord
|
|||
delegate :scopes, :with_scope, :to => :proxy_scope
|
||||
|
||||
def initialize(proxy_scope, options, &block)
|
||||
options ||= {}
|
||||
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
|
||||
extend Module.new(&block) if block_given?
|
||||
unless Scope === proxy_scope
|
||||
|
@ -170,7 +176,7 @@ module ActiveRecord
|
|||
if scopes.include?(method)
|
||||
scopes[method].call(self, *args)
|
||||
else
|
||||
with_scope :find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {} do
|
||||
with_scope({:find => proxy_options, :create => proxy_options[:conditions].is_a?(Hash) ? proxy_options[:conditions] : {}}, :reverse_merge) do
|
||||
method = :new if method == :build
|
||||
if current_scoped_methods_when_defined
|
||||
with_scope current_scoped_methods_when_defined do
|
||||
|
|
|
@ -197,7 +197,7 @@ module ActiveRecord
|
|||
|
||||
def counter_cache_column
|
||||
if options[:counter_cache] == true
|
||||
"#{active_record.name.underscore.pluralize}_count"
|
||||
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
||||
elsif options[:counter_cache]
|
||||
options[:counter_cache]
|
||||
end
|
||||
|
|
|
@ -231,16 +231,22 @@ module ActiveRecord #:nodoc:
|
|||
def add_associations(association, records, opts)
|
||||
if records.is_a?(Enumerable)
|
||||
tag = reformat_name(association.to_s)
|
||||
type = options[:skip_types] ? {} : {:type => "array"}
|
||||
|
||||
if records.empty?
|
||||
builder.tag!(tag, :type => :array)
|
||||
builder.tag!(tag, type)
|
||||
else
|
||||
builder.tag!(tag, :type => :array) do
|
||||
builder.tag!(tag, type) do
|
||||
association_name = association.to_s.singularize
|
||||
records.each do |record|
|
||||
record.to_xml opts.merge(
|
||||
:root => association_name,
|
||||
:type => (record.class.to_s.underscore == association_name ? nil : record.class.name)
|
||||
)
|
||||
if options[:skip_types]
|
||||
record_type = {}
|
||||
else
|
||||
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
|
||||
record_type = {:type => record_class}
|
||||
end
|
||||
|
||||
record.to_xml opts.merge(:root => association_name).merge(record_type)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,5 +49,18 @@ module ActiveRecord
|
|||
ActiveRecord::Base.clear_all_connections!
|
||||
ActiveRecord::Base.establish_connection(@connection)
|
||||
end
|
||||
|
||||
def with_kcode(kcode)
|
||||
if RUBY_VERSION < '1.9'
|
||||
orig_kcode, $KCODE = $KCODE, kcode
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
$KCODE = orig_kcode
|
||||
end
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -89,7 +89,7 @@ module ActiveRecord
|
|||
|
||||
message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)
|
||||
|
||||
defaults = @base.class.self_and_descendents_from_active_record.map do |klass|
|
||||
defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
|
||||
[ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}",
|
||||
:"models.#{klass.name.underscore}.#{message}" ]
|
||||
end
|
||||
|
@ -720,20 +720,20 @@ module ActiveRecord
|
|||
# class (which has a database table to query from).
|
||||
finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? }
|
||||
|
||||
is_text_column = finder_class.columns_hash[attr_name.to_s].text?
|
||||
column = finder_class.columns_hash[attr_name.to_s]
|
||||
|
||||
if value.nil?
|
||||
comparison_operator = "IS ?"
|
||||
elsif is_text_column
|
||||
elsif column.text?
|
||||
comparison_operator = "#{connection.case_sensitive_equality_operator} ?"
|
||||
value = value.to_s
|
||||
value = column.limit ? value.to_s[0, column.limit] : value.to_s
|
||||
else
|
||||
comparison_operator = "= ?"
|
||||
end
|
||||
|
||||
sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}"
|
||||
|
||||
if value.nil? || (configuration[:case_sensitive] || !is_text_column)
|
||||
if value.nil? || (configuration[:case_sensitive] || !column.text?)
|
||||
condition_sql = "#{sql_attribute} #{comparison_operator}"
|
||||
condition_params = [value]
|
||||
else
|
||||
|
@ -802,7 +802,7 @@ module ActiveRecord
|
|||
# Validates whether the value of the specified attribute is available in a particular enumerable object.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!"
|
||||
# validates_inclusion_of :gender, :in => %w( m f )
|
||||
# validates_inclusion_of :age, :in => 0..99
|
||||
# validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension {{value}} is not included in the list"
|
||||
# end
|
||||
|
@ -1040,6 +1040,11 @@ module ActiveRecord
|
|||
errors.empty?
|
||||
end
|
||||
|
||||
# Performs the opposite of <tt>valid?</tt>. Returns true if errors were added, false otherwise.
|
||||
def invalid?
|
||||
!valid?
|
||||
end
|
||||
|
||||
# Returns the Errors object that holds all information about attribute error messages.
|
||||
def errors
|
||||
@errors ||= Errors.new(self)
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActiveRecord
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 1
|
||||
TINY = 2
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue