Instiki 0.16.3: Rails 2.3.0
Instiki now runs on the Rails 2.3.0 Candidate Release. Among other improvements, this means that it now automagically selects between WEBrick and Mongrel. Just run ./instiki --daemon
This commit is contained in:
parent
43aadecc99
commit
4e14ccc74d
893 changed files with 71965 additions and 28511 deletions
90
vendor/rails/activerecord/lib/active_record.rb
vendored
90
vendor/rails/activerecord/lib/active_record.rb
vendored
|
@ -1,5 +1,5 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2008 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2009 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -31,51 +31,53 @@ rescue LoadError
|
|||
end
|
||||
end
|
||||
|
||||
require 'active_record/base'
|
||||
require 'active_record/named_scope'
|
||||
require 'active_record/observer'
|
||||
require 'active_record/query_cache'
|
||||
require 'active_record/validations'
|
||||
require 'active_record/callbacks'
|
||||
require 'active_record/reflection'
|
||||
require 'active_record/associations'
|
||||
require 'active_record/association_preload'
|
||||
require 'active_record/aggregations'
|
||||
require 'active_record/transactions'
|
||||
require 'active_record/timestamp'
|
||||
require 'active_record/locking/optimistic'
|
||||
require 'active_record/locking/pessimistic'
|
||||
require 'active_record/migration'
|
||||
require 'active_record/schema'
|
||||
require 'active_record/calculations'
|
||||
require 'active_record/serialization'
|
||||
require 'active_record/attribute_methods'
|
||||
require 'active_record/dirty'
|
||||
require 'active_record/dynamic_finder_match'
|
||||
module ActiveRecord
|
||||
# TODO: Review explicit loads to see if they will automatically be handled by the initilizer.
|
||||
def self.load_all!
|
||||
[Base, DynamicFinderMatch, ConnectionAdapters::AbstractAdapter]
|
||||
end
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
extend ActiveRecord::QueryCache
|
||||
include ActiveRecord::Validations
|
||||
include ActiveRecord::Locking::Optimistic
|
||||
include ActiveRecord::Locking::Pessimistic
|
||||
include ActiveRecord::AttributeMethods
|
||||
include ActiveRecord::Dirty
|
||||
include ActiveRecord::Callbacks
|
||||
include ActiveRecord::Observing
|
||||
include ActiveRecord::Timestamp
|
||||
include ActiveRecord::Associations
|
||||
include ActiveRecord::NamedScope
|
||||
include ActiveRecord::AssociationPreload
|
||||
include ActiveRecord::Aggregations
|
||||
include ActiveRecord::Transactions
|
||||
include ActiveRecord::Reflection
|
||||
include ActiveRecord::Calculations
|
||||
include ActiveRecord::Serialization
|
||||
autoload :VERSION, 'active_record/version'
|
||||
|
||||
autoload :ActiveRecordError, 'active_record/base'
|
||||
autoload :ConnectionNotEstablished, 'active_record/base'
|
||||
|
||||
autoload :Aggregations, 'active_record/aggregations'
|
||||
autoload :AssociationPreload, 'active_record/association_preload'
|
||||
autoload :Associations, 'active_record/associations'
|
||||
autoload :AttributeMethods, 'active_record/attribute_methods'
|
||||
autoload :AutosaveAssociation, 'active_record/autosave_association'
|
||||
autoload :Base, 'active_record/base'
|
||||
autoload :Calculations, 'active_record/calculations'
|
||||
autoload :Callbacks, 'active_record/callbacks'
|
||||
autoload :Dirty, 'active_record/dirty'
|
||||
autoload :DynamicFinderMatch, 'active_record/dynamic_finder_match'
|
||||
autoload :DynamicScopeMatch, 'active_record/dynamic_scope_match'
|
||||
autoload :Migration, 'active_record/migration'
|
||||
autoload :Migrator, 'active_record/migration'
|
||||
autoload :NamedScope, 'active_record/named_scope'
|
||||
autoload :NestedAttributes, 'active_record/nested_attributes'
|
||||
autoload :Observing, 'active_record/observer'
|
||||
autoload :QueryCache, 'active_record/query_cache'
|
||||
autoload :Reflection, 'active_record/reflection'
|
||||
autoload :Schema, 'active_record/schema'
|
||||
autoload :SchemaDumper, 'active_record/schema_dumper'
|
||||
autoload :Serialization, 'active_record/serialization'
|
||||
autoload :SessionStore, 'active_record/session_store'
|
||||
autoload :TestCase, 'active_record/test_case'
|
||||
autoload :Timestamp, 'active_record/timestamp'
|
||||
autoload :Transactions, 'active_record/transactions'
|
||||
autoload :Validations, 'active_record/validations'
|
||||
|
||||
module Locking
|
||||
autoload :Optimistic, 'active_record/locking/optimistic'
|
||||
autoload :Pessimistic, 'active_record/locking/pessimistic'
|
||||
end
|
||||
|
||||
module ConnectionAdapters
|
||||
autoload :AbstractAdapter, 'active_record/connection_adapters/abstract_adapter'
|
||||
end
|
||||
end
|
||||
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
||||
require 'active_record/schema_dumper'
|
||||
|
||||
require 'active_record/i18n_interpolation_deprecation'
|
||||
I18n.load_path << File.dirname(__FILE__) + '/active_record/locale/en.yml'
|
||||
|
|
|
@ -43,7 +43,7 @@ module ActiveRecord
|
|||
# loading in a more high-level (application developer-friendly) manner.
|
||||
module ClassMethods
|
||||
protected
|
||||
|
||||
|
||||
# Eager loads the named associations for the given ActiveRecord record(s).
|
||||
#
|
||||
# In this description, 'association name' shall refer to the name passed
|
||||
|
@ -94,8 +94,8 @@ module ActiveRecord
|
|||
raise "parent must be an association name" unless parent.is_a?(String) || parent.is_a?(Symbol)
|
||||
preload_associations(records, parent, preload_options)
|
||||
reflection = reflections[parent]
|
||||
parents = records.map {|record| record.send(reflection.name)}.flatten
|
||||
unless parents.empty? || parents.first.nil?
|
||||
parents = records.map {|record| record.send(reflection.name)}.flatten.compact
|
||||
unless parents.empty?
|
||||
parents.first.class.preload_associations(parents, child)
|
||||
end
|
||||
end
|
||||
|
@ -113,7 +113,7 @@ module ActiveRecord
|
|||
# unnecessarily
|
||||
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, records|
|
||||
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
|
||||
|
||||
|
||||
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
|
||||
# the following could call 'preload_belongs_to_association',
|
||||
# 'preload_has_many_association', etc.
|
||||
|
@ -128,7 +128,7 @@ module ActiveRecord
|
|||
association_proxy.target.push(*[associated_record].flatten)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def add_preloaded_record_to_collection(parent_records, reflection_name, associated_record)
|
||||
parent_records.each do |parent_record|
|
||||
parent_record.send("set_#{reflection_name}_target", associated_record)
|
||||
|
@ -183,18 +183,19 @@ module ActiveRecord
|
|||
conditions = "t0.#{reflection.primary_key_name} #{in_or_equals_for_ids(ids)}"
|
||||
conditions << append_conditions(reflection, preload_options)
|
||||
|
||||
associated_records = reflection.klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} as t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
|
||||
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
|
||||
:order => options[:order])
|
||||
|
||||
associated_records = reflection.klass.with_exclusive_scope do
|
||||
reflection.klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:joins => "INNER JOIN #{connection.quote_table_name options[:join_table]} t0 ON #{reflection.klass.quoted_table_name}.#{reflection.klass.primary_key} = t0.#{reflection.association_foreign_key}",
|
||||
:select => "#{options[:select] || table_name+'.*'}, t0.#{reflection.primary_key_name} as the_parent_record_id",
|
||||
:order => options[:order])
|
||||
end
|
||||
set_association_collection_records(id_to_record_map, reflection.name, associated_records, 'the_parent_record_id')
|
||||
end
|
||||
|
||||
def preload_has_one_association(records, reflection, preload_options={})
|
||||
return if records.first.send("loaded_#{reflection.name}?")
|
||||
id_to_record_map, ids = construct_id_map(records)
|
||||
id_to_record_map, ids = construct_id_map(records, reflection.options[:primary_key])
|
||||
options = reflection.options
|
||||
records.each {|record| record.send("set_#{reflection.name}_target", nil)}
|
||||
if options[:through]
|
||||
|
@ -204,9 +205,18 @@ module ActiveRecord
|
|||
unless through_records.empty?
|
||||
source = reflection.source_reflection.name
|
||||
through_records.first.class.preload_associations(through_records, source)
|
||||
through_records.each do |through_record|
|
||||
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
|
||||
reflection.name, through_record.send(source))
|
||||
if through_reflection.macro == :belongs_to
|
||||
rev_id_to_record_map, rev_ids = construct_id_map(records, through_primary_key)
|
||||
rev_primary_key = through_reflection.klass.primary_key
|
||||
through_records.each do |through_record|
|
||||
add_preloaded_record_to_collection(rev_id_to_record_map[through_record[rev_primary_key].to_s],
|
||||
reflection.name, through_record.send(source))
|
||||
end
|
||||
else
|
||||
through_records.each do |through_record|
|
||||
add_preloaded_record_to_collection(id_to_record_map[through_record[through_primary_key].to_s],
|
||||
reflection.name, through_record.send(source))
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -219,7 +229,7 @@ module ActiveRecord
|
|||
options = reflection.options
|
||||
|
||||
primary_key_name = reflection.through_reflection_primary_key_name
|
||||
id_to_record_map, ids = construct_id_map(records, primary_key_name)
|
||||
id_to_record_map, ids = construct_id_map(records, primary_key_name || reflection.options[:primary_key])
|
||||
records.each {|record| record.send(reflection.name).loaded}
|
||||
|
||||
if options[:through]
|
||||
|
@ -239,7 +249,7 @@ module ActiveRecord
|
|||
reflection.primary_key_name)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def preload_through_records(records, reflection, through_association)
|
||||
through_reflection = reflections[through_association]
|
||||
through_primary_key = through_reflection.primary_key_name
|
||||
|
@ -307,6 +317,7 @@ module ActiveRecord
|
|||
|
||||
klasses_and_ids.each do |klass_and_id|
|
||||
klass_name, id_map = *klass_and_id
|
||||
next if id_map.empty?
|
||||
klass = klass_name.constantize
|
||||
|
||||
table_name = klass.quoted_table_name
|
||||
|
@ -323,11 +334,13 @@ module ActiveRecord
|
|||
end
|
||||
conditions = "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"
|
||||
conditions << append_conditions(reflection, preload_options)
|
||||
associated_records = klass.find(:all, :conditions => [conditions, ids],
|
||||
associated_records = klass.with_exclusive_scope do
|
||||
klass.find(:all, :conditions => [conditions, ids],
|
||||
:include => options[:include],
|
||||
:select => options[:select],
|
||||
:joins => options[:joins],
|
||||
:order => options[:order])
|
||||
end
|
||||
set_association_single_records(id_map, reflection.name, associated_records, primary_key)
|
||||
end
|
||||
end
|
||||
|
@ -345,13 +358,15 @@ module ActiveRecord
|
|||
|
||||
conditions << append_conditions(reflection, preload_options)
|
||||
|
||||
reflection.klass.find(:all,
|
||||
reflection.klass.with_exclusive_scope do
|
||||
reflection.klass.find(:all,
|
||||
:select => (preload_options[:select] || options[:select] || "#{table_name}.*"),
|
||||
:include => preload_options[:include] || options[:include],
|
||||
:conditions => [conditions, ids],
|
||||
:joins => options[:joins],
|
||||
:group => preload_options[:group] || options[:group],
|
||||
:order => preload_options[:order] || options[:order])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,3 @@
|
|||
require 'active_record/associations/association_proxy'
|
||||
require 'active_record/associations/association_collection'
|
||||
require 'active_record/associations/belongs_to_association'
|
||||
require 'active_record/associations/belongs_to_polymorphic_association'
|
||||
require 'active_record/associations/has_one_association'
|
||||
require 'active_record/associations/has_many_association'
|
||||
require 'active_record/associations/has_many_through_association'
|
||||
require 'active_record/associations/has_and_belongs_to_many_association'
|
||||
require 'active_record/associations/has_one_through_association'
|
||||
|
||||
module ActiveRecord
|
||||
class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
|
||||
def initialize(owner_class_name, reflection)
|
||||
|
@ -32,7 +22,7 @@ module ActiveRecord
|
|||
through_reflection = reflection.through_reflection
|
||||
source_reflection_names = reflection.source_reflection_names
|
||||
source_associations = reflection.through_reflection.klass.reflect_on_all_associations.collect { |a| a.name.inspect }
|
||||
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :connector => 'or'} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :connector => 'or'}?")
|
||||
super("Could not find the source association(s) #{source_reflection_names.collect(&:inspect).to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '} in model #{through_reflection.klass}. Try 'has_many #{reflection.name.inspect}, :through => #{through_reflection.name.inspect}, :source => <name>'. Is it one of #{source_associations.to_sentence :two_words_connector => ' or ', :last_word_connector => ', or '}?")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -75,6 +65,18 @@ module ActiveRecord
|
|||
|
||||
# See ActiveRecord::Associations::ClassMethods for documentation.
|
||||
module Associations # :nodoc:
|
||||
# These classes will be loaded when associatoins 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'
|
||||
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
|
||||
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
|
||||
autoload :HasAndBelongsToManyAssociation, 'active_record/associations/has_and_belongs_to_many_association'
|
||||
autoload :HasManyAssociation, 'active_record/associations/has_many_association'
|
||||
autoload :HasManyThroughAssociation, 'active_record/associations/has_many_through_association'
|
||||
autoload :HasOneAssociation, 'active_record/associations/has_one_association'
|
||||
autoload :HasOneThroughAssociation, 'active_record/associations/has_one_through_association'
|
||||
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
@ -86,6 +88,18 @@ module ActiveRecord
|
|||
end unless self.new_record?
|
||||
end
|
||||
|
||||
private
|
||||
# Gets the specified association instance if it responds to :loaded?, nil otherwise.
|
||||
def association_instance_get(name)
|
||||
association = instance_variable_get("@#{name}")
|
||||
association if association.respond_to?(:loaded?)
|
||||
end
|
||||
|
||||
# Set the specified association instance.
|
||||
def association_instance_set(name, association)
|
||||
instance_variable_set("@#{name}", association)
|
||||
end
|
||||
|
||||
# Associations are a set of macro-like class methods for tying objects together through foreign keys. They express relationships like
|
||||
# "Project has one Project Manager" or "Project belongs to a Portfolio". Each macro adds a number of methods to the class which are
|
||||
# specialized according to the collection or association symbol and the options hash. It works much the same way as Ruby's own <tt>attr*</tt>
|
||||
|
@ -151,7 +165,7 @@ module ActiveRecord
|
|||
# #others.destroy_all | X | X | X
|
||||
# #others.find(*args) | X | X | X
|
||||
# #others.find_first | X | |
|
||||
# #others.exist? | X | X | X
|
||||
# #others.exists? | X | X | X
|
||||
# #others.uniq | X | X | X
|
||||
# #others.reset | X | X | X
|
||||
#
|
||||
|
@ -254,6 +268,10 @@ module ActiveRecord
|
|||
# You can manipulate objects and associations before they are saved to the database, but there is some special behavior you should be
|
||||
# aware of, mostly involving the saving of associated objects.
|
||||
#
|
||||
# Unless you enable the :autosave option on a <tt>has_one</tt>, <tt>belongs_to</tt>,
|
||||
# <tt>has_many</tt>, or <tt>has_and_belongs_to_many</tt> association,
|
||||
# in which case the members are always saved.
|
||||
#
|
||||
# === One-to-one associations
|
||||
#
|
||||
# * Assigning an object to a +has_one+ association automatically saves that object and the object being replaced (if there is one), in
|
||||
|
@ -650,7 +668,7 @@ module ActiveRecord
|
|||
# Returns the number of associated objects.
|
||||
# [collection.find(...)]
|
||||
# Finds an associated object according to the same rules as ActiveRecord::Base.find.
|
||||
# [collection.exist?(...)]
|
||||
# [collection.exists?(...)]
|
||||
# Checks whether an associated object with the given conditions exists.
|
||||
# Uses the same rules as ActiveRecord::Base.exists?.
|
||||
# [collection.build(attributes = {}, ...)]
|
||||
|
@ -680,7 +698,7 @@ module ActiveRecord
|
|||
# * <tt>Firm#clients.empty?</tt> (similar to <tt>firm.clients.size == 0</tt>)
|
||||
# * <tt>Firm#clients.size</tt> (similar to <tt>Client.count "firm_id = #{id}"</tt>)
|
||||
# * <tt>Firm#clients.find</tt> (similar to <tt>Client.find(id, :conditions => "firm_id = #{id}")</tt>)
|
||||
# * <tt>Firm#clients.exist?(:name => 'ACME')</tt> (similar to <tt>Client.exist?(:name => 'ACME', :firm_id => firm.id)</tt>)
|
||||
# * <tt>Firm#clients.exists?(:name => 'ACME')</tt> (similar to <tt>Client.exists?(:name => 'ACME', :firm_id => firm.id)</tt>)
|
||||
# * <tt>Firm#clients.build</tt> (similar to <tt>Client.new("firm_id" => id)</tt>)
|
||||
# * <tt>Firm#clients.create</tt> (similar to <tt>c = Client.new("firm_id" => id); c.save; c</tt>)
|
||||
# The declaration can also include an options hash to specialize the behavior of the association.
|
||||
|
@ -722,6 +740,8 @@ module ActiveRecord
|
|||
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
||||
# [:group]
|
||||
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
||||
# [:having]
|
||||
# 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.
|
||||
# [:limit]
|
||||
# An integer determining the limit on the number of rows that should be returned.
|
||||
# [:offset]
|
||||
|
@ -748,6 +768,9 @@ module ActiveRecord
|
|||
# If true, all the associated objects are readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated objects when saving the parent object. true by default.
|
||||
# [:autosave]
|
||||
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_many :comments, :order => "posted_on"
|
||||
# has_many :comments, :include => :author
|
||||
|
@ -861,6 +884,8 @@ module ActiveRecord
|
|||
# If true, the associated object is readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated object when saving the parent object. +false+ by default.
|
||||
# [:autosave]
|
||||
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
||||
|
@ -878,13 +903,10 @@ module ActiveRecord
|
|||
else
|
||||
reflection = create_has_one_reflection(association_id, options)
|
||||
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
method_name = "has_one_after_save_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
|
||||
if !association.nil? && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
|
||||
association = association_instance_get(reflection.name)
|
||||
if association && (new_record? || association.new_record? || association[reflection.primary_key_name] != id)
|
||||
association[reflection.primary_key_name] = id
|
||||
association.save(true)
|
||||
end
|
||||
|
@ -975,6 +997,8 @@ module ActiveRecord
|
|||
# If true, the associated object is readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
|
||||
# [:autosave]
|
||||
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# belongs_to :firm, :foreign_key => "client_of"
|
||||
|
@ -987,15 +1011,12 @@ module ActiveRecord
|
|||
def belongs_to(association_id, options = {})
|
||||
reflection = create_belongs_to_reflection(association_id, options)
|
||||
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
if reflection.options[:polymorphic]
|
||||
association_accessor_methods(reflection, BelongsToPolymorphicAssociation)
|
||||
|
||||
method_name = "polymorphic_belongs_to_before_save_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
|
||||
association = association_instance_get(reflection.name)
|
||||
if association && association.target
|
||||
if association.new_record?
|
||||
association.save(true)
|
||||
|
@ -1015,9 +1036,7 @@ module ActiveRecord
|
|||
|
||||
method_name = "belongs_to_before_save_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
|
||||
if !association.nil?
|
||||
if association = association_instance_get(reflection.name)
|
||||
if association.new_record?
|
||||
association.save(true)
|
||||
end
|
||||
|
@ -1103,7 +1122,7 @@ module ActiveRecord
|
|||
# Finds an associated object responding to the +id+ and that
|
||||
# meets the condition that it has to be associated with this object.
|
||||
# Uses the same rules as ActiveRecord::Base.find.
|
||||
# [collection.exist?(...)]
|
||||
# [collection.exists?(...)]
|
||||
# Checks whether an associated object with the given conditions exists.
|
||||
# Uses the same rules as ActiveRecord::Base.exists?.
|
||||
# [collection.build(attributes = {})]
|
||||
|
@ -1129,7 +1148,7 @@ module ActiveRecord
|
|||
# * <tt>Developer#projects.empty?</tt>
|
||||
# * <tt>Developer#projects.size</tt>
|
||||
# * <tt>Developer#projects.find(id)</tt>
|
||||
# * <tt>Developer#clients.exist?(...)</tt>
|
||||
# * <tt>Developer#clients.exists?(...)</tt>
|
||||
# * <tt>Developer#projects.build</tt> (similar to <tt>Project.new("project_id" => id)</tt>)
|
||||
# * <tt>Developer#projects.create</tt> (similar to <tt>c = Project.new("project_id" => id); c.save; c</tt>)
|
||||
# The declaration may include an options hash to specialize the behavior of the association.
|
||||
|
@ -1147,11 +1166,12 @@ module ActiveRecord
|
|||
# [:foreign_key]
|
||||
# Specify the foreign key used for the association. By default this is guessed to be the name
|
||||
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_and_belongs_to_many+ association
|
||||
# will use "person_id" as the default <tt>:foreign_key</tt>.
|
||||
# to Project will use "person_id" as the default <tt>:foreign_key</tt>.
|
||||
# [:association_foreign_key]
|
||||
# Specify the association foreign key used for the association. By default this is
|
||||
# guessed to be the name of the associated class in lower-case and "_id" suffixed. So if the associated class is Project,
|
||||
# the +has_and_belongs_to_many+ association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
||||
# Specify the foreign key used for the association on the receiving side of the association.
|
||||
# By default this is guessed to be the name of the associated class in lower-case and "_id" suffixed.
|
||||
# So if a Person class makes a +has_and_belongs_to_many+ association to Project,
|
||||
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
||||
# [:conditions]
|
||||
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
||||
# SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are scoped if a hash is used.
|
||||
|
@ -1179,6 +1199,8 @@ module ActiveRecord
|
|||
# Specify second-order associations that should be eager loaded when the collection is loaded.
|
||||
# [:group]
|
||||
# An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
||||
# [:having]
|
||||
# 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.
|
||||
# [:limit]
|
||||
# An integer determining the limit on the number of rows that should be returned.
|
||||
# [:offset]
|
||||
|
@ -1190,6 +1212,8 @@ module ActiveRecord
|
|||
# If true, all the associated objects are readonly through the association.
|
||||
# [:validate]
|
||||
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
||||
# [:autosave]
|
||||
# If true, always save any loaded members and destroy members marked for destruction, when saving the parent object. Off by default.
|
||||
#
|
||||
# Option examples:
|
||||
# has_and_belongs_to_many :projects
|
||||
|
@ -1210,11 +1234,11 @@ module ActiveRecord
|
|||
# callbacks will be executed after the association is wiped out.
|
||||
old_method = "destroy_without_habtm_shim_for_#{reflection.name}"
|
||||
class_eval <<-end_eval unless method_defined?(old_method)
|
||||
alias_method :#{old_method}, :destroy_without_callbacks
|
||||
def destroy_without_callbacks
|
||||
#{reflection.name}.clear
|
||||
#{old_method}
|
||||
end
|
||||
alias_method :#{old_method}, :destroy_without_callbacks # alias_method :destroy_without_habtm_shim_for_posts, :destroy_without_callbacks
|
||||
def destroy_without_callbacks # def destroy_without_callbacks
|
||||
#{reflection.name}.clear # posts.clear
|
||||
#{old_method} # destroy_without_habtm_shim_for_posts
|
||||
end # end
|
||||
end_eval
|
||||
|
||||
add_association_callbacks(reflection.name, options)
|
||||
|
@ -1237,33 +1261,30 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def association_accessor_methods(reflection, association_proxy_class)
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
define_method(reflection.name) do |*params|
|
||||
force_reload = params.first unless params.empty?
|
||||
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
if association.nil? || force_reload
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
retval = association.reload
|
||||
if retval.nil? and association_proxy_class == BelongsToAssociation
|
||||
instance_variable_set(ivar, nil)
|
||||
association_instance_set(reflection.name, nil)
|
||||
return nil
|
||||
end
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
|
||||
association.target.nil? ? nil : association
|
||||
end
|
||||
|
||||
define_method("loaded_#{reflection.name}?") do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
association && association.loaded?
|
||||
end
|
||||
|
||||
define_method("#{reflection.name}=") do |new_value|
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
if association.nil? || association.target != new_value
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
|
@ -1274,7 +1295,7 @@ module ActiveRecord
|
|||
self.send(reflection.name, new_value)
|
||||
else
|
||||
association.replace(new_value)
|
||||
instance_variable_set(ivar, new_value.nil? ? nil : association)
|
||||
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1282,20 +1303,18 @@ module ActiveRecord
|
|||
return if target.nil? and association_proxy_class == BelongsToAssociation
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
association.target = target
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
end
|
||||
|
||||
def collection_reader_method(reflection, association_proxy_class)
|
||||
define_method(reflection.name) do |*params|
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
force_reload = params.first unless params.empty?
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
unless association.respond_to?(:loaded?)
|
||||
unless association
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
|
||||
association.reload if force_reload
|
||||
|
@ -1333,8 +1352,7 @@ module ActiveRecord
|
|||
def add_single_associated_validation_callbacks(association_name)
|
||||
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get("@#{association_name}")
|
||||
if !association.nil?
|
||||
if association = association_instance_get(association_name)
|
||||
errors.add association_name unless association.target.nil? || association.valid?
|
||||
end
|
||||
end
|
||||
|
@ -1344,12 +1362,10 @@ module ActiveRecord
|
|||
|
||||
def add_multiple_associated_validation_callbacks(association_name)
|
||||
method_name = "validate_associated_records_for_#{association_name}".to_sym
|
||||
ivar = "@#{association_name}"
|
||||
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(association_name)
|
||||
|
||||
if association.respond_to?(:loaded?)
|
||||
if association
|
||||
if new_record?
|
||||
association
|
||||
elsif association.loaded?
|
||||
|
@ -1366,8 +1382,6 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def add_multiple_associated_save_callbacks(association_name)
|
||||
ivar = "@#{association_name}"
|
||||
|
||||
method_name = "before_save_associated_records_for_#{association_name}".to_sym
|
||||
define_method(method_name) do
|
||||
@new_record_before_save = new_record?
|
||||
|
@ -1377,13 +1391,13 @@ module ActiveRecord
|
|||
|
||||
method_name = "after_create_or_update_associated_records_for_#{association_name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(association_name)
|
||||
|
||||
records_to_save = if @new_record_before_save
|
||||
association
|
||||
elsif association.respond_to?(:loaded?) && association.loaded?
|
||||
elsif association && association.loaded?
|
||||
association.select { |record| record.new_record? }
|
||||
elsif association.respond_to?(:loaded?) && !association.loaded?
|
||||
elsif association && !association.loaded?
|
||||
association.target.select { |record| record.new_record? }
|
||||
else
|
||||
[]
|
||||
|
@ -1401,15 +1415,13 @@ module ActiveRecord
|
|||
|
||||
def association_constructor_method(constructor, reflection, association_proxy_class)
|
||||
define_method("#{constructor}_#{reflection.name}") do |*params|
|
||||
ivar = "@#{reflection.name}"
|
||||
|
||||
attributees = params.first unless params.empty?
|
||||
replace_existing = params[1].nil? ? true : params[1]
|
||||
association = instance_variable_get(ivar) if instance_variable_defined?(ivar)
|
||||
association = association_instance_get(reflection.name)
|
||||
|
||||
if association.nil?
|
||||
unless association
|
||||
association = association_proxy_class.new(self, reflection)
|
||||
instance_variable_set(ivar, association)
|
||||
association_instance_set(reflection.name, association)
|
||||
end
|
||||
|
||||
if association_proxy_class == HasOneAssociation
|
||||
|
@ -1447,7 +1459,7 @@ module ActiveRecord
|
|||
dependent_conditions << sanitize_sql(reflection.options[:conditions]) if reflection.options[:conditions]
|
||||
dependent_conditions << extra_conditions if extra_conditions
|
||||
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
||||
|
||||
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
||||
case reflection.options[:dependent]
|
||||
when :destroy
|
||||
method_name = "has_many_dependent_destroy_for_#{reflection.name}".to_sym
|
||||
|
@ -1457,22 +1469,22 @@ module ActiveRecord
|
|||
before_destroy method_name
|
||||
when :delete_all
|
||||
module_eval %Q{
|
||||
before_destroy do |record|
|
||||
delete_all_has_many_dependencies(record,
|
||||
"#{reflection.name}",
|
||||
#{reflection.class_name},
|
||||
"#{dependent_conditions}")
|
||||
end
|
||||
before_destroy do |record| # before_destroy do |record|
|
||||
delete_all_has_many_dependencies(record, # delete_all_has_many_dependencies(record,
|
||||
"#{reflection.name}", # "posts",
|
||||
#{reflection.class_name}, # Post,
|
||||
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
||||
end # end
|
||||
}
|
||||
when :nullify
|
||||
module_eval %Q{
|
||||
before_destroy do |record|
|
||||
nullify_has_many_dependencies(record,
|
||||
"#{reflection.name}",
|
||||
#{reflection.class_name},
|
||||
"#{reflection.primary_key_name}",
|
||||
"#{dependent_conditions}")
|
||||
end
|
||||
before_destroy do |record| # before_destroy do |record|
|
||||
nullify_has_many_dependencies(record, # nullify_has_many_dependencies(record,
|
||||
"#{reflection.name}", # "posts",
|
||||
#{reflection.class_name}, # Post,
|
||||
"#{reflection.primary_key_name}", # "user_id",
|
||||
%@#{dependent_conditions}@) # %@...@) # this is a string literal like %(...)
|
||||
end # end
|
||||
}
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy, :delete_all, or :nullify (#{reflection.options[:dependent].inspect})"
|
||||
|
@ -1525,14 +1537,14 @@ module ActiveRecord
|
|||
association = send(reflection.name)
|
||||
association.destroy unless association.nil?
|
||||
end
|
||||
before_destroy method_name
|
||||
after_destroy method_name
|
||||
when :delete
|
||||
method_name = "belongs_to_dependent_delete_for_#{reflection.name}".to_sym
|
||||
define_method(method_name) do
|
||||
association = send(reflection.name)
|
||||
association.delete unless association.nil?
|
||||
end
|
||||
before_destroy method_name
|
||||
after_destroy method_name
|
||||
else
|
||||
raise ArgumentError, "The :dependent option expects either :destroy or :delete (#{reflection.options[:dependent].inspect})"
|
||||
end
|
||||
|
@ -1551,7 +1563,7 @@ module ActiveRecord
|
|||
@@valid_keys_for_has_many_association = [
|
||||
:class_name, :table_name, :foreign_key, :primary_key,
|
||||
:dependent,
|
||||
:select, :conditions, :include, :order, :group, :limit, :offset,
|
||||
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
||||
:as, :through, :source, :source_type,
|
||||
:uniq,
|
||||
:finder_sql, :counter_sql,
|
||||
|
@ -1607,7 +1619,7 @@ module ActiveRecord
|
|||
mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
|
||||
@@valid_keys_for_has_and_belongs_to_many_association = [
|
||||
:class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
|
||||
:select, :conditions, :include, :order, :group, :limit, :offset,
|
||||
:select, :conditions, :include, :order, :group, :having, :limit, :offset,
|
||||
:uniq,
|
||||
:finder_sql, :counter_sql, :delete_sql, :insert_sql,
|
||||
:before_add, :after_add, :before_remove, :after_remove,
|
||||
|
@ -1656,7 +1668,7 @@ module ActiveRecord
|
|||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
||||
|
||||
add_group!(sql, options[:group], scope)
|
||||
add_group!(sql, options[:group], options[:having], scope)
|
||||
add_order!(sql, options[:order], scope)
|
||||
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
||||
add_lock!(sql, options, scope)
|
||||
|
@ -1712,7 +1724,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
add_group!(sql, options[:group], scope)
|
||||
add_group!(sql, options[:group], options[:having], scope)
|
||||
|
||||
if order && is_distinct
|
||||
connection.add_order_by_for_association_limiting!(sql, :order => order)
|
||||
|
@ -1725,46 +1737,70 @@ module ActiveRecord
|
|||
return sanitize_sql(sql)
|
||||
end
|
||||
|
||||
def tables_in_string(string)
|
||||
return [] if string.blank?
|
||||
string.scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
end
|
||||
|
||||
def conditions_tables(options)
|
||||
# look in both sets of conditions
|
||||
conditions = [scope(:find, :conditions), options[:conditions]].inject([]) do |all, cond|
|
||||
case cond
|
||||
when nil then all
|
||||
when Array then all << cond.first
|
||||
when Hash then all << cond.keys
|
||||
else all << cond
|
||||
end
|
||||
end
|
||||
conditions.join(' ').scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
tables_in_string(conditions.join(' '))
|
||||
end
|
||||
|
||||
def order_tables(options)
|
||||
order = [options[:order], scope(:find, :order) ].join(", ")
|
||||
return [] unless order && order.is_a?(String)
|
||||
order.scan(/([\.a-zA-Z_]+).?\./).flatten
|
||||
tables_in_string(order)
|
||||
end
|
||||
|
||||
def selects_tables(options)
|
||||
select = options[:select]
|
||||
return [] unless select && select.is_a?(String)
|
||||
select.scan(/"?([\.a-zA-Z_]+)"?.?\./).flatten
|
||||
tables_in_string(select)
|
||||
end
|
||||
|
||||
def joined_tables(options)
|
||||
scope = scope(:find)
|
||||
joins = options[:joins]
|
||||
merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
|
||||
[table_name] + case merged_joins
|
||||
when Symbol, Hash, Array
|
||||
if array_of_strings?(merged_joins)
|
||||
tables_in_string(merged_joins.join(' '))
|
||||
else
|
||||
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
|
||||
join_dependency.join_associations.collect {|join_association| [join_association.aliased_join_table_name, join_association.aliased_table_name]}.flatten.compact
|
||||
end
|
||||
else
|
||||
tables_in_string(merged_joins)
|
||||
end
|
||||
end
|
||||
|
||||
# Checks if the conditions reference a table other than the current model table
|
||||
def include_eager_conditions?(options, tables = nil)
|
||||
((tables || conditions_tables(options)) - [table_name]).any?
|
||||
def include_eager_conditions?(options, tables = nil, joined_tables = nil)
|
||||
((tables || conditions_tables(options)) - (joined_tables || joined_tables(options))).any?
|
||||
end
|
||||
|
||||
# Checks if the query order references a table other than the current model's table.
|
||||
def include_eager_order?(options, tables = nil)
|
||||
((tables || order_tables(options)) - [table_name]).any?
|
||||
def include_eager_order?(options, tables = nil, joined_tables = nil)
|
||||
((tables || order_tables(options)) - (joined_tables || joined_tables(options))).any?
|
||||
end
|
||||
|
||||
def include_eager_select?(options)
|
||||
(selects_tables(options) - [table_name]).any?
|
||||
def include_eager_select?(options, joined_tables = nil)
|
||||
(selects_tables(options) - (joined_tables || joined_tables(options))).any?
|
||||
end
|
||||
|
||||
def references_eager_loaded_tables?(options)
|
||||
include_eager_order?(options) || include_eager_conditions?(options) || include_eager_select?(options)
|
||||
joined_tables = joined_tables(options)
|
||||
include_eager_order?(options, nil, joined_tables) || include_eager_conditions?(options, nil, joined_tables) || include_eager_select?(options, joined_tables)
|
||||
end
|
||||
|
||||
def using_limitable_reflections?(reflections)
|
||||
|
@ -2141,7 +2177,7 @@ module ActiveRecord
|
|||
aliased_table_name,
|
||||
foreign_key,
|
||||
parent.aliased_table_name,
|
||||
parent.primary_key
|
||||
reflection.options[:primary_key] || parent.primary_key
|
||||
]
|
||||
end
|
||||
when :belongs_to
|
||||
|
@ -2168,7 +2204,7 @@ module ActiveRecord
|
|||
protected
|
||||
|
||||
def aliased_table_name_for(name, suffix = nil)
|
||||
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{name.downcase}\son}
|
||||
if !parent.table_joins.blank? && parent.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+#{active_record.connection.quote_table_name name.downcase}\son}
|
||||
@join_dependency.table_aliases[name] += 1
|
||||
end
|
||||
|
||||
|
|
|
@ -83,7 +83,11 @@ module ActiveRecord
|
|||
|
||||
def to_ary
|
||||
load_target
|
||||
@target.to_ary
|
||||
if @target.is_a?(Array)
|
||||
@target.to_ary
|
||||
else
|
||||
Array(@target)
|
||||
end
|
||||
end
|
||||
|
||||
def reset
|
||||
|
|
|
@ -180,7 +180,10 @@ module ActiveRecord
|
|||
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
||||
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
||||
else
|
||||
record[@reflection.primary_key_name] = @owner.id unless @owner.new_record?
|
||||
unless @owner.new_record?
|
||||
primary_key = @reflection.options[:primary_key] || :id
|
||||
record[@reflection.primary_key_name] = @owner.send(primary_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -188,6 +191,7 @@ module ActiveRecord
|
|||
def merge_options_from_reflection!(options)
|
||||
options.reverse_merge!(
|
||||
:group => @reflection.options[:group],
|
||||
:having => @reflection.options[:having],
|
||||
:limit => @reflection.options[:limit],
|
||||
:offset => @reflection.options[:offset],
|
||||
:joins => @reflection.options[:joins],
|
||||
|
@ -206,7 +210,10 @@ module ActiveRecord
|
|||
# Forwards any missing method call to the \target.
|
||||
def method_missing(method, *args)
|
||||
if load_target
|
||||
raise NoMethodError unless @target.respond_to?(method)
|
||||
unless @target.respond_to?(method)
|
||||
message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}"
|
||||
raise NoMethodError, message
|
||||
end
|
||||
|
||||
if block_given?
|
||||
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
||||
|
|
|
@ -9,6 +9,14 @@ module ActiveRecord
|
|||
create_record(attributes) { |record| insert_record(record, true) }
|
||||
end
|
||||
|
||||
def columns
|
||||
@reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
||||
end
|
||||
|
||||
def reset_column_information
|
||||
@reflection.reset_column_information
|
||||
end
|
||||
|
||||
protected
|
||||
def construct_find_options!(options)
|
||||
options[:joins] = @join_sql
|
||||
|
@ -32,8 +40,6 @@ module ActiveRecord
|
|||
if @reflection.options[:insert_sql]
|
||||
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
|
||||
else
|
||||
columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
||||
|
||||
attributes = columns.inject({}) do |attrs, column|
|
||||
case column.name.to_s
|
||||
when @reflection.primary_key_name.to_s
|
||||
|
@ -103,7 +109,7 @@ module ActiveRecord
|
|||
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
|
||||
# an id column. This will then overwrite the id column of the records coming back.
|
||||
def finding_with_ambiguous_select?(select_clause)
|
||||
!select_clause && @owner.connection.columns(@reflection.options[:join_table], "Join Table Columns").size != 2
|
||||
!select_clause && columns.size != 2
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -160,9 +160,9 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
"INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
|
||||
@reflection.through_reflection.table_name,
|
||||
@reflection.table_name, reflection_primary_key,
|
||||
@reflection.through_reflection.table_name, source_primary_key,
|
||||
@reflection.through_reflection.quoted_table_name,
|
||||
@reflection.quoted_table_name, reflection_primary_key,
|
||||
@reflection.through_reflection.quoted_table_name, source_primary_key,
|
||||
polymorphic_join
|
||||
]
|
||||
end
|
||||
|
|
213
vendor/rails/activerecord/lib/active_record/autosave_association.rb
vendored
Normal file
213
vendor/rails/activerecord/lib/active_record/autosave_association.rb
vendored
Normal file
|
@ -0,0 +1,213 @@
|
|||
module ActiveRecord
|
||||
# AutosaveAssociation is a module that takes care of automatically saving
|
||||
# your associations when the parent is saved. In addition to saving, it
|
||||
# also destroys any associations that were marked for destruction.
|
||||
# (See mark_for_destruction and marked_for_destruction?)
|
||||
#
|
||||
# Saving of the parent, its associations, and the destruction of marked
|
||||
# associations, all happen inside 1 transaction. This should never leave the
|
||||
# database in an inconsistent state after, for instance, mass assigning
|
||||
# attributes and saving them.
|
||||
#
|
||||
# If validations for any of the associations fail, their error messages will
|
||||
# be applied to the parent.
|
||||
#
|
||||
# Note that it also means that associations marked for destruction won't
|
||||
# be destroyed directly. They will however still be marked for destruction.
|
||||
#
|
||||
# === One-to-one Example
|
||||
#
|
||||
# Consider a Post model with one Author:
|
||||
#
|
||||
# class Post
|
||||
# has_one :author, :autosave => true
|
||||
# end
|
||||
#
|
||||
# Saving changes to the parent and its associated model can now be performed
|
||||
# automatically _and_ atomically:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.title # => "The current global position of migrating ducks"
|
||||
# post.author.name # => "alloy"
|
||||
#
|
||||
# post.title = "On the migration of ducks"
|
||||
# post.author.name = "Eloy Duran"
|
||||
#
|
||||
# post.save
|
||||
# post.reload
|
||||
# post.title # => "On the migration of ducks"
|
||||
# post.author.name # => "Eloy Duran"
|
||||
#
|
||||
# Destroying an associated model, as part of the parent's save action, is as
|
||||
# simple as marking it for destruction:
|
||||
#
|
||||
# post.author.mark_for_destruction
|
||||
# post.author.marked_for_destruction? # => true
|
||||
#
|
||||
# Note that the model is _not_ yet removed from the database:
|
||||
# id = post.author.id
|
||||
# Author.find_by_id(id).nil? # => false
|
||||
#
|
||||
# post.save
|
||||
# post.reload.author # => nil
|
||||
#
|
||||
# Now it _is_ removed from the database:
|
||||
# Author.find_by_id(id).nil? # => true
|
||||
#
|
||||
# === One-to-many Example
|
||||
#
|
||||
# Consider a Post model with many Comments:
|
||||
#
|
||||
# class Post
|
||||
# has_many :comments, :autosave => true
|
||||
# end
|
||||
#
|
||||
# Saving changes to the parent and its associated model can now be performed
|
||||
# automatically _and_ atomically:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.title # => "The current global position of migrating ducks"
|
||||
# post.comments.first.body # => "Wow, awesome info thanks!"
|
||||
# post.comments.last.body # => "Actually, your article should be named differently."
|
||||
#
|
||||
# post.title = "On the migration of ducks"
|
||||
# post.comments.last.body = "Actually, your article should be named differently. [UPDATED]: You are right, thanks."
|
||||
#
|
||||
# post.save
|
||||
# post.reload
|
||||
# post.title # => "On the migration of ducks"
|
||||
# post.comments.last.body # => "Actually, your article should be named differently. [UPDATED]: You are right, thanks."
|
||||
#
|
||||
# Destroying one of the associated models members, as part of the parent's
|
||||
# save action, is as simple as marking it for destruction:
|
||||
#
|
||||
# post.comments.last.mark_for_destruction
|
||||
# post.comments.last.marked_for_destruction? # => true
|
||||
# post.comments.length # => 2
|
||||
#
|
||||
# Note that the model is _not_ yet removed from the database:
|
||||
# id = post.comments.last.id
|
||||
# Comment.find_by_id(id).nil? # => false
|
||||
#
|
||||
# post.save
|
||||
# post.reload.comments.length # => 1
|
||||
#
|
||||
# Now it _is_ removed from the database:
|
||||
# Comment.find_by_id(id).nil? # => true
|
||||
#
|
||||
# === Validation
|
||||
#
|
||||
# Validation is performed on the parent as usual, but also on all autosave
|
||||
# enabled associations. If any of the associations fail validation, its
|
||||
# error messages will be applied on the parents errors object and validation
|
||||
# of the parent will fail.
|
||||
#
|
||||
# Consider a Post model with Author which validates the presence of its name
|
||||
# attribute:
|
||||
#
|
||||
# class Post
|
||||
# has_one :author, :autosave => true
|
||||
# end
|
||||
#
|
||||
# class Author
|
||||
# validates_presence_of :name
|
||||
# end
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.author.name = ''
|
||||
# post.save # => false
|
||||
# post.errors # => #<ActiveRecord::Errors:0x174498c @errors={"author_name"=>["can't be blank"]}, @base=#<Post ...>>
|
||||
#
|
||||
# No validations will be performed on the associated models when validations
|
||||
# are skipped for the parent:
|
||||
#
|
||||
# post = Post.find(1)
|
||||
# post.author.name = ''
|
||||
# post.save(false) # => true
|
||||
module AutosaveAssociation
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
alias_method_chain :reload, :autosave_associations
|
||||
alias_method_chain :save, :autosave_associations
|
||||
alias_method_chain :valid?, :autosave_associations
|
||||
|
||||
%w{ has_one belongs_to has_many has_and_belongs_to_many }.each do |type|
|
||||
base.send("valid_keys_for_#{type}_association") << :autosave
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Saves the parent, <tt>self</tt>, and any loaded autosave associations.
|
||||
# In addition, it destroys all children that were marked for destruction
|
||||
# with mark_for_destruction.
|
||||
#
|
||||
# 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_with_autosave_associations(perform_validation = true)
|
||||
returning(save_without_autosave_associations(perform_validation)) do |valid|
|
||||
if valid
|
||||
self.class.reflect_on_all_autosave_associations.each do |reflection|
|
||||
if (association = association_instance_get(reflection.name)) && association.loaded?
|
||||
if association.is_a?(Array)
|
||||
association.proxy_target.each do |child|
|
||||
child.marked_for_destruction? ? child.destroy : child.save(perform_validation)
|
||||
end
|
||||
else
|
||||
association.marked_for_destruction? ? association.destroy : association.save(perform_validation)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether or not the parent, <tt>self</tt>, and any loaded autosave associations are valid.
|
||||
def valid_with_autosave_associations?
|
||||
if valid_without_autosave_associations?
|
||||
self.class.reflect_on_all_autosave_associations.all? do |reflection|
|
||||
if (association = association_instance_get(reflection.name)) && association.loaded?
|
||||
if association.is_a?(Array)
|
||||
association.proxy_target.all? { |child| autosave_association_valid?(reflection, child) }
|
||||
else
|
||||
autosave_association_valid?(reflection, association)
|
||||
end
|
||||
else
|
||||
true # association not loaded yet, so it should be valid
|
||||
end
|
||||
end
|
||||
else
|
||||
false # self was not valid
|
||||
end
|
||||
end
|
||||
|
||||
# Returns whether or not the association is valid and applies any errors to the parent, <tt>self</tt>, if it wasn't.
|
||||
def autosave_association_valid?(reflection, association)
|
||||
returning(association.valid?) do |valid|
|
||||
association.errors.each do |attribute, message|
|
||||
errors.add "#{reflection.name}_#{attribute}", message
|
||||
end unless valid
|
||||
end
|
||||
end
|
||||
|
||||
# Reloads the attributes of the object as usual and removes a mark for destruction.
|
||||
def reload_with_autosave_associations(options = nil)
|
||||
@marked_for_destruction = false
|
||||
reload_without_autosave_associations(options)
|
||||
end
|
||||
|
||||
# Marks this record to be destroyed as part of the parents save transaction.
|
||||
# This does _not_ actually destroy the record yet, rather it will be destroyed when <tt>parent.save</tt> is called.
|
||||
#
|
||||
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
||||
def mark_for_destruction
|
||||
@marked_for_destruction = true
|
||||
end
|
||||
|
||||
# Returns whether or not this record will be destroyed as part of the parents save transaction.
|
||||
#
|
||||
# Only useful if the <tt>:autosave</tt> option on the parent is enabled for this associated model.
|
||||
def marked_for_destruction?
|
||||
@marked_for_destruction
|
||||
end
|
||||
end
|
||||
end
|
350
vendor/rails/activerecord/lib/active_record/base.rb
vendored
350
vendor/rails/activerecord/lib/active_record/base.rb
vendored
|
@ -327,7 +327,7 @@ module ActiveRecord #:nodoc:
|
|||
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
||||
#
|
||||
# You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
|
||||
# descendent of a class not in the hierarchy. Example:
|
||||
# descendant of a class not in the hierarchy. Example:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# serialize :preferences, Hash
|
||||
|
@ -389,6 +389,8 @@ 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+.
|
||||
cattr_accessor :logger, :instance_writer => false
|
||||
|
@ -414,7 +416,9 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
@@subclasses = {}
|
||||
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Contains the database configuration - as is typically stored in config/database.yml -
|
||||
# as a Hash.
|
||||
#
|
||||
|
@ -443,6 +447,8 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :configurations, :instance_writer => false
|
||||
@@configurations = {}
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
|
||||
# :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
|
||||
# the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
|
||||
|
@ -450,34 +456,46 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :primary_key_prefix_type, :instance_writer => false
|
||||
@@primary_key_prefix_type = nil
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
|
||||
# table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
|
||||
# for tables in a shared database. By default, the prefix is the empty string.
|
||||
cattr_accessor :table_name_prefix, :instance_writer => false
|
||||
@@table_name_prefix = ""
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
|
||||
# "people_basecamp"). By default, the suffix is the empty string.
|
||||
cattr_accessor :table_name_suffix, :instance_writer => false
|
||||
@@table_name_suffix = ""
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Indicates whether table names should be the pluralized versions of the corresponding class names.
|
||||
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
|
||||
# See table_name for the full rules on table/class naming. This is true, by default.
|
||||
cattr_accessor :pluralize_table_names, :instance_writer => false
|
||||
@@pluralize_table_names = true
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
|
||||
# make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
|
||||
# may complicate matters if you use software like syslog. This is true, by default.
|
||||
cattr_accessor :colorize_logging, :instance_writer => false
|
||||
@@colorize_logging = true
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
|
||||
# This is set to :local by default.
|
||||
cattr_accessor :default_timezone, :instance_writer => false
|
||||
@@default_timezone = :local
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Specifies the format to use when dumping the database schema with Rails'
|
||||
# Rakefile. If :sql, the schema is dumped as (potentially database-
|
||||
# specific) SQL statements. If :ruby, the schema is dumped as an
|
||||
|
@ -487,6 +505,8 @@ module ActiveRecord #:nodoc:
|
|||
cattr_accessor :schema_format , :instance_writer => false
|
||||
@@schema_format = :ruby
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# Specify whether or not to use timestamps for migration numbers
|
||||
cattr_accessor :timestamped_migrations , :instance_writer => false
|
||||
@@timestamped_migrations = true
|
||||
|
@ -495,6 +515,10 @@ module ActiveRecord #:nodoc:
|
|||
superclass_delegating_accessor :store_full_sti_class
|
||||
self.store_full_sti_class = false
|
||||
|
||||
# Stores the default scope for the class
|
||||
class_inheritable_accessor :default_scoping, :instance_writer => false
|
||||
self.default_scoping = []
|
||||
|
||||
class << self # Class methods
|
||||
# Find operates with four different retrieval approaches:
|
||||
#
|
||||
|
@ -517,10 +541,12 @@ module ActiveRecord #:nodoc:
|
|||
# * <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)
|
||||
# or 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).
|
||||
# * <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
|
||||
|
@ -635,16 +661,24 @@ module ActiveRecord #:nodoc:
|
|||
connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
|
||||
end
|
||||
|
||||
# Checks whether a record exists in the database that matches conditions given. These conditions
|
||||
# can either be a single integer representing a primary key id to be found, or a condition to be
|
||||
# matched like using ActiveRecord#find.
|
||||
|
||||
# Returns true if a record exists in the table that matches the +id+ or
|
||||
# conditions given, or false otherwise. The argument can take four forms:
|
||||
#
|
||||
# The +id_or_conditions+ parameter can be an Integer or a String if you want to search the primary key
|
||||
# column of the table for a matching id, or if you're looking to match against a condition you can use
|
||||
# an Array or a Hash.
|
||||
# * 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>).
|
||||
#
|
||||
# Possible gotcha: You can't pass in a condition as a string e.g. "name = 'Jamie'", this would be
|
||||
# sanitized and then queried against the primary key column as "id = 'name = \'Jamie"
|
||||
# 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)
|
||||
|
@ -722,25 +756,26 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Delete an object (or multiple objects) where the +id+ given matches the primary_key. A SQL +DELETE+ command
|
||||
# is executed on the database which means that no callbacks are fired off running this. This is an efficient method
|
||||
# of deleting records that don't need cleaning up after or other actions to be taken.
|
||||
# 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.
|
||||
#
|
||||
# Objects are _not_ instantiated with this method, and so +:dependent+ rules
|
||||
# defined on associations are not honered.
|
||||
# You can delete multiple rows at once by passing an Array of <tt>id</tt>s.
|
||||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +id+ - Can be either an Integer or an Array of Integers.
|
||||
# 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 object
|
||||
# # Delete a single row
|
||||
# Todo.delete(1)
|
||||
#
|
||||
# # Delete multiple objects
|
||||
# todos = [1,2,3]
|
||||
# Todo.delete(todos)
|
||||
# # Delete multiple rows
|
||||
# Todo.delete([2,3,4])
|
||||
def delete(id)
|
||||
delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
|
||||
end
|
||||
|
@ -778,8 +813,7 @@ module ActiveRecord #:nodoc:
|
|||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions.
|
||||
# What goes into the SET clause.
|
||||
# * +updates+ - A string of column and value pairs that will be set on any records that match conditions. This creates the SET clause of the generated SQL.
|
||||
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro for more info.
|
||||
# * +options+ - Additional options are <tt>:limit</tt> and <tt>:order</tt>, see the examples for usage.
|
||||
#
|
||||
|
@ -817,25 +851,32 @@ module ActiveRecord #:nodoc:
|
|||
connection.update(sql, "#{name} Update")
|
||||
end
|
||||
|
||||
# Destroys the records matching +conditions+ by instantiating each record and calling their +destroy+ method.
|
||||
# This means at least 2*N database queries to destroy N records, so avoid +destroy_all+ if you are deleting
|
||||
# many records. If you want to simply delete records without worrying about dependent associations or
|
||||
# callbacks, use the much faster +delete_all+ method instead.
|
||||
# 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+ - Conditions are specified the same way as with +find+ method.
|
||||
# * +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.
|
||||
#
|
||||
# ==== Example
|
||||
# ==== Examples
|
||||
#
|
||||
# Person.destroy_all("last_login < '2004-04-04'")
|
||||
#
|
||||
# This loads and destroys each person one by one, including its dependent associations and before_ and
|
||||
# after_destroy callbacks.
|
||||
#
|
||||
# +conditions+ can be anything that +find+ also accepts:
|
||||
#
|
||||
# Person.destroy_all(:last_login => 6.hours.ago)
|
||||
# Person.destroy_all(:status => "inactive")
|
||||
def destroy_all(conditions = nil)
|
||||
find(:all, :conditions => conditions).each { |object| object.destroy }
|
||||
end
|
||||
|
@ -886,7 +927,7 @@ module ActiveRecord #:nodoc:
|
|||
#
|
||||
# ==== Parameters
|
||||
#
|
||||
# * +id+ - The id of the object you wish to update a counter on.
|
||||
# * +id+ - The id of the object you wish to update a counter on or an Array of ids.
|
||||
# * +counters+ - An Array of Hashes containing the names of the fields
|
||||
# to update as keys and the amount to update the field by as values.
|
||||
#
|
||||
|
@ -900,12 +941,27 @@ module ActiveRecord #:nodoc:
|
|||
# # SET comment_count = comment_count - 1,
|
||||
# # action_count = action_count + 1
|
||||
# # WHERE id = 5
|
||||
#
|
||||
# # For the Posts with id of 10 and 15, increment the comment_count by 1
|
||||
# Post.update_counters [10, 15], :comment_count => 1
|
||||
# # Executes the following SQL:
|
||||
# # UPDATE posts
|
||||
# # SET comment_count = comment_count + 1,
|
||||
# # WHERE id IN (10, 15)
|
||||
def update_counters(id, counters)
|
||||
updates = counters.inject([]) { |list, (counter_name, increment)|
|
||||
sign = increment < 0 ? "-" : "+"
|
||||
list << "#{connection.quote_column_name(counter_name)} = COALESCE(#{connection.quote_column_name(counter_name)}, 0) #{sign} #{increment.abs}"
|
||||
}.join(", ")
|
||||
update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
|
||||
|
||||
if id.is_a?(Array)
|
||||
ids_list = id.map {|i| quote_value(i)}.join(', ')
|
||||
condition = "IN (#{ids_list})"
|
||||
else
|
||||
condition = "= #{quote_value(id)}"
|
||||
end
|
||||
|
||||
update_all(updates, "#{connection.quote_column_name(primary_key)} #{condition}")
|
||||
end
|
||||
|
||||
# Increment a number field by one, usually representing a count.
|
||||
|
@ -1384,8 +1440,8 @@ module ActiveRecord #:nodoc:
|
|||
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
||||
if logger && logger.level <= log_level
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
||||
logger.add(log_level, "#{title} (#{'%.1f' % (seconds * 1000)}ms)")
|
||||
ms = Benchmark.ms { result = use_silence ? silence { yield } : yield }
|
||||
logger.add(log_level, '%s (%.1fms)' % [title, ms])
|
||||
result
|
||||
else
|
||||
yield
|
||||
|
@ -1424,7 +1480,10 @@ module ActiveRecord #:nodoc:
|
|||
def respond_to?(method_id, include_private = false)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
return true if all_attributes_exists?(match.attribute_names)
|
||||
end
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
|
@ -1462,11 +1521,16 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
if scoped?(:find, :order)
|
||||
scoped_order = reverse_sql_order(scope(:find, :order))
|
||||
scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
|
||||
scope = scope(:find)
|
||||
original_scoped_order = scope[:order]
|
||||
scope[:order] = reverse_sql_order(original_scoped_order)
|
||||
end
|
||||
|
||||
find_initial(options.merge({ :order => order }))
|
||||
begin
|
||||
find_initial(options.merge({ :order => order }))
|
||||
ensure
|
||||
scope[:order] = original_scoped_order if original_scoped_order
|
||||
end
|
||||
end
|
||||
|
||||
def reverse_sql_order(order_query)
|
||||
|
@ -1628,7 +1692,7 @@ module ActiveRecord #:nodoc:
|
|||
add_joins!(sql, options[:joins], scope)
|
||||
add_conditions!(sql, options[:conditions], scope)
|
||||
|
||||
add_group!(sql, options[:group], scope)
|
||||
add_group!(sql, options[:group], options[:having], scope)
|
||||
add_order!(sql, options[:order], scope)
|
||||
add_limit!(sql, options, scope)
|
||||
add_lock!(sql, options, scope)
|
||||
|
@ -1651,7 +1715,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
join
|
||||
end
|
||||
joins.flatten.uniq
|
||||
joins.flatten.map{|j| j.strip}.uniq
|
||||
else
|
||||
joins.collect{|j| safe_to_array(j)}.flatten.uniq
|
||||
end
|
||||
|
@ -1684,13 +1748,15 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def add_group!(sql, group, scope = :auto)
|
||||
def add_group!(sql, group, having, scope = :auto)
|
||||
if group
|
||||
sql << " GROUP BY #{group}"
|
||||
sql << " HAVING #{having}" if having
|
||||
else
|
||||
scope = scope(:find) if :auto == scope
|
||||
if scope && (scoped_group = scope[:group])
|
||||
sql << " GROUP BY #{scoped_group}"
|
||||
sql << " HAVING #{scoped_having}" if (scoped_having = scope[:having])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1760,17 +1826,19 @@ module ActiveRecord #:nodoc:
|
|||
table_name
|
||||
end
|
||||
|
||||
# Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
|
||||
# find(:first, :conditions => ["user_name = ?", user_name]) and find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])
|
||||
# respectively. Also works for find(:all) by using find_all_by_amount(50) that is turned into find(:all, :conditions => ["amount = ?", 50]).
|
||||
# Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
|
||||
# that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
|
||||
# <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
|
||||
# <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
|
||||
#
|
||||
# It's even possible to use all the additional parameters to find. For example, the full interface for find_all_by_amount
|
||||
# is actually find_all_by_amount(amount, options).
|
||||
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
|
||||
# is actually <tt>find_all_by_amount(amount, options)</tt>.
|
||||
#
|
||||
# This also enables you to initialize a record if it is not found, such as find_or_initialize_by_amount(amount)
|
||||
# or find_or_create_by_user_and_password(user, password).
|
||||
# Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
|
||||
# are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
|
||||
# respectively.
|
||||
#
|
||||
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
|
||||
# attempts to use it do not run through method_missing.
|
||||
def method_missing(method_id, *arguments, &block)
|
||||
if match = DynamicFinderMatch.match(method_id)
|
||||
|
@ -1779,10 +1847,31 @@ module ActiveRecord #:nodoc:
|
|||
if match.finder?
|
||||
finder = match.finder
|
||||
bang = match.bang?
|
||||
# def self.find_by_login_and_activated(*args)
|
||||
# options = args.extract_options!
|
||||
# attributes = construct_attributes_from_arguments(
|
||||
# [:login,:activated],
|
||||
# args
|
||||
# )
|
||||
# finder_options = { :conditions => attributes }
|
||||
# validate_find_options(options)
|
||||
# set_readonly_option!(options)
|
||||
#
|
||||
# if options[:conditions]
|
||||
# with_scope(:find => finder_options) do
|
||||
# find(:first, options)
|
||||
# end
|
||||
# else
|
||||
# find(:first, options.merge(finder_options))
|
||||
# end
|
||||
# end
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args)
|
||||
options = args.extract_options!
|
||||
attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
|
||||
attributes = construct_attributes_from_arguments(
|
||||
[:#{attribute_names.join(',:')}],
|
||||
args
|
||||
)
|
||||
finder_options = { :conditions => attributes }
|
||||
validate_find_options(options)
|
||||
set_readonly_option!(options)
|
||||
|
@ -1794,12 +1883,37 @@ module ActiveRecord #:nodoc:
|
|||
else
|
||||
find(:#{finder}, options.merge(finder_options))
|
||||
end
|
||||
#{'result || raise(RecordNotFound)' if bang}
|
||||
#{'result || raise(RecordNotFound, "Couldn\'t find #{name} with #{attributes.to_a.collect {|pair| "#{pair.first} = #{pair.second}"}.join(\', \')}")' if bang}
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments)
|
||||
elsif match.instantiator?
|
||||
instantiator = match.instantiator
|
||||
# def self.find_or_create_by_user_id(*args)
|
||||
# guard_protected_attributes = false
|
||||
#
|
||||
# if args[0].is_a?(Hash)
|
||||
# guard_protected_attributes = true
|
||||
# attributes = args[0].with_indifferent_access
|
||||
# find_attributes = attributes.slice(*[:user_id])
|
||||
# else
|
||||
# find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
|
||||
# end
|
||||
#
|
||||
# options = { :conditions => find_attributes }
|
||||
# set_readonly_option!(options)
|
||||
#
|
||||
# record = find(:first, options)
|
||||
#
|
||||
# if record.nil?
|
||||
# record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
|
||||
# yield(record) if block_given?
|
||||
# record.save
|
||||
# record
|
||||
# else
|
||||
# record
|
||||
# end
|
||||
# end
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args)
|
||||
guard_protected_attributes = false
|
||||
|
@ -1829,6 +1943,22 @@ module ActiveRecord #:nodoc:
|
|||
}, __FILE__, __LINE__
|
||||
send(method_id, *arguments, &block)
|
||||
end
|
||||
elsif match = DynamicScopeMatch.match(method_id)
|
||||
attribute_names = match.attribute_names
|
||||
super unless all_attributes_exists?(attribute_names)
|
||||
if match.scope?
|
||||
self.class_eval %{
|
||||
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
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__
|
||||
send(method_id, *arguments)
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
@ -1924,7 +2054,11 @@ module ActiveRecord #:nodoc:
|
|||
# end
|
||||
#
|
||||
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
|
||||
# <tt>:conditions</tt> and <tt>:include</tt> options in <tt>:find</tt>, which are merged.
|
||||
# <tt>:conditions</tt>, <tt>:include</tt>, and <tt>:joins</tt> options in <tt>:find</tt>, which are merged.
|
||||
#
|
||||
# <tt>:joins</tt> options are uniqued so multiple scopes can join in the same table without table aliasing
|
||||
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
|
||||
# array of strings format for your joins.
|
||||
#
|
||||
# class Article < ActiveRecord::Base
|
||||
# def self.find_with_scope
|
||||
|
@ -1978,7 +2112,11 @@ module ActiveRecord #:nodoc:
|
|||
(hash[method].keys + params.keys).uniq.each do |key|
|
||||
merge = hash[method][key] && params[key] # merge if both scopes have the same key
|
||||
if key == :conditions && merge
|
||||
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
||||
if params[key].is_a?(Hash) && hash[method][key].is_a?(Hash)
|
||||
hash[method][key] = merge_conditions(hash[method][key].deep_merge(params[key]))
|
||||
else
|
||||
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
||||
end
|
||||
elsif key == :include && merge
|
||||
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
|
||||
elsif key == :joins && merge
|
||||
|
@ -1988,7 +2126,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
else
|
||||
hash[method] = params.merge(hash[method])
|
||||
hash[method] = hash[method].merge(params)
|
||||
end
|
||||
else
|
||||
hash[method] = params
|
||||
|
@ -2016,6 +2154,16 @@ module ActiveRecord #:nodoc:
|
|||
@@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
|
||||
end
|
||||
|
||||
# Sets the default options for the model. The format of the
|
||||
# <tt>options</tt> argument is the same as in find.
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# default_scope :order => 'last_name, first_name'
|
||||
# end
|
||||
def default_scope(options = {})
|
||||
self.default_scoping << { :find => options, :create => (options.is_a?(Hash) && options.has_key?(:conditions)) ? options[:conditions] : {} }
|
||||
end
|
||||
|
||||
# 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])
|
||||
|
@ -2031,14 +2179,14 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
def scoped_methods #:nodoc:
|
||||
Thread.current[:"#{self}_scoped_methods"] ||= []
|
||||
Thread.current[:"#{self}_scoped_methods"] ||= self.default_scoping.dup
|
||||
end
|
||||
|
||||
def current_scoped_methods #:nodoc:
|
||||
scoped_methods.last
|
||||
end
|
||||
|
||||
# Returns the class type of the record using the current module as a prefix. So descendents of
|
||||
# Returns the class type of the record using the current module as a prefix. So descendants of
|
||||
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
||||
def compute_type(type_name)
|
||||
modularized_name = type_name_with_module(type_name)
|
||||
|
@ -2051,7 +2199,8 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Returns the class descending directly from Active Record in the inheritance hierarchy.
|
||||
# Returns the class descending directly from ActiveRecord::Base or an
|
||||
# abstract class, if any, in the inheritance hierarchy.
|
||||
def class_of_active_record_descendant(klass)
|
||||
if klass.superclass == Base || klass.superclass.abstract_class?
|
||||
klass
|
||||
|
@ -2245,7 +2394,7 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
|
||||
:order, :select, :readonly, :group, :from, :lock ]
|
||||
:order, :select, :readonly, :group, :having, :from, :lock ]
|
||||
|
||||
def validate_find_options(options) #:nodoc:
|
||||
options.assert_valid_keys(VALID_FIND_OPTIONS)
|
||||
|
@ -2303,15 +2452,15 @@ module ActiveRecord #:nodoc:
|
|||
# object. The default implementation returns this record's id as a String,
|
||||
# or nil if this record's unsaved.
|
||||
#
|
||||
# For example, suppose that you have a Users model, and that you have a
|
||||
# <tt>map.resources :users</tt> route. Normally, +users_path+ will
|
||||
# construct an URI with the user object's 'id' in it:
|
||||
# For example, suppose that you have a User model, and that you have a
|
||||
# <tt>map.resources :users</tt> route. Normally, +user_path+ will
|
||||
# construct a path with the user object's 'id' in it:
|
||||
#
|
||||
# user = User.find_by_name('Phusion')
|
||||
# user_path(path) # => "/users/1"
|
||||
# user_path(user) # => "/users/1"
|
||||
#
|
||||
# You can override +to_param+ in your model to make +users_path+ construct
|
||||
# an URI using the user's name instead of the user's id:
|
||||
# You can override +to_param+ in your model to make +user_path+ construct
|
||||
# a path using the user's name instead of the user's id:
|
||||
#
|
||||
# class User < ActiveRecord::Base
|
||||
# def to_param # overridden
|
||||
|
@ -2320,7 +2469,7 @@ module ActiveRecord #:nodoc:
|
|||
# end
|
||||
#
|
||||
# user = User.find_by_name('Phusion')
|
||||
# user_path(path) # => "/users/Phusion"
|
||||
# user_path(user) # => "/users/Phusion"
|
||||
def to_param
|
||||
# We can't use alias_method here, because method 'id' optimizes itself on the fly.
|
||||
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
|
||||
|
@ -2357,9 +2506,9 @@ module ActiveRecord #:nodoc:
|
|||
write_attribute(self.class.primary_key, value)
|
||||
end
|
||||
|
||||
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
|
||||
# Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet; otherwise, returns false.
|
||||
def new_record?
|
||||
defined?(@new_record) && @new_record
|
||||
@new_record || false
|
||||
end
|
||||
|
||||
# :call-seq:
|
||||
|
@ -2400,14 +2549,16 @@ module ActiveRecord #:nodoc:
|
|||
create_or_update || raise(RecordNotSaved)
|
||||
end
|
||||
|
||||
# Deletes the record in the database and freezes this instance to reflect that no changes should
|
||||
# be made (since they can't be persisted).
|
||||
# Deletes the record in the database and freezes this instance to
|
||||
# reflect that no changes should be made (since they can't be
|
||||
# persisted). Returns the frozen instance.
|
||||
#
|
||||
# Unlike #destroy, this method doesn't run any +before_delete+ and +after_delete+
|
||||
# callbacks, nor will it enforce any association +:dependent+ rules.
|
||||
#
|
||||
# In addition to deleting this record, any defined +before_delete+ and +after_delete+
|
||||
# callbacks are run, and +:dependent+ rules defined on associations are run.
|
||||
# The row is simply removed with a SQL +DELETE+ statement on the
|
||||
# record's primary key, and no callbacks are executed.
|
||||
#
|
||||
# To enforce the object's +before_destroy+ and +after_destroy+
|
||||
# callbacks, Observer methods, or any <tt>:dependent</tt> association
|
||||
# options, use <tt>#destroy</tt>.
|
||||
def delete
|
||||
self.class.delete(id) unless new_record?
|
||||
freeze
|
||||
|
@ -2608,7 +2759,19 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
# Format attributes nicely for inspect.
|
||||
# Returns an <tt>#inspect</tt>-like string for the value of the
|
||||
# attribute +attr_name+. String attributes are elided after 50
|
||||
# characters, and Date and Time attributes are returned in the
|
||||
# <tt>:db</tt> format. Other attributes return the value of
|
||||
# <tt>#inspect</tt> without modification.
|
||||
#
|
||||
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
||||
#
|
||||
# person.attribute_for_inspect(:name)
|
||||
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
||||
#
|
||||
# person.attribute_for_inspect(:created_at)
|
||||
# # => '"2009-01-12 04:48:57"'
|
||||
def attribute_for_inspect(attr_name)
|
||||
value = read_attribute(attr_name)
|
||||
|
||||
|
@ -2737,7 +2900,7 @@ module ActiveRecord #:nodoc:
|
|||
id
|
||||
end
|
||||
|
||||
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
|
||||
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendant.
|
||||
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
|
||||
# set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
|
||||
# Message class in that example.
|
||||
|
@ -2964,4 +3127,23 @@ module ActiveRecord #:nodoc:
|
|||
value
|
||||
end
|
||||
end
|
||||
|
||||
Base.class_eval do
|
||||
extend QueryCache::ClassMethods
|
||||
include Validations
|
||||
include Locking::Optimistic, Locking::Pessimistic
|
||||
include AttributeMethods
|
||||
include Dirty
|
||||
include Callbacks, Observing, Timestamp
|
||||
include Associations, AssociationPreload, NamedScope
|
||||
|
||||
# AutosaveAssociation needs to be included before Transactions, because we want
|
||||
# #save_with_autosave_associations to be wrapped inside a transaction.
|
||||
include AutosaveAssociation, NestedAttributes
|
||||
|
||||
include Aggregations, Transactions, Reflection, Calculations, Serialization
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Remove this and make it work with LAZY flag
|
||||
require 'active_record/connection_adapters/abstract_adapter'
|
||||
|
|
|
@ -48,30 +48,38 @@ module ActiveRecord
|
|||
calculate(:count, *construct_count_options_from_args(*args))
|
||||
end
|
||||
|
||||
# Calculates the average value on a given column. The value is returned as a float. See +calculate+ for examples with options.
|
||||
# Calculates the average value on a given column. The value is returned as
|
||||
# a float, or +nil+ if there's no row. See +calculate+ for examples with
|
||||
# options.
|
||||
#
|
||||
# Person.average('age')
|
||||
# Person.average('age') # => 35.8
|
||||
def average(column_name, options = {})
|
||||
calculate(:avg, column_name, options)
|
||||
end
|
||||
|
||||
# Calculates the minimum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
|
||||
# Calculates the minimum value on a given column. The value is returned
|
||||
# with the same data type of the column, or +nil+ if there's no row. See
|
||||
# +calculate+ for examples with options.
|
||||
#
|
||||
# Person.minimum('age')
|
||||
# Person.minimum('age') # => 7
|
||||
def minimum(column_name, options = {})
|
||||
calculate(:min, column_name, options)
|
||||
end
|
||||
|
||||
# Calculates the maximum value on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
|
||||
# Calculates the maximum value on a given column. The value is returned
|
||||
# with the same data type of the column, or +nil+ if there's no row. See
|
||||
# +calculate+ for examples with options.
|
||||
#
|
||||
# Person.maximum('age')
|
||||
# Person.maximum('age') # => 93
|
||||
def maximum(column_name, options = {})
|
||||
calculate(:max, column_name, options)
|
||||
end
|
||||
|
||||
# Calculates the sum of values on a given column. The value is returned with the same data type of the column. See +calculate+ for examples with options.
|
||||
# Calculates the sum of values on a given column. The value is returned
|
||||
# with the same data type of the column, 0 if there's no row. See
|
||||
# +calculate+ for examples with options.
|
||||
#
|
||||
# Person.sum('age')
|
||||
# Person.sum('age') # => 4562
|
||||
def sum(column_name, options = {})
|
||||
calculate(:sum, column_name, options)
|
||||
end
|
||||
|
|
|
@ -77,7 +77,7 @@ module ActiveRecord
|
|||
#
|
||||
# In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+. So, use the callback macros when
|
||||
# you want to ensure that a certain callback is called for the entire hierarchy, and use the regular overwriteable methods
|
||||
# when you want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
|
||||
# when you want to leave it up to each descendant to decide whether they want to call +super+ and trigger the inherited callbacks.
|
||||
#
|
||||
# *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
|
||||
# associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
|
||||
|
@ -219,8 +219,9 @@ module ActiveRecord
|
|||
def after_save() end
|
||||
def create_or_update_with_callbacks #:nodoc:
|
||||
return false if callback(:before_save) == false
|
||||
result = create_or_update_without_callbacks
|
||||
callback(:after_save)
|
||||
if result = create_or_update_without_callbacks
|
||||
callback(:after_save)
|
||||
end
|
||||
result
|
||||
end
|
||||
private :create_or_update_with_callbacks
|
||||
|
|
|
@ -7,6 +7,8 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The connection handler
|
||||
cattr_accessor :connection_handler, :instance_writer => false
|
||||
@@connection_handler = ConnectionAdapters::ConnectionHandler.new
|
||||
|
@ -121,6 +123,7 @@ module ActiveRecord
|
|||
connection_handler.retrieve_connection(self)
|
||||
end
|
||||
|
||||
# Returns true if +ActiveRecord+ is connected.
|
||||
def connected?
|
||||
connection_handler.connected?(self)
|
||||
end
|
||||
|
|
|
@ -31,13 +31,13 @@ module ActiveRecord
|
|||
# Returns an array of arrays containing the field values.
|
||||
# Order is the same as that returned by +columns+.
|
||||
def select_rows(sql, name = nil)
|
||||
raise NotImplementedError, "select_rows is an abstract method"
|
||||
end
|
||||
undef_method :select_rows
|
||||
|
||||
# Executes the SQL statement in the context of this connection.
|
||||
def execute(sql, name = nil)
|
||||
raise NotImplementedError, "execute is an abstract method"
|
||||
def execute(sql, name = nil, skip_logging = false)
|
||||
end
|
||||
undef_method :execute
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
|
@ -53,36 +53,124 @@ module ActiveRecord
|
|||
def delete(sql, name = nil)
|
||||
delete_sql(sql, name)
|
||||
end
|
||||
|
||||
# Checks whether there is currently no transaction active. This is done
|
||||
# by querying the database driver, and does not use the transaction
|
||||
# house-keeping information recorded by #increment_open_transactions and
|
||||
# friends.
|
||||
#
|
||||
# Returns true if there is no transaction active, false if there is a
|
||||
# transaction active, and nil if this information is unknown.
|
||||
#
|
||||
# Not all adapters supports transaction state introspection. Currently,
|
||||
# only the PostgreSQL adapter supports this.
|
||||
def outside_transaction?
|
||||
nil
|
||||
end
|
||||
|
||||
# Runs the given block in a database transaction, and returns the result
|
||||
# of the block.
|
||||
#
|
||||
# == Nested transactions support
|
||||
#
|
||||
# Most databases don't support true nested transactions. At the time of
|
||||
# writing, the only database that supports true nested transactions that
|
||||
# we're aware of, is MS-SQL.
|
||||
#
|
||||
# In order to get around this problem, #transaction will emulate the effect
|
||||
# of nested transactions, by using savepoints:
|
||||
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
|
||||
# Savepoints are supported by MySQL and PostgreSQL, but not SQLite3.
|
||||
#
|
||||
# It is safe to call this method if a database transaction is already open,
|
||||
# i.e. if #transaction is called within another #transaction block. In case
|
||||
# of a nested call, #transaction will behave as follows:
|
||||
#
|
||||
# - The block will be run without doing anything. All database statements
|
||||
# that happen within the block are effectively appended to the already
|
||||
# open database transaction.
|
||||
# - However, if +:requires_new+ is set, the block will be wrapped in a
|
||||
# database savepoint acting as a sub-transaction.
|
||||
#
|
||||
# === Caveats
|
||||
#
|
||||
# MySQL doesn't support DDL transactions. If you perform a DDL operation,
|
||||
# then any created savepoints will be automatically released. For example,
|
||||
# if you've created a savepoint, then you execute a CREATE TABLE statement,
|
||||
# then the savepoint that was created will be automatically released.
|
||||
#
|
||||
# This means that, on MySQL, you shouldn't execute DDL operations inside
|
||||
# a #transaction call that you know might create a savepoint. Otherwise,
|
||||
# #transaction will raise exceptions when it tries to release the
|
||||
# already-automatically-released savepoints:
|
||||
#
|
||||
# Model.connection.transaction do # BEGIN
|
||||
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
|
||||
# Model.connection.create_table(...)
|
||||
# # active_record_1 now automatically released
|
||||
# end # RELEASE SAVEPOINT active_record_1 <--- BOOM! database error!
|
||||
# end
|
||||
def transaction(options = {})
|
||||
options.assert_valid_keys :requires_new, :joinable
|
||||
|
||||
last_transaction_joinable = @transaction_joinable
|
||||
if options.has_key?(:joinable)
|
||||
@transaction_joinable = options[:joinable]
|
||||
else
|
||||
@transaction_joinable = true
|
||||
end
|
||||
requires_new = options[:requires_new] || !last_transaction_joinable
|
||||
|
||||
# Wrap a block in a transaction. Returns result of block.
|
||||
def transaction(start_db_transaction = true)
|
||||
transaction_open = false
|
||||
begin
|
||||
if block_given?
|
||||
if start_db_transaction
|
||||
begin_db_transaction
|
||||
if requires_new || open_transactions == 0
|
||||
if open_transactions == 0
|
||||
begin_db_transaction
|
||||
elsif requires_new
|
||||
create_savepoint
|
||||
end
|
||||
increment_open_transactions
|
||||
transaction_open = true
|
||||
end
|
||||
yield
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
if transaction_open
|
||||
if transaction_open && !outside_transaction?
|
||||
transaction_open = false
|
||||
rollback_db_transaction
|
||||
decrement_open_transactions
|
||||
if open_transactions == 0
|
||||
rollback_db_transaction
|
||||
else
|
||||
rollback_to_savepoint
|
||||
end
|
||||
end
|
||||
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
||||
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
|
||||
end
|
||||
ensure
|
||||
if transaction_open
|
||||
@transaction_joinable = last_transaction_joinable
|
||||
|
||||
if outside_transaction?
|
||||
@open_transactions = 0
|
||||
elsif transaction_open
|
||||
decrement_open_transactions
|
||||
begin
|
||||
commit_db_transaction
|
||||
if open_transactions == 0
|
||||
commit_db_transaction
|
||||
else
|
||||
release_savepoint
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction
|
||||
if open_transactions == 0
|
||||
rollback_db_transaction
|
||||
else
|
||||
rollback_to_savepoint
|
||||
end
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Begins the transaction (and turns off auto-committing).
|
||||
def begin_db_transaction() end
|
||||
|
||||
|
@ -163,8 +251,8 @@ module ActiveRecord
|
|||
# Returns an array of record hashes with the column names as keys and
|
||||
# column values as values.
|
||||
def select(sql, name = nil)
|
||||
raise NotImplementedError, "select is an abstract method"
|
||||
end
|
||||
undef_method :select
|
||||
|
||||
# Returns the last auto-generated ID from the affected table.
|
||||
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
|
||||
|
|
|
@ -14,12 +14,12 @@ module ActiveRecord
|
|||
def dirties_query_cache(base, *method_names)
|
||||
method_names.each do |method_name|
|
||||
base.class_eval <<-end_code, __FILE__, __LINE__
|
||||
def #{method_name}_with_query_dirty(*args)
|
||||
clear_query_cache if @query_cache_enabled
|
||||
#{method_name}_without_query_dirty(*args)
|
||||
end
|
||||
|
||||
alias_method_chain :#{method_name}, :query_dirty
|
||||
def #{method_name}_with_query_dirty(*args) # def update_with_query_dirty(*args)
|
||||
clear_query_cache if @query_cache_enabled # clear_query_cache if @query_cache_enabled
|
||||
#{method_name}_without_query_dirty(*args) # update_without_query_dirty(*args)
|
||||
end # end
|
||||
#
|
||||
alias_method_chain :#{method_name}, :query_dirty # alias_method_chain :update, :query_dirty
|
||||
end_code
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,10 +32,12 @@ module ActiveRecord
|
|||
@primary = nil
|
||||
end
|
||||
|
||||
# Returns +true+ if the column is either of type string or text.
|
||||
def text?
|
||||
type == :string || type == :text
|
||||
end
|
||||
|
||||
# Returns +true+ if the column is either of type integer, float or decimal.
|
||||
def number?
|
||||
type == :integer || type == :float || type == :decimal
|
||||
end
|
||||
|
@ -295,7 +297,7 @@ module ActiveRecord
|
|||
# puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
|
||||
# end
|
||||
# end
|
||||
#
|
||||
#
|
||||
# def self.down
|
||||
# ...
|
||||
# end
|
||||
|
@ -474,12 +476,12 @@ module ActiveRecord
|
|||
|
||||
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
|
||||
class_eval <<-EOV
|
||||
def #{column_type}(*args)
|
||||
options = args.extract_options!
|
||||
column_names = args
|
||||
|
||||
column_names.each { |name| column(name, '#{column_type}', options) }
|
||||
end
|
||||
def #{column_type}(*args) # def string(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
column_names = args # column_names = args
|
||||
#
|
||||
column_names.each { |name| column(name, '#{column_type}', options) } # column_names.each { |name| column(name, 'string', options) }
|
||||
end # end
|
||||
EOV
|
||||
end
|
||||
|
||||
|
@ -674,24 +676,24 @@ module ActiveRecord
|
|||
# t.string(:goat, :sheep)
|
||||
%w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
|
||||
class_eval <<-EOV
|
||||
def #{column_type}(*args)
|
||||
options = args.extract_options!
|
||||
column_names = args
|
||||
|
||||
column_names.each do |name|
|
||||
column = ColumnDefinition.new(@base, name, '#{column_type}')
|
||||
if options[:limit]
|
||||
column.limit = options[:limit]
|
||||
elsif native['#{column_type}'.to_sym].is_a?(Hash)
|
||||
column.limit = native['#{column_type}'.to_sym][:limit]
|
||||
end
|
||||
column.precision = options[:precision]
|
||||
column.scale = options[:scale]
|
||||
column.default = options[:default]
|
||||
column.null = options[:null]
|
||||
@base.add_column(@table_name, name, column.sql_type, options)
|
||||
end
|
||||
end
|
||||
def #{column_type}(*args) # def string(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
column_names = args # column_names = args
|
||||
#
|
||||
column_names.each do |name| # column_names.each do |name|
|
||||
column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
|
||||
if options[:limit] # if options[:limit]
|
||||
column.limit = options[:limit] # column.limit = options[:limit]
|
||||
elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
|
||||
column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
|
||||
end # end
|
||||
column.precision = options[:precision] # column.precision = options[:precision]
|
||||
column.scale = options[:scale] # column.scale = options[:scale]
|
||||
column.default = options[:default] # column.default = options[:default]
|
||||
column.null = options[:null] # column.null = options[:null]
|
||||
@base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
|
||||
end # end
|
||||
end # end
|
||||
EOV
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ require 'date'
|
|||
require 'bigdecimal'
|
||||
require 'bigdecimal/util'
|
||||
|
||||
# TODO: Autoload these files
|
||||
require 'active_record/connection_adapters/abstract/schema_definitions'
|
||||
require 'active_record/connection_adapters/abstract/schema_statements'
|
||||
require 'active_record/connection_adapters/abstract/database_statements'
|
||||
|
@ -65,6 +66,12 @@ module ActiveRecord
|
|||
def supports_ddl_transactions?
|
||||
false
|
||||
end
|
||||
|
||||
# Does this adapter support savepoints? PostgreSQL and MySQL do, SQLite
|
||||
# does not.
|
||||
def supports_savepoints?
|
||||
false
|
||||
end
|
||||
|
||||
# Should primary key values be selected from their corresponding
|
||||
# sequence before the insert statement? If true, next_sequence_value
|
||||
|
@ -159,9 +166,26 @@ module ActiveRecord
|
|||
@open_transactions -= 1
|
||||
end
|
||||
|
||||
def log_info(sql, name, seconds)
|
||||
def transaction_joinable=(joinable)
|
||||
@transaction_joinable = joinable
|
||||
end
|
||||
|
||||
def create_savepoint
|
||||
end
|
||||
|
||||
def rollback_to_savepoint
|
||||
end
|
||||
|
||||
def release_savepoint
|
||||
end
|
||||
|
||||
def current_savepoint_name
|
||||
"active_record_#{open_transactions}"
|
||||
end
|
||||
|
||||
def log_info(sql, name, ms)
|
||||
if @logger && @logger.debug?
|
||||
name = "#{name.nil? ? "SQL" : name} (#{sprintf("%.1f", seconds * 1000)}ms)"
|
||||
name = '%s (%.1fms)' % [name || 'SQL', ms]
|
||||
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
|
||||
end
|
||||
end
|
||||
|
@ -170,9 +194,9 @@ module ActiveRecord
|
|||
def log(sql, name)
|
||||
if block_given?
|
||||
result = nil
|
||||
seconds = Benchmark.realtime { result = yield }
|
||||
@runtime += seconds
|
||||
log_info(sql, name, seconds)
|
||||
ms = Benchmark.ms { result = yield }
|
||||
@runtime += ms
|
||||
log_info(sql, name, ms)
|
||||
result
|
||||
else
|
||||
log_info(sql, name, 0)
|
||||
|
|
|
@ -13,23 +13,25 @@ module MysqlCompat #:nodoc:
|
|||
# C driver >= 2.7 returns null values in each_hash
|
||||
if Mysql.const_defined?(:VERSION) && (Mysql::VERSION.is_a?(String) || Mysql::VERSION >= 20700)
|
||||
target.class_eval <<-'end_eval'
|
||||
def all_hashes
|
||||
rows = []
|
||||
each_hash { |row| rows << row }
|
||||
rows
|
||||
end
|
||||
def all_hashes # def all_hashes
|
||||
rows = [] # rows = []
|
||||
each_hash { |row| rows << row } # each_hash { |row| rows << row }
|
||||
rows # rows
|
||||
end # end
|
||||
end_eval
|
||||
|
||||
# adapters before 2.7 don't have a version constant
|
||||
# and don't return null values in each_hash
|
||||
else
|
||||
target.class_eval <<-'end_eval'
|
||||
def all_hashes
|
||||
rows = []
|
||||
all_fields = fetch_fields.inject({}) { |fields, f| fields[f.name] = nil; fields }
|
||||
each_hash { |row| rows << all_fields.dup.update(row) }
|
||||
rows
|
||||
end
|
||||
def all_hashes # def all_hashes
|
||||
rows = [] # rows = []
|
||||
all_fields = fetch_fields.inject({}) { |fields, f| # all_fields = fetch_fields.inject({}) { |fields, f|
|
||||
fields[f.name] = nil; fields # fields[f.name] = nil; fields
|
||||
} # }
|
||||
each_hash { |row| rows << all_fields.dup.update(row) } # each_hash { |row| rows << all_fields.dup.update(row) }
|
||||
rows # rows
|
||||
end # end
|
||||
end_eval
|
||||
end
|
||||
|
||||
|
@ -150,19 +152,23 @@ module ActiveRecord
|
|||
# * <tt>:password</tt> - Defaults to nothing.
|
||||
# * <tt>:database</tt> - The name of the database. No default, must be provided.
|
||||
# * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
|
||||
# * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
|
||||
# * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
|
||||
# * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
|
||||
#
|
||||
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
||||
# as boolean. If you wish to disable this emulation (which was the default
|
||||
# behavior in versions 0.13.1 and earlier) you can add the following line
|
||||
# to your environment.rb file:
|
||||
#
|
||||
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
|
||||
class MysqlAdapter < AbstractAdapter
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
|
||||
# as boolean. If you wish to disable this emulation (which was the default
|
||||
# behavior in versions 0.13.1 and earlier) you can add the following line
|
||||
# to your environment.rb file:
|
||||
#
|
||||
# ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
|
||||
cattr_accessor :emulate_booleans
|
||||
self.emulate_booleans = true
|
||||
|
||||
|
@ -205,6 +211,10 @@ module ActiveRecord
|
|||
def supports_migrations? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def supports_savepoints? #:nodoc:
|
||||
true
|
||||
end
|
||||
|
||||
def native_database_types #:nodoc:
|
||||
NATIVE_DATABASE_TYPES
|
||||
|
@ -305,6 +315,7 @@ module ActiveRecord
|
|||
rows
|
||||
end
|
||||
|
||||
# Executes a SQL query and returns a MySQL::Result object. Note that you have to free the Result object after you're done using it.
|
||||
def execute(sql, name = nil) #:nodoc:
|
||||
log(sql, name) { @connection.query(sql) }
|
||||
rescue ActiveRecord::StatementInvalid => exception
|
||||
|
@ -343,6 +354,17 @@ module ActiveRecord
|
|||
# Transactions aren't supported
|
||||
end
|
||||
|
||||
def create_savepoint
|
||||
execute("SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def rollback_to_savepoint
|
||||
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def release_savepoint
|
||||
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def add_limit_offset!(sql, options) #:nodoc:
|
||||
if limit = options[:limit]
|
||||
|
@ -411,7 +433,9 @@ module ActiveRecord
|
|||
|
||||
def tables(name = nil) #:nodoc:
|
||||
tables = []
|
||||
execute("SHOW TABLES", name).each { |field| tables << field[0] }
|
||||
result = execute("SHOW TABLES", name)
|
||||
result.each { |field| tables << field[0] }
|
||||
result.free
|
||||
tables
|
||||
end
|
||||
|
||||
|
@ -422,7 +446,8 @@ module ActiveRecord
|
|||
def indexes(table_name, name = nil)#:nodoc:
|
||||
indexes = []
|
||||
current_index = nil
|
||||
execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name).each do |row|
|
||||
result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
|
||||
result.each do |row|
|
||||
if current_index != row[2]
|
||||
next if row[2] == "PRIMARY" # skip the primary key
|
||||
current_index = row[2]
|
||||
|
@ -431,13 +456,16 @@ module ActiveRecord
|
|||
|
||||
indexes.last.columns << row[4]
|
||||
end
|
||||
result.free
|
||||
indexes
|
||||
end
|
||||
|
||||
def columns(table_name, name = nil)#:nodoc:
|
||||
sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
|
||||
columns = []
|
||||
execute(sql, name).each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
||||
result = execute(sql, name)
|
||||
result.each { |field| columns << MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
|
||||
result.free
|
||||
columns
|
||||
end
|
||||
|
||||
|
@ -518,9 +546,11 @@ module ActiveRecord
|
|||
# Returns a table's primary key and belonging sequence.
|
||||
def pk_and_sequence_for(table) #:nodoc:
|
||||
keys = []
|
||||
execute("describe #{quote_table_name(table)}").each_hash do |h|
|
||||
result = execute("describe #{quote_table_name(table)}")
|
||||
result.each_hash do |h|
|
||||
keys << h["Field"]if h["Key"] == "PRI"
|
||||
end
|
||||
result.free
|
||||
keys.length == 1 ? [keys.first, nil] : nil
|
||||
end
|
||||
|
||||
|
@ -534,8 +564,6 @@ module ActiveRecord
|
|||
|
||||
private
|
||||
def connect
|
||||
@connection.reconnect = true if @connection.respond_to?(:reconnect=)
|
||||
|
||||
encoding = @config[:encoding]
|
||||
if encoding
|
||||
@connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
|
||||
|
@ -546,6 +574,10 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
@connection.real_connect(*@connection_options)
|
||||
|
||||
# reconnect must be set after real_connect is called, because real_connect sets it to false internally
|
||||
@connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
|
||||
|
||||
configure_connection
|
||||
end
|
||||
|
||||
|
|
|
@ -272,6 +272,10 @@ module ActiveRecord
|
|||
def supports_ddl_transactions?
|
||||
true
|
||||
end
|
||||
|
||||
def supports_savepoints?
|
||||
true
|
||||
end
|
||||
|
||||
# Returns the configured supported identifier length supported by PostgreSQL,
|
||||
# or report the default of 63 on PostgreSQL 7.x.
|
||||
|
@ -528,45 +532,26 @@ module ActiveRecord
|
|||
def rollback_db_transaction
|
||||
execute "ROLLBACK"
|
||||
end
|
||||
|
||||
# ruby-pg defines Ruby constants for transaction status,
|
||||
# ruby-postgres does not.
|
||||
PQTRANS_IDLE = defined?(PGconn::PQTRANS_IDLE) ? PGconn::PQTRANS_IDLE : 0
|
||||
|
||||
# Check whether a transaction is active.
|
||||
def transaction_active?
|
||||
@connection.transaction_status != PQTRANS_IDLE
|
||||
end
|
||||
|
||||
# Wrap a block in a transaction. Returns result of block.
|
||||
def transaction(start_db_transaction = true)
|
||||
transaction_open = false
|
||||
begin
|
||||
if block_given?
|
||||
if start_db_transaction
|
||||
begin_db_transaction
|
||||
transaction_open = true
|
||||
end
|
||||
yield
|
||||
end
|
||||
rescue Exception => database_transaction_rollback
|
||||
if transaction_open && transaction_active?
|
||||
transaction_open = false
|
||||
rollback_db_transaction
|
||||
end
|
||||
raise unless database_transaction_rollback.is_a? ActiveRecord::Rollback
|
||||
end
|
||||
ensure
|
||||
if transaction_open && transaction_active?
|
||||
begin
|
||||
commit_db_transaction
|
||||
rescue Exception => database_transaction_rollback
|
||||
rollback_db_transaction
|
||||
raise
|
||||
end
|
||||
|
||||
if defined?(PGconn::PQTRANS_IDLE)
|
||||
# The ruby-pg driver supports inspecting the transaction status,
|
||||
# while the ruby-postgres driver does not.
|
||||
def outside_transaction?
|
||||
@connection.transaction_status == PGconn::PQTRANS_IDLE
|
||||
end
|
||||
end
|
||||
|
||||
def create_savepoint
|
||||
execute("SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def rollback_to_savepoint
|
||||
execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
def release_savepoint
|
||||
execute("RELEASE SAVEPOINT #{current_savepoint_name}")
|
||||
end
|
||||
|
||||
# SCHEMA STATEMENTS ========================================
|
||||
|
||||
|
@ -950,13 +935,13 @@ module ActiveRecord
|
|||
# should know about this but can't detect it there, so deal with it here.
|
||||
money_precision = (postgresql_version >= 80300) ? 19 : 10
|
||||
PostgreSQLColumn.module_eval(<<-end_eval)
|
||||
def extract_precision(sql_type)
|
||||
if sql_type =~ /^money$/
|
||||
#{money_precision}
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
def extract_precision(sql_type) # def extract_precision(sql_type)
|
||||
if sql_type =~ /^money$/ # if sql_type =~ /^money$/
|
||||
#{money_precision} # 19
|
||||
else # else
|
||||
super # super
|
||||
end # end
|
||||
end # end
|
||||
end_eval
|
||||
|
||||
configure_connection
|
||||
|
|
|
@ -306,7 +306,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def copy_table(from, to, options = {}) #:nodoc:
|
||||
options = options.merge(:id => !columns(from).detect{|c| c.name == 'id'}.nil?)
|
||||
options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s))
|
||||
create_table(to, options) do |definition|
|
||||
@definition = definition
|
||||
columns(from).each do |column|
|
||||
|
@ -402,6 +402,10 @@ module ActiveRecord
|
|||
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
|
||||
|
|
|
@ -151,12 +151,12 @@ module ActiveRecord
|
|||
|
||||
def field_changed?(attr, old, value)
|
||||
if column = column_for_attribute(attr)
|
||||
if column.type == :integer && column.null && (old.nil? || old == 0)
|
||||
# For nullable integer columns, NULL gets stored in database for blank (i.e. '') values.
|
||||
if column.number? && column.null && (old.nil? || old == 0) && value.blank?
|
||||
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
||||
# Hence we don't record it as a change if the value changes from nil to ''.
|
||||
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
||||
# be typecast back to 0 (''.to_i => 0)
|
||||
value = nil if value.blank?
|
||||
value = nil
|
||||
else
|
||||
value = column.type_cast(value)
|
||||
end
|
||||
|
@ -174,7 +174,7 @@ module ActiveRecord
|
|||
alias_attribute_without_dirty(new_name, old_name)
|
||||
DIRTY_SUFFIXES.each do |suffix|
|
||||
module_eval <<-STR, __FILE__, __LINE__+1
|
||||
def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end
|
||||
def #{new_name}#{suffix}; self.#{old_name}#{suffix}; end # def subject_changed?; self.title_changed?; end
|
||||
STR
|
||||
end
|
||||
end
|
||||
|
|
25
vendor/rails/activerecord/lib/active_record/dynamic_scope_match.rb
vendored
Normal file
25
vendor/rails/activerecord/lib/active_record/dynamic_scope_match.rb
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
module ActiveRecord
|
||||
class DynamicScopeMatch
|
||||
def self.match(method)
|
||||
ds_match = self.new(method)
|
||||
ds_match.scope ? ds_match : nil
|
||||
end
|
||||
|
||||
def initialize(method)
|
||||
@scope = true
|
||||
case method.to_s
|
||||
when /^scoped_by_([_a-zA-Z]\w*)$/
|
||||
names = $1
|
||||
else
|
||||
@scope = nil
|
||||
end
|
||||
@attribute_names = names && names.split('_and_')
|
||||
end
|
||||
|
||||
attr_reader :scope, :attribute_names
|
||||
|
||||
def scope?
|
||||
!@scope.nil?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,7 @@
|
|||
require 'erb'
|
||||
require 'yaml'
|
||||
require 'csv'
|
||||
require 'active_support/dependencies'
|
||||
require 'active_support/test_case'
|
||||
|
||||
if RUBY_VERSION < '1.9'
|
||||
|
@ -515,7 +516,7 @@ class Fixtures < (RUBY_VERSION < '1.9' ? YAML::Omap : Hash)
|
|||
|
||||
all_loaded_fixtures.update(fixtures_map)
|
||||
|
||||
connection.transaction(connection.open_transactions.zero?) do
|
||||
connection.transaction(:requires_new => true) do
|
||||
fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
|
||||
fixtures.each { |fixture| fixture.insert_fixtures }
|
||||
|
||||
|
@ -813,186 +814,191 @@ class Fixture #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
module Test #:nodoc:
|
||||
module Unit #:nodoc:
|
||||
class TestCase #:nodoc:
|
||||
setup :setup_fixtures
|
||||
teardown :teardown_fixtures
|
||||
module ActiveRecord
|
||||
module TestFixtures
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
setup :setup_fixtures
|
||||
teardown :teardown_fixtures
|
||||
|
||||
superclass_delegating_accessor :fixture_path
|
||||
superclass_delegating_accessor :fixture_table_names
|
||||
superclass_delegating_accessor :fixture_class_names
|
||||
superclass_delegating_accessor :use_transactional_fixtures
|
||||
superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
||||
superclass_delegating_accessor :pre_loaded_fixtures
|
||||
superclass_delegating_accessor :fixture_path
|
||||
superclass_delegating_accessor :fixture_table_names
|
||||
superclass_delegating_accessor :fixture_class_names
|
||||
superclass_delegating_accessor :use_transactional_fixtures
|
||||
superclass_delegating_accessor :use_instantiated_fixtures # true, false, or :no_instances
|
||||
superclass_delegating_accessor :pre_loaded_fixtures
|
||||
|
||||
self.fixture_table_names = []
|
||||
self.use_transactional_fixtures = false
|
||||
self.use_instantiated_fixtures = true
|
||||
self.pre_loaded_fixtures = false
|
||||
self.fixture_table_names = []
|
||||
self.use_transactional_fixtures = false
|
||||
self.use_instantiated_fixtures = true
|
||||
self.pre_loaded_fixtures = false
|
||||
|
||||
@@already_loaded_fixtures = {}
|
||||
self.fixture_class_names = {}
|
||||
|
||||
class << self
|
||||
def set_fixture_class(class_names = {})
|
||||
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
||||
end
|
||||
|
||||
def fixtures(*table_names)
|
||||
if table_names.first == :all
|
||||
table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
|
||||
table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
|
||||
else
|
||||
table_names = table_names.flatten.map { |n| n.to_s }
|
||||
end
|
||||
|
||||
self.fixture_table_names |= table_names
|
||||
require_fixture_classes(table_names)
|
||||
setup_fixture_accessors(table_names)
|
||||
end
|
||||
|
||||
def try_to_load_dependency(file_name)
|
||||
require_dependency file_name
|
||||
rescue LoadError => e
|
||||
# Let's hope the developer has included it himself
|
||||
|
||||
# Let's warn in case this is a subdependency, otherwise
|
||||
# subdependency error messages are totally cryptic
|
||||
if ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
|
||||
def require_fixture_classes(table_names = nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
file_name = table_name.to_s
|
||||
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
|
||||
try_to_load_dependency(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def setup_fixture_accessors(table_names = nil)
|
||||
table_names = [table_names] if table_names && !table_names.respond_to?(:each)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
table_name = table_name.to_s.tr('.', '_')
|
||||
|
||||
define_method(table_name) do |*fixtures|
|
||||
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
|
||||
|
||||
@fixture_cache[table_name] ||= {}
|
||||
|
||||
instances = fixtures.map do |fixture|
|
||||
@fixture_cache[table_name].delete(fixture) if force_reload
|
||||
|
||||
if @loaded_fixtures[table_name][fixture.to_s]
|
||||
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
||||
else
|
||||
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
||||
end
|
||||
end
|
||||
|
||||
instances.size == 1 ? instances.first : instances
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def uses_transaction(*methods)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.concat methods.map(&:to_s)
|
||||
end
|
||||
|
||||
def uses_transaction?(method)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.include?(method.to_s)
|
||||
end
|
||||
self.fixture_class_names = {}
|
||||
end
|
||||
|
||||
def use_transactional_fixtures?
|
||||
use_transactional_fixtures &&
|
||||
!self.class.uses_transaction?(method_name)
|
||||
end
|
||||
|
||||
def setup_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
if pre_loaded_fixtures && !use_transactional_fixtures
|
||||
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
||||
end
|
||||
|
||||
@fixture_cache = {}
|
||||
|
||||
# Load fixtures once and begin transaction.
|
||||
if use_transactional_fixtures?
|
||||
if @@already_loaded_fixtures[self.class]
|
||||
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
||||
else
|
||||
load_fixtures
|
||||
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
||||
end
|
||||
ActiveRecord::Base.connection.increment_open_transactions
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
Fixtures.reset_cache
|
||||
@@already_loaded_fixtures[self.class] = nil
|
||||
load_fixtures
|
||||
end
|
||||
|
||||
# Instantiate fixtures for every test if requested.
|
||||
instantiate_fixtures if use_instantiated_fixtures
|
||||
end
|
||||
|
||||
def teardown_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
unless use_transactional_fixtures?
|
||||
Fixtures.reset_cache
|
||||
end
|
||||
|
||||
# Rollback changes if a transaction is active.
|
||||
if use_transactional_fixtures? && ActiveRecord::Base.connection.open_transactions != 0
|
||||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
ActiveRecord::Base.connection.decrement_open_transactions
|
||||
end
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
private
|
||||
def load_fixtures
|
||||
@loaded_fixtures = {}
|
||||
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
||||
unless fixtures.nil?
|
||||
if fixtures.instance_of?(Fixtures)
|
||||
@loaded_fixtures[fixtures.name] = fixtures
|
||||
else
|
||||
fixtures.each { |f| @loaded_fixtures[f.name] = f }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
|
||||
@@required_fixture_classes = false
|
||||
|
||||
def instantiate_fixtures
|
||||
if pre_loaded_fixtures
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
|
||||
unless @@required_fixture_classes
|
||||
self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
|
||||
@@required_fixture_classes = true
|
||||
end
|
||||
Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
|
||||
else
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
||||
@loaded_fixtures.each do |table_name, fixtures|
|
||||
Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_instances?
|
||||
use_instantiated_fixtures != :no_instances
|
||||
end
|
||||
base.extend ClassMethods
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def set_fixture_class(class_names = {})
|
||||
self.fixture_class_names = self.fixture_class_names.merge(class_names)
|
||||
end
|
||||
|
||||
def fixtures(*table_names)
|
||||
if table_names.first == :all
|
||||
table_names = Dir["#{fixture_path}/*.yml"] + Dir["#{fixture_path}/*.csv"]
|
||||
table_names.map! { |f| File.basename(f).split('.')[0..-2].join('.') }
|
||||
else
|
||||
table_names = table_names.flatten.map { |n| n.to_s }
|
||||
end
|
||||
|
||||
self.fixture_table_names |= table_names
|
||||
require_fixture_classes(table_names)
|
||||
setup_fixture_accessors(table_names)
|
||||
end
|
||||
|
||||
def try_to_load_dependency(file_name)
|
||||
require_dependency file_name
|
||||
rescue LoadError => e
|
||||
# Let's hope the developer has included it himself
|
||||
|
||||
# Let's warn in case this is a subdependency, otherwise
|
||||
# subdependency error messages are totally cryptic
|
||||
if ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger.warn("Unable to load #{file_name}, underlying cause #{e.message} \n\n #{e.backtrace.join("\n")}")
|
||||
end
|
||||
end
|
||||
|
||||
def require_fixture_classes(table_names = nil)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
file_name = table_name.to_s
|
||||
file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
|
||||
try_to_load_dependency(file_name)
|
||||
end
|
||||
end
|
||||
|
||||
def setup_fixture_accessors(table_names = nil)
|
||||
table_names = [table_names] if table_names && !table_names.respond_to?(:each)
|
||||
(table_names || fixture_table_names).each do |table_name|
|
||||
table_name = table_name.to_s.tr('.', '_')
|
||||
|
||||
define_method(table_name) do |*fixtures|
|
||||
force_reload = fixtures.pop if fixtures.last == true || fixtures.last == :reload
|
||||
|
||||
@fixture_cache[table_name] ||= {}
|
||||
|
||||
instances = fixtures.map do |fixture|
|
||||
@fixture_cache[table_name].delete(fixture) if force_reload
|
||||
|
||||
if @loaded_fixtures[table_name][fixture.to_s]
|
||||
@fixture_cache[table_name][fixture] ||= @loaded_fixtures[table_name][fixture.to_s].find
|
||||
else
|
||||
raise StandardError, "No fixture with name '#{fixture}' found for table '#{table_name}'"
|
||||
end
|
||||
end
|
||||
|
||||
instances.size == 1 ? instances.first : instances
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def uses_transaction(*methods)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.concat methods.map(&:to_s)
|
||||
end
|
||||
|
||||
def uses_transaction?(method)
|
||||
@uses_transaction = [] unless defined?(@uses_transaction)
|
||||
@uses_transaction.include?(method.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def run_in_transaction?
|
||||
use_transactional_fixtures &&
|
||||
!self.class.uses_transaction?(method_name)
|
||||
end
|
||||
|
||||
def setup_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
if pre_loaded_fixtures && !use_transactional_fixtures
|
||||
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
|
||||
end
|
||||
|
||||
@fixture_cache = {}
|
||||
@@already_loaded_fixtures ||= {}
|
||||
|
||||
# Load fixtures once and begin transaction.
|
||||
if run_in_transaction?
|
||||
if @@already_loaded_fixtures[self.class]
|
||||
@loaded_fixtures = @@already_loaded_fixtures[self.class]
|
||||
else
|
||||
load_fixtures
|
||||
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
||||
end
|
||||
ActiveRecord::Base.connection.increment_open_transactions
|
||||
ActiveRecord::Base.connection.transaction_joinable = false
|
||||
ActiveRecord::Base.connection.begin_db_transaction
|
||||
# Load fixtures for every test.
|
||||
else
|
||||
Fixtures.reset_cache
|
||||
@@already_loaded_fixtures[self.class] = nil
|
||||
load_fixtures
|
||||
end
|
||||
|
||||
# Instantiate fixtures for every test if requested.
|
||||
instantiate_fixtures if use_instantiated_fixtures
|
||||
end
|
||||
|
||||
def teardown_fixtures
|
||||
return unless defined?(ActiveRecord) && !ActiveRecord::Base.configurations.blank?
|
||||
|
||||
unless run_in_transaction?
|
||||
Fixtures.reset_cache
|
||||
end
|
||||
|
||||
# Rollback changes if a transaction is active.
|
||||
if run_in_transaction? && ActiveRecord::Base.connection.open_transactions != 0
|
||||
ActiveRecord::Base.connection.rollback_db_transaction
|
||||
ActiveRecord::Base.connection.decrement_open_transactions
|
||||
end
|
||||
ActiveRecord::Base.clear_active_connections!
|
||||
end
|
||||
|
||||
private
|
||||
def load_fixtures
|
||||
@loaded_fixtures = {}
|
||||
fixtures = Fixtures.create_fixtures(fixture_path, fixture_table_names, fixture_class_names)
|
||||
unless fixtures.nil?
|
||||
if fixtures.instance_of?(Fixtures)
|
||||
@loaded_fixtures[fixtures.name] = fixtures
|
||||
else
|
||||
fixtures.each { |f| @loaded_fixtures[f.name] = f }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# for pre_loaded_fixtures, only require the classes once. huge speed improvement
|
||||
@@required_fixture_classes = false
|
||||
|
||||
def instantiate_fixtures
|
||||
if pre_loaded_fixtures
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
|
||||
unless @@required_fixture_classes
|
||||
self.class.require_fixture_classes Fixtures.all_loaded_fixtures.keys
|
||||
@@required_fixture_classes = true
|
||||
end
|
||||
Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
|
||||
else
|
||||
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
|
||||
@loaded_fixtures.each do |table_name, fixtures|
|
||||
Fixtures.instantiate_fixtures(self, table_name, fixtures, load_instances?)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def load_instances?
|
||||
use_instantiated_fixtures != :no_instances
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ en:
|
|||
# blank: "This is a custom blank message for User login"
|
||||
# Will define custom blank validation message for User model and
|
||||
# custom blank validation message for login attribute of User model.
|
||||
models:
|
||||
#models:
|
||||
|
||||
# Translate model names. Used in Model.human_name().
|
||||
#models:
|
||||
|
|
|
@ -130,7 +130,9 @@ module ActiveRecord
|
|||
# To run migrations against the currently configured database, use
|
||||
# <tt>rake db:migrate</tt>. This will update the database by running all of the
|
||||
# pending migrations, creating the <tt>schema_migrations</tt> table
|
||||
# (see "About the schema_migrations table" section below) if missing.
|
||||
# (see "About the schema_migrations table" section below) if missing. It will also
|
||||
# invoke the db:schema:dump task, which will update your db/schema.rb file
|
||||
# to match the structure of your database.
|
||||
#
|
||||
# To roll the database back to a previous migration version, use
|
||||
# <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
|
||||
|
|
|
@ -39,7 +39,7 @@ module ActiveRecord
|
|||
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
|
||||
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
||||
#
|
||||
# All \scopes are available as class methods on the ActiveRecord::Base descendent upon which the \scopes were defined. But they are also available to
|
||||
# All \scopes are available as class methods on the ActiveRecord::Base descendant upon which the \scopes were defined. But they are also available to
|
||||
# <tt>has_many</tt> associations. If,
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
|
|
279
vendor/rails/activerecord/lib/active_record/nested_attributes.rb
vendored
Normal file
279
vendor/rails/activerecord/lib/active_record/nested_attributes.rb
vendored
Normal file
|
@ -0,0 +1,279 @@
|
|||
module ActiveRecord
|
||||
module NestedAttributes #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
base.class_inheritable_accessor :reject_new_nested_attributes_procs, :instance_writer => false
|
||||
base.reject_new_nested_attributes_procs = {}
|
||||
end
|
||||
|
||||
# == Nested Attributes
|
||||
#
|
||||
# Nested attributes allow you to save attributes on associated records
|
||||
# through the parent. By default nested attribute updating is turned off,
|
||||
# you can enable it using the accepts_nested_attributes_for class method.
|
||||
# When you enable nested attributes an attribute writer is defined on
|
||||
# the model.
|
||||
#
|
||||
# The attribute writer is named after the association, which means that
|
||||
# in the following example, two new methods are added to your model:
|
||||
# <tt>author_attributes=(attributes)</tt> and
|
||||
# <tt>pages_attributes=(attributes)</tt>.
|
||||
#
|
||||
# class Book < ActiveRecord::Base
|
||||
# has_one :author
|
||||
# has_many :pages
|
||||
#
|
||||
# accepts_nested_attributes_for :author, :pages
|
||||
# end
|
||||
#
|
||||
# Note that the <tt>:autosave</tt> option is automatically enabled on every
|
||||
# association that accepts_nested_attributes_for is used for.
|
||||
#
|
||||
# === One-to-one
|
||||
#
|
||||
# Consider a Member model that has one Avatar:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_one :avatar
|
||||
# accepts_nested_attributes_for :avatar
|
||||
# end
|
||||
#
|
||||
# Enabling nested attributes on a one-to-one association allows you to
|
||||
# create the member and avatar in one go:
|
||||
#
|
||||
# params = { 'member' => { 'name' => 'Jack', 'avatar_attributes' => { 'icon' => 'smiling' } } }
|
||||
# member = Member.create(params)
|
||||
# member.avatar.icon #=> 'smiling'
|
||||
#
|
||||
# It also allows you to update the avatar through the member:
|
||||
#
|
||||
# params = { 'member' => { 'avatar_attributes' => { 'icon' => 'sad' } } }
|
||||
# member.update_attributes params['member']
|
||||
# member.avatar.icon #=> 'sad'
|
||||
#
|
||||
# By default you will only be able to set and update attributes on the
|
||||
# associated model. If you want to destroy the associated model through the
|
||||
# attributes hash, you have to enable it first using the
|
||||
# <tt>:allow_destroy</tt> option.
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_one :avatar
|
||||
# accepts_nested_attributes_for :avatar, :allow_destroy => true
|
||||
# end
|
||||
#
|
||||
# Now, when you add the <tt>_delete</tt> key to the attributes hash, with a
|
||||
# value that evaluates to +true+, you will destroy the associated model:
|
||||
#
|
||||
# member.avatar_attributes = { '_delete' => '1' }
|
||||
# member.avatar.marked_for_destruction? # => true
|
||||
# member.save
|
||||
# member.avatar #=> nil
|
||||
#
|
||||
# Note that the model will _not_ be destroyed until the parent is saved.
|
||||
#
|
||||
# === One-to-many
|
||||
#
|
||||
# Consider a member that has a number of posts:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# accepts_nested_attributes_for :posts, :reject_if => proc { |attributes| attributes['title'].blank? }
|
||||
# end
|
||||
#
|
||||
# You can now set or update attributes on an associated post model through
|
||||
# the attribute hash.
|
||||
#
|
||||
# For each key in the hash that starts with the string 'new' a new model
|
||||
# will be instantiated. When the proc given with the <tt>:reject_if</tt>
|
||||
# option evaluates to +false+ for a certain attribute hash no record will
|
||||
# be built for that hash.
|
||||
#
|
||||
# params = { 'member' => {
|
||||
# 'name' => 'joe', 'posts_attributes' => {
|
||||
# 'new_12345' => { 'title' => 'Kari, the awesome Ruby documentation browser!' },
|
||||
# 'new_54321' => { 'title' => 'The egalitarian assumption of the modern citizen' },
|
||||
# 'new_67890' => { 'title' => '' } # This one matches the :reject_if proc and will not be instantiated.
|
||||
# }
|
||||
# }}
|
||||
#
|
||||
# member = Member.create(params['member'])
|
||||
# member.posts.length #=> 2
|
||||
# member.posts.first.title #=> 'Kari, the awesome Ruby documentation browser!'
|
||||
# member.posts.second.title #=> 'The egalitarian assumption of the modern citizen'
|
||||
#
|
||||
# When the key for post attributes is an integer, the associated post with
|
||||
# that ID will be updated:
|
||||
#
|
||||
# member.attributes = {
|
||||
# 'name' => 'Joe',
|
||||
# 'posts_attributes' => {
|
||||
# '1' => { 'title' => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
|
||||
# '2' => { 'title' => '[UPDATED] other post' }
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# By default the associated models are protected from being destroyed. If
|
||||
# you want to destroy any of the associated models through the attributes
|
||||
# hash, you have to enable it first using the <tt>:allow_destroy</tt>
|
||||
# option.
|
||||
#
|
||||
# This will allow you to specify which models to destroy in the attributes
|
||||
# hash by setting the '_delete' attribute to a value that evaluates to
|
||||
# +true+:
|
||||
#
|
||||
# class Member < ActiveRecord::Base
|
||||
# has_many :posts
|
||||
# accepts_nested_attributes_for :posts, :allow_destroy => true
|
||||
# end
|
||||
#
|
||||
# params = {'member' => { 'name' => 'joe', 'posts_attributes' => {
|
||||
# '2' => { '_delete' => '1' }
|
||||
# }}}
|
||||
# member.attributes = params['member']
|
||||
# member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
|
||||
# member.posts.length #=> 2
|
||||
# member.save
|
||||
# member.posts.length # => 1
|
||||
#
|
||||
# === Saving
|
||||
#
|
||||
# All changes to models, including the destruction of those marked for
|
||||
# destruction, are saved and destroyed automatically and atomically when
|
||||
# the parent model is saved. This happens inside the transaction initiated
|
||||
# by the parents save method. See ActiveRecord::AutosaveAssociation.
|
||||
module ClassMethods
|
||||
# Defines an attributes writer for the specified association(s).
|
||||
#
|
||||
# Supported options:
|
||||
# [:allow_destroy]
|
||||
# If true, destroys any members from the attributes hash with a
|
||||
# <tt>_delete</tt> key and a value that converts to +true+
|
||||
# (eg. 1, '1', true, or 'true'). This option is off by default.
|
||||
# [:reject_if]
|
||||
# Allows you to specify a Proc that checks whether a record should be
|
||||
# built for a certain attribute hash. The hash is passed to the Proc
|
||||
# and the Proc should return either +true+ or +false+. When no Proc
|
||||
# is specified a record will be built for all attribute hashes.
|
||||
#
|
||||
# Examples:
|
||||
# accepts_nested_attributes_for :avatar
|
||||
# accepts_nested_attributes_for :avatar, :allow_destroy => true
|
||||
# accepts_nested_attributes_for :avatar, :reject_if => proc { ... }
|
||||
# accepts_nested_attributes_for :avatar, :posts, :allow_destroy => true, :reject_if => proc { ... }
|
||||
def accepts_nested_attributes_for(*attr_names)
|
||||
options = { :allow_destroy => false }
|
||||
options.update(attr_names.extract_options!)
|
||||
options.assert_valid_keys(:allow_destroy, :reject_if)
|
||||
|
||||
attr_names.each do |association_name|
|
||||
if reflection = reflect_on_association(association_name)
|
||||
type = case reflection.macro
|
||||
when :has_one, :belongs_to
|
||||
:one_to_one
|
||||
when :has_many, :has_and_belongs_to_many
|
||||
:collection
|
||||
end
|
||||
|
||||
reflection.options[:autosave] = true
|
||||
self.reject_new_nested_attributes_procs[association_name.to_sym] = options[:reject_if]
|
||||
|
||||
# def pirate_attributes=(attributes)
|
||||
# assign_nested_attributes_for_one_to_one_association(:pirate, attributes, false)
|
||||
# end
|
||||
class_eval %{
|
||||
def #{association_name}_attributes=(attributes)
|
||||
assign_nested_attributes_for_#{type}_association(:#{association_name}, attributes, #{options[:allow_destroy]})
|
||||
end
|
||||
}, __FILE__, __LINE__
|
||||
else
|
||||
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns ActiveRecord::AutosaveAssociation::marked_for_destruction?
|
||||
# It's used in conjunction with fields_for to build a form element
|
||||
# for the destruction of this association.
|
||||
#
|
||||
# See ActionView::Helpers::FormHelper::fields_for for more info.
|
||||
def _delete
|
||||
marked_for_destruction?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Assigns the given attributes to the association. An association will be
|
||||
# build if it doesn't exist yet.
|
||||
def assign_nested_attributes_for_one_to_one_association(association_name, attributes, allow_destroy)
|
||||
if should_destroy_nested_attributes_record?(allow_destroy, attributes)
|
||||
send(association_name).mark_for_destruction
|
||||
else
|
||||
(send(association_name) || send("build_#{association_name}")).attributes = attributes
|
||||
end
|
||||
end
|
||||
|
||||
# Assigns the given attributes to the collection association.
|
||||
#
|
||||
# Keys containing an ID for an associated record will update that record.
|
||||
# Keys starting with <tt>new</tt> will instantiate a new record for that
|
||||
# association.
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# assign_nested_attributes_for_collection_association(:people, {
|
||||
# '1' => { 'name' => 'Peter' },
|
||||
# 'new_43' => { 'name' => 'John' }
|
||||
# })
|
||||
#
|
||||
# Will update the name of the Person with ID 1 and create a new associated
|
||||
# person with the name 'John'.
|
||||
def assign_nested_attributes_for_collection_association(association_name, attributes, allow_destroy)
|
||||
unless attributes.is_a?(Hash)
|
||||
raise ArgumentError, "Hash expected, got #{attributes.class.name} (#{attributes.inspect})"
|
||||
end
|
||||
|
||||
# Make sure any new records sorted by their id before they're build.
|
||||
sorted_by_id = attributes.sort_by { |id, _| id.is_a?(String) ? id.sub(/^new_/, '').to_i : id }
|
||||
|
||||
sorted_by_id.each do |id, record_attributes|
|
||||
if id.acts_like?(:string) && id.starts_with?('new_')
|
||||
build_new_nested_attributes_record(association_name, record_attributes)
|
||||
else
|
||||
assign_to_or_destroy_nested_attributes_record(association_name, id, record_attributes, allow_destroy)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns +true+ if <tt>allow_destroy</tt> is enabled and the attributes
|
||||
# contains a truthy value for the key <tt>'_delete'</tt>.
|
||||
#
|
||||
# It will _always_ remove the <tt>'_delete'</tt> key, if present.
|
||||
def should_destroy_nested_attributes_record?(allow_destroy, attributes)
|
||||
ConnectionAdapters::Column.value_to_boolean(attributes.delete('_delete')) && allow_destroy
|
||||
end
|
||||
|
||||
# Builds a new record with the given attributes.
|
||||
#
|
||||
# If a <tt>:reject_if</tt> proc exists for this association, it will be
|
||||
# called with the attributes as its argument. If the proc returns a truthy
|
||||
# value, the record is _not_ build.
|
||||
def build_new_nested_attributes_record(association_name, attributes)
|
||||
if reject_proc = self.class.reject_new_nested_attributes_procs[association_name]
|
||||
return if reject_proc.call(attributes)
|
||||
end
|
||||
send(association_name).build(attributes)
|
||||
end
|
||||
|
||||
# Assigns the attributes to the record specified by +id+. Or marks it for
|
||||
# destruction if #should_destroy_nested_attributes_record? returns +true+.
|
||||
def assign_to_or_destroy_nested_attributes_record(association_name, id, attributes, allow_destroy)
|
||||
record = send(association_name).detect { |record| record.id == id.to_i }
|
||||
if should_destroy_nested_attributes_record?(allow_destroy, attributes)
|
||||
record.mark_for_destruction
|
||||
else
|
||||
record.attributes = attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,20 +1,32 @@
|
|||
module ActiveRecord
|
||||
module QueryCache
|
||||
# Enable the query cache within the block if Active Record is configured.
|
||||
def cache(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.cache(&block)
|
||||
class QueryCache
|
||||
module ClassMethods
|
||||
# Enable the query cache within the block if Active Record is configured.
|
||||
def cache(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.cache(&block)
|
||||
end
|
||||
end
|
||||
|
||||
# Disable the query cache within the block if Active Record is configured.
|
||||
def uncached(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.uncached(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Disable the query cache within the block if Active Record is configured.
|
||||
def uncached(&block)
|
||||
if ActiveRecord::Base.configurations.blank?
|
||||
yield
|
||||
else
|
||||
connection.uncached(&block)
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
ActiveRecord::Base.cache do
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -65,6 +65,11 @@ module ActiveRecord
|
|||
def reflect_on_association(association)
|
||||
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
||||
end
|
||||
|
||||
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
||||
def reflect_on_all_autosave_associations
|
||||
reflections.values.select { |reflection| reflection.options[:autosave] }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
@ -198,6 +203,14 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def columns(tbl_name, log_msg)
|
||||
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
||||
end
|
||||
|
||||
def reset_column_information
|
||||
@columns = nil
|
||||
end
|
||||
|
||||
def check_validity!
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ module ActiveRecord
|
|||
class SchemaDumper #:nodoc:
|
||||
private_class_method :new
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# A list of tables which should not be dumped to the schema.
|
||||
# Acceptable values are strings as well as regexp.
|
||||
# This setting is only used if ActiveRecord::Base.schema_format == :ruby
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
require 'active_support/json'
|
||||
|
||||
module ActiveRecord #:nodoc:
|
||||
module Serialization
|
||||
class Serializer #:nodoc:
|
||||
|
@ -95,4 +97,4 @@ module ActiveRecord #:nodoc:
|
|||
end
|
||||
|
||||
require 'active_record/serializers/xml_serializer'
|
||||
require 'active_record/serializers/json_serializer'
|
||||
require 'active_record/serializers/json_serializer'
|
||||
|
|
|
@ -23,11 +23,12 @@ module ActiveRecord #:nodoc:
|
|||
# </topic>
|
||||
#
|
||||
# This behavior can be controlled with <tt>:only</tt>, <tt>:except</tt>,
|
||||
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt> and <tt>:dasherize</tt>.
|
||||
# <tt>:skip_instruct</tt>, <tt>:skip_types</tt>, <tt>:dasherize</tt> and <tt>:camelize</tt> .
|
||||
# The <tt>:only</tt> and <tt>:except</tt> options are the same as for the
|
||||
# +attributes+ method. The default is to dasherize all column names, but you
|
||||
# can disable this setting <tt>:dasherize</tt> to +false+. To not have the
|
||||
# column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
||||
# can disable this setting <tt>:dasherize</tt> to +false+. Setting <tt>:camelize</tt>
|
||||
# to +true+ will camelize all column names - this also overrides <tt>:dasherize</tt>.
|
||||
# To not have the column type included in the XML output set <tt>:skip_types</tt> to +true+.
|
||||
#
|
||||
# For instance:
|
||||
#
|
||||
|
@ -178,13 +179,22 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def root
|
||||
root = (options[:root] || @record.class.to_s.underscore).to_s
|
||||
dasherize? ? root.dasherize : root
|
||||
reformat_name(root)
|
||||
end
|
||||
|
||||
def dasherize?
|
||||
!options.has_key?(:dasherize) || options[:dasherize]
|
||||
end
|
||||
|
||||
def camelize?
|
||||
options.has_key?(:camelize) && options[:camelize]
|
||||
end
|
||||
|
||||
def reformat_name(name)
|
||||
name = name.camelize if camelize?
|
||||
dasherize? ? name.dasherize : name
|
||||
end
|
||||
|
||||
def serializable_attributes
|
||||
serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
|
||||
end
|
||||
|
@ -212,7 +222,7 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def add_tag(attribute)
|
||||
builder.tag!(
|
||||
dasherize? ? attribute.name.dasherize : attribute.name,
|
||||
reformat_name(attribute.name),
|
||||
attribute.value.to_s,
|
||||
attribute.decorations(!options[:skip_types])
|
||||
)
|
||||
|
@ -220,8 +230,7 @@ module ActiveRecord #:nodoc:
|
|||
|
||||
def add_associations(association, records, opts)
|
||||
if records.is_a?(Enumerable)
|
||||
tag = association.to_s
|
||||
tag = tag.dasherize if dasherize?
|
||||
tag = reformat_name(association.to_s)
|
||||
if records.empty?
|
||||
builder.tag!(tag, :type => :array)
|
||||
else
|
||||
|
|
314
vendor/rails/activerecord/lib/active_record/session_store.rb
vendored
Normal file
314
vendor/rails/activerecord/lib/active_record/session_store.rb
vendored
Normal file
|
@ -0,0 +1,314 @@
|
|||
module ActiveRecord
|
||||
# A session store backed by an Active Record class. A default class is
|
||||
# provided, but any object duck-typing to an Active Record Session class
|
||||
# with text +session_id+ and +data+ attributes is sufficient.
|
||||
#
|
||||
# The default assumes a +sessions+ tables with columns:
|
||||
# +id+ (numeric primary key),
|
||||
# +session_id+ (text, or longtext if your session data exceeds 65K), and
|
||||
# +data+ (text or longtext; careful if your session data exceeds 65KB).
|
||||
# The +session_id+ column should always be indexed for speedy lookups.
|
||||
# Session data is marshaled to the +data+ column in Base64 format.
|
||||
# If the data you write is larger than the column's size limit,
|
||||
# ActionController::SessionOverflowError will be raised.
|
||||
#
|
||||
# You may configure the table name, primary key, and data column.
|
||||
# For example, at the end of <tt>config/environment.rb</tt>:
|
||||
# ActiveRecord::SessionStore::Session.table_name = 'legacy_session_table'
|
||||
# ActiveRecord::SessionStore::Session.primary_key = 'session_id'
|
||||
# ActiveRecord::SessionStore::Session.data_column_name = 'legacy_session_data'
|
||||
# Note that setting the primary key to the +session_id+ frees you from
|
||||
# having a separate +id+ column if you don't want it. However, you must
|
||||
# set <tt>session.model.id = session.session_id</tt> by hand! A before filter
|
||||
# on ApplicationController is a good place.
|
||||
#
|
||||
# Since the default class is a simple Active Record, you get timestamps
|
||||
# for free if you add +created_at+ and +updated_at+ datetime columns to
|
||||
# the +sessions+ table, making periodic session expiration a snap.
|
||||
#
|
||||
# You may provide your own session class implementation, whether a
|
||||
# feature-packed Active Record or a bare-metal high-performance SQL
|
||||
# store, by setting
|
||||
# ActiveRecord::SessionStore.session_class = MySessionClass
|
||||
# You must implement these methods:
|
||||
# self.find_by_session_id(session_id)
|
||||
# initialize(hash_of_session_id_and_data)
|
||||
# attr_reader :session_id
|
||||
# attr_accessor :data
|
||||
# save
|
||||
# destroy
|
||||
#
|
||||
# The example SqlBypass class is a generic SQL session store. You may
|
||||
# use it as a basis for high-performance database-specific stores.
|
||||
class SessionStore < ActionController::Session::AbstractStore
|
||||
# The default Active Record class.
|
||||
class Session < ActiveRecord::Base
|
||||
##
|
||||
# :singleton-method:
|
||||
# Customizable data column name. Defaults to 'data'.
|
||||
cattr_accessor :data_column_name
|
||||
self.data_column_name = 'data'
|
||||
|
||||
before_save :marshal_data!
|
||||
before_save :raise_on_session_data_overflow!
|
||||
|
||||
class << self
|
||||
def data_column_size_limit
|
||||
@data_column_size_limit ||= columns_hash[@@data_column_name].limit
|
||||
end
|
||||
|
||||
# Hook to set up sessid compatibility.
|
||||
def find_by_session_id(session_id)
|
||||
setup_sessid_compatibility!
|
||||
find_by_session_id(session_id)
|
||||
end
|
||||
|
||||
def marshal(data)
|
||||
ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
|
||||
end
|
||||
|
||||
def unmarshal(data)
|
||||
Marshal.load(ActiveSupport::Base64.decode64(data)) if data
|
||||
end
|
||||
|
||||
def create_table!
|
||||
connection.execute <<-end_sql
|
||||
CREATE TABLE #{table_name} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
#{connection.quote_column_name('session_id')} TEXT UNIQUE,
|
||||
#{connection.quote_column_name(@@data_column_name)} TEXT(255)
|
||||
)
|
||||
end_sql
|
||||
end
|
||||
|
||||
def drop_table!
|
||||
connection.execute "DROP TABLE #{table_name}"
|
||||
end
|
||||
|
||||
private
|
||||
# Compatibility with tables using sessid instead of session_id.
|
||||
def setup_sessid_compatibility!
|
||||
# Reset column info since it may be stale.
|
||||
reset_column_information
|
||||
if columns_hash['sessid']
|
||||
def self.find_by_session_id(*args)
|
||||
find_by_sessid(*args)
|
||||
end
|
||||
|
||||
define_method(:session_id) { sessid }
|
||||
define_method(:session_id=) { |session_id| self.sessid = session_id }
|
||||
else
|
||||
def self.find_by_session_id(session_id)
|
||||
find :first, :conditions => ["session_id #{attribute_condition(session_id)}", session_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Lazy-unmarshal session state.
|
||||
def data
|
||||
@data ||= self.class.unmarshal(read_attribute(@@data_column_name)) || {}
|
||||
end
|
||||
|
||||
attr_writer :data
|
||||
|
||||
# Has the session been loaded yet?
|
||||
def loaded?
|
||||
!!@data
|
||||
end
|
||||
|
||||
private
|
||||
def marshal_data!
|
||||
return false if !loaded?
|
||||
write_attribute(@@data_column_name, self.class.marshal(self.data))
|
||||
end
|
||||
|
||||
# Ensures that the data about to be stored in the database is not
|
||||
# larger than the data storage column. Raises
|
||||
# ActionController::SessionOverflowError.
|
||||
def raise_on_session_data_overflow!
|
||||
return false if !loaded?
|
||||
limit = self.class.data_column_size_limit
|
||||
if loaded? and limit and read_attribute(@@data_column_name).size > limit
|
||||
raise ActionController::SessionOverflowError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# A barebones session store which duck-types with the default session
|
||||
# store but bypasses Active Record and issues SQL directly. This is
|
||||
# an example session model class meant as a basis for your own classes.
|
||||
#
|
||||
# The database connection, table name, and session id and data columns
|
||||
# are configurable class attributes. Marshaling and unmarshaling
|
||||
# are implemented as class methods that you may override. By default,
|
||||
# marshaling data is
|
||||
#
|
||||
# ActiveSupport::Base64.encode64(Marshal.dump(data))
|
||||
#
|
||||
# and unmarshaling data is
|
||||
#
|
||||
# Marshal.load(ActiveSupport::Base64.decode64(data))
|
||||
#
|
||||
# This marshaling behavior is intended to store the widest range of
|
||||
# binary session data in a +text+ column. For higher performance,
|
||||
# store in a +blob+ column instead and forgo the Base64 encoding.
|
||||
class SqlBypass
|
||||
##
|
||||
# :singleton-method:
|
||||
# Use the ActiveRecord::Base.connection by default.
|
||||
cattr_accessor :connection
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The table name defaults to 'sessions'.
|
||||
cattr_accessor :table_name
|
||||
@@table_name = 'sessions'
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The session id field defaults to 'session_id'.
|
||||
cattr_accessor :session_id_column
|
||||
@@session_id_column = 'session_id'
|
||||
|
||||
##
|
||||
# :singleton-method:
|
||||
# The data field defaults to 'data'.
|
||||
cattr_accessor :data_column
|
||||
@@data_column = 'data'
|
||||
|
||||
class << self
|
||||
def connection
|
||||
@@connection ||= ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
# Look up a session by id and unmarshal its data if found.
|
||||
def find_by_session_id(session_id)
|
||||
if record = @@connection.select_one("SELECT * FROM #{@@table_name} WHERE #{@@session_id_column}=#{@@connection.quote(session_id)}")
|
||||
new(:session_id => session_id, :marshaled_data => record['data'])
|
||||
end
|
||||
end
|
||||
|
||||
def marshal(data)
|
||||
ActiveSupport::Base64.encode64(Marshal.dump(data)) if data
|
||||
end
|
||||
|
||||
def unmarshal(data)
|
||||
Marshal.load(ActiveSupport::Base64.decode64(data)) if data
|
||||
end
|
||||
|
||||
def create_table!
|
||||
@@connection.execute <<-end_sql
|
||||
CREATE TABLE #{table_name} (
|
||||
id INTEGER PRIMARY KEY,
|
||||
#{@@connection.quote_column_name(session_id_column)} TEXT UNIQUE,
|
||||
#{@@connection.quote_column_name(data_column)} TEXT
|
||||
)
|
||||
end_sql
|
||||
end
|
||||
|
||||
def drop_table!
|
||||
@@connection.execute "DROP TABLE #{table_name}"
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :session_id
|
||||
attr_writer :data
|
||||
|
||||
# Look for normal and marshaled data, self.find_by_session_id's way of
|
||||
# telling us to postpone unmarshaling until the data is requested.
|
||||
# We need to handle a normal data attribute in case of a new record.
|
||||
def initialize(attributes)
|
||||
@session_id, @data, @marshaled_data = attributes[:session_id], attributes[:data], attributes[:marshaled_data]
|
||||
@new_record = @marshaled_data.nil?
|
||||
end
|
||||
|
||||
def new_record?
|
||||
@new_record
|
||||
end
|
||||
|
||||
# Lazy-unmarshal session state.
|
||||
def data
|
||||
unless @data
|
||||
if @marshaled_data
|
||||
@data, @marshaled_data = self.class.unmarshal(@marshaled_data) || {}, nil
|
||||
else
|
||||
@data = {}
|
||||
end
|
||||
end
|
||||
@data
|
||||
end
|
||||
|
||||
def loaded?
|
||||
!!@data
|
||||
end
|
||||
|
||||
def save
|
||||
return false if !loaded?
|
||||
marshaled_data = self.class.marshal(data)
|
||||
|
||||
if @new_record
|
||||
@new_record = false
|
||||
@@connection.update <<-end_sql, 'Create session'
|
||||
INSERT INTO #{@@table_name} (
|
||||
#{@@connection.quote_column_name(@@session_id_column)},
|
||||
#{@@connection.quote_column_name(@@data_column)} )
|
||||
VALUES (
|
||||
#{@@connection.quote(session_id)},
|
||||
#{@@connection.quote(marshaled_data)} )
|
||||
end_sql
|
||||
else
|
||||
@@connection.update <<-end_sql, 'Update session'
|
||||
UPDATE #{@@table_name}
|
||||
SET #{@@connection.quote_column_name(@@data_column)}=#{@@connection.quote(marshaled_data)}
|
||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
unless @new_record
|
||||
@@connection.delete <<-end_sql, 'Destroy session'
|
||||
DELETE FROM #{@@table_name}
|
||||
WHERE #{@@connection.quote_column_name(@@session_id_column)}=#{@@connection.quote(session_id)}
|
||||
end_sql
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The class used for session storage. Defaults to
|
||||
# ActiveRecord::SessionStore::Session
|
||||
cattr_accessor :session_class
|
||||
self.session_class = Session
|
||||
|
||||
SESSION_RECORD_KEY = 'rack.session.record'.freeze
|
||||
|
||||
private
|
||||
def get_session(env, sid)
|
||||
Base.silence do
|
||||
sid ||= generate_sid
|
||||
session = @@session_class.find_by_session_id(sid)
|
||||
session ||= @@session_class.new(:session_id => sid, :data => {})
|
||||
env[SESSION_RECORD_KEY] = session
|
||||
[sid, session.data]
|
||||
end
|
||||
end
|
||||
|
||||
def set_session(env, sid, session_data)
|
||||
Base.silence do
|
||||
record = env[SESSION_RECORD_KEY]
|
||||
record.data = session_data
|
||||
return false unless record.save
|
||||
|
||||
session_data = record.data
|
||||
if session_data && session_data.respond_to?(:each_value)
|
||||
session_data.each_value do |obj|
|
||||
obj.clear_association_cache if obj.respond_to?(:clear_association_cache)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +1,7 @@
|
|||
require "active_support/test_case"
|
||||
|
||||
module ActiveRecord
|
||||
module ActiveRecord
|
||||
class TestCase < ActiveSupport::TestCase #:nodoc:
|
||||
self.fixture_path = FIXTURES_ROOT
|
||||
self.use_instantiated_fixtures = false
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
def create_fixtures(*table_names, &block)
|
||||
Fixtures.create_fixtures(FIXTURES_ROOT, table_names, {}, &block)
|
||||
end
|
||||
|
||||
def assert_date_from_db(expected, actual, message = nil)
|
||||
# SybaseAdapter doesn't have a separate column type just for dates,
|
||||
# so the time is in the string and incorrectly formatted
|
||||
|
@ -35,6 +27,7 @@ module ActiveRecord
|
|||
$queries_executed = []
|
||||
yield
|
||||
ensure
|
||||
%w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
|
||||
assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
|
||||
end
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@ module ActiveRecord
|
|||
write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
|
||||
write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
|
||||
|
||||
write_attribute('updated_at', t) if respond_to?(:updated_at)
|
||||
write_attribute('updated_on', t) if respond_to?(:updated_on)
|
||||
write_attribute('updated_at', t) if respond_to?(:updated_at) && updated_at.nil?
|
||||
write_attribute('updated_on', t) if respond_to?(:updated_on) && updated_on.nil?
|
||||
end
|
||||
create_without_timestamps
|
||||
end
|
||||
|
|
|
@ -120,16 +120,66 @@ module ActiveRecord
|
|||
# end
|
||||
#
|
||||
# One should restart the entire transaction if a StatementError occurred.
|
||||
#
|
||||
# == Nested transactions
|
||||
#
|
||||
# #transaction calls can be nested. By default, this makes all database
|
||||
# statements in the nested transaction block become part of the parent
|
||||
# transaction. For example:
|
||||
#
|
||||
# User.transaction do
|
||||
# User.create(:username => 'Kotori')
|
||||
# User.transaction do
|
||||
# User.create(:username => 'Nemu')
|
||||
# raise ActiveRecord::Rollback
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(:all) # => empty
|
||||
#
|
||||
# It is also possible to requires a sub-transaction by passing
|
||||
# <tt>:requires_new => true</tt>. If anything goes wrong, the
|
||||
# database rolls back to the beginning of the sub-transaction
|
||||
# without rolling back the parent transaction. For example:
|
||||
#
|
||||
# User.transaction do
|
||||
# User.create(:username => 'Kotori')
|
||||
# User.transaction(:requires_new => true) do
|
||||
# User.create(:username => 'Nemu')
|
||||
# raise ActiveRecord::Rollback
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# User.find(:all) # => Returns only Kotori
|
||||
#
|
||||
# Most databases don't support true nested transactions. At the time of
|
||||
# writing, the only database that we're aware of that supports true nested
|
||||
# transactions, is MS-SQL. Because of this, Active Record emulates nested
|
||||
# transactions by using savepoints. See
|
||||
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
|
||||
# for more information about savepoints.
|
||||
#
|
||||
# === Caveats
|
||||
#
|
||||
# If you're on MySQL, then do not use DDL operations in nested transactions
|
||||
# blocks that are emulated with savepoints. That is, do not execute statements
|
||||
# like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
|
||||
# releases all savepoints upon executing a DDL operation. When #transaction
|
||||
# is finished and tries to release the savepoint it created earlier, a
|
||||
# database error will occur because the savepoint has already been
|
||||
# automatically released. The following example demonstrates the problem:
|
||||
#
|
||||
# Model.connection.transaction do # BEGIN
|
||||
# Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
|
||||
# Model.connection.create_table(...) # active_record_1 now automatically released
|
||||
# end # RELEASE savepoint active_record_1
|
||||
# # ^^^^ BOOM! database error!
|
||||
# end
|
||||
module ClassMethods
|
||||
# See ActiveRecord::Transactions::ClassMethods for detailed documentation.
|
||||
def transaction(&block)
|
||||
connection.increment_open_transactions
|
||||
|
||||
begin
|
||||
connection.transaction(connection.open_transactions == 1, &block)
|
||||
ensure
|
||||
connection.decrement_open_transactions
|
||||
end
|
||||
def transaction(options = {}, &block)
|
||||
# See the ConnectionAdapters::DatabaseStatements#transaction API docs.
|
||||
connection.transaction(options, &block)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -147,7 +197,7 @@ module ActiveRecord
|
|||
end
|
||||
|
||||
def save_with_transactions! #:nodoc:
|
||||
rollback_active_record_state! { transaction { save_without_transactions! } }
|
||||
rollback_active_record_state! { self.class.transaction { save_without_transactions! } }
|
||||
end
|
||||
|
||||
# Reset id and @new_record if the transaction rolls back.
|
||||
|
@ -175,7 +225,7 @@ module ActiveRecord
|
|||
# instance.
|
||||
def with_transaction_returning_status(method, *args)
|
||||
status = nil
|
||||
transaction do
|
||||
self.class.transaction do
|
||||
status = send(method, *args)
|
||||
raise ActiveRecord::Rollback unless status
|
||||
end
|
||||
|
|
|
@ -203,9 +203,8 @@ module ActiveRecord
|
|||
if attr == "base"
|
||||
full_messages << message
|
||||
else
|
||||
#key = :"activerecord.att.#{@base.class.name.underscore.to_sym}.#{attr}"
|
||||
attr_name = @base.class.human_attribute_name(attr)
|
||||
full_messages << attr_name + ' ' + message
|
||||
full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -494,18 +493,20 @@ module ActiveRecord
|
|||
# The first_name attribute must be in the object and it cannot be blank.
|
||||
#
|
||||
# If you want to validate the presence of a boolean field (where the real values are true and false),
|
||||
# you will want to use validates_inclusion_of :field_name, :in => [true, false]
|
||||
# This is due to the way Object#blank? handles boolean values. false.blank? # => true
|
||||
# you will want to use <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
|
||||
#
|
||||
# This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>.
|
||||
#
|
||||
# Configuration options:
|
||||
# * <tt>message</tt> - A custom error message (default is: "can't be blank").
|
||||
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
||||
# * <tt>on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>,
|
||||
# <tt>:update</tt>).
|
||||
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>).
|
||||
# The method, proc or string should return or evaluate to a true or false value.
|
||||
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
|
||||
# The method, proc or string should return or evaluate to a true or false value.
|
||||
#
|
||||
def validates_presence_of(*attr_names)
|
||||
configuration = { :on => :save }
|
||||
|
@ -574,6 +575,8 @@ module ActiveRecord
|
|||
# Get range option and value.
|
||||
option = range_options.first
|
||||
option_value = options[range_options.first]
|
||||
key = {:is => :wrong_length, :minimum => :too_short, :maximum => :too_long}[option]
|
||||
custom_message = options[:message] || options[key]
|
||||
|
||||
case option
|
||||
when :within, :in
|
||||
|
@ -582,9 +585,9 @@ module ActiveRecord
|
|||
validates_each(attrs, options) do |record, attr, value|
|
||||
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
||||
if value.nil? or value.size < option_value.begin
|
||||
record.errors.add(attr, :too_short, :default => options[:too_short], :count => option_value.begin)
|
||||
record.errors.add(attr, :too_short, :default => custom_message || options[:too_short], :count => option_value.begin)
|
||||
elsif value.size > option_value.end
|
||||
record.errors.add(attr, :too_long, :default => options[:too_long], :count => option_value.end)
|
||||
record.errors.add(attr, :too_long, :default => custom_message || options[:too_long], :count => option_value.end)
|
||||
end
|
||||
end
|
||||
when :is, :minimum, :maximum
|
||||
|
@ -592,13 +595,10 @@ module ActiveRecord
|
|||
|
||||
# Declare different validations per option.
|
||||
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
|
||||
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
|
||||
|
||||
validates_each(attrs, options) do |record, attr, value|
|
||||
value = options[:tokenizer].call(value) if value.kind_of?(String)
|
||||
unless !value.nil? and value.size.method(validity_checks[option])[option_value]
|
||||
key = message_options[option]
|
||||
custom_message = options[:message] || options[key]
|
||||
record.errors.add(attr, key, :default => custom_message, :count => option_value)
|
||||
end
|
||||
end
|
||||
|
@ -903,7 +903,7 @@ module ActiveRecord
|
|||
configuration.update(attr_names.extract_options!)
|
||||
|
||||
validates_each(attr_names, configuration) do |record, attr_name, value|
|
||||
unless (value.is_a?(Array) ? value : [value]).inject(true) { |v, r| (r.nil? || r.valid?) && v }
|
||||
unless (value.is_a?(Array) ? value : [value]).collect { |r| r.nil? || r.valid? }.all?
|
||||
record.errors.add(attr_name, :invalid, :default => configuration[:message], :value => value)
|
||||
end
|
||||
end
|
||||
|
@ -1047,15 +1047,15 @@ module ActiveRecord
|
|||
|
||||
protected
|
||||
# Overwrite this method for validation checks on all saves and use <tt>Errors.add(field, msg)</tt> for invalid attributes.
|
||||
def validate #:doc:
|
||||
def validate
|
||||
end
|
||||
|
||||
# Overwrite this method for validation checks used only on creation.
|
||||
def validate_on_create #:doc:
|
||||
def validate_on_create
|
||||
end
|
||||
|
||||
# Overwrite this method for validation checks used only on updates.
|
||||
def validate_on_update # :doc:
|
||||
def validate_on_update
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
module ActiveRecord
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 2
|
||||
TINY = 2
|
||||
MINOR = 3
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue