aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb11
-rw-r--r--activerecord/lib/active_record/aggregations.rb261
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb30
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb15
-rw-r--r--activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_one_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_helper.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb23
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb26
-rw-r--r--activerecord/lib/active_record/attribute_methods/dirty.rb29
-rw-r--r--activerecord/lib/active_record/attribute_methods/read.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/serialization.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb13
-rw-r--r--activerecord/lib/active_record/callbacks.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_handling.rb4
-rw-r--r--activerecord/lib/active_record/core.rb139
-rw-r--r--activerecord/lib/active_record/counter_cache.rb200
-rw-r--r--activerecord/lib/active_record/dynamic_matchers.rb3
-rw-r--r--activerecord/lib/active_record/errors.rb3
-rw-r--r--activerecord/lib/active_record/explain.rb11
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb7
-rw-r--r--activerecord/lib/active_record/fixtures.rb2
-rw-r--r--activerecord/lib/active_record/inheritance.rb10
-rw-r--r--activerecord/lib/active_record/locking/optimistic.rb8
-rw-r--r--activerecord/lib/active_record/migration.rb4
-rw-r--r--activerecord/lib/active_record/model.rb103
-rw-r--r--activerecord/lib/active_record/model_schema.rb23
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb6
-rw-r--r--activerecord/lib/active_record/persistence.rb22
-rw-r--r--activerecord/lib/active_record/railtie.rb26
-rw-r--r--activerecord/lib/active_record/railties/databases.rake244
-rw-r--r--activerecord/lib/active_record/readonly_attributes.rb2
-rw-r--r--activerecord/lib/active_record/reflection.rb64
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb51
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb133
-rw-r--r--activerecord/lib/active_record/sanitization.rb32
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/scoping/default.rb2
-rw-r--r--activerecord/lib/active_record/serialization.rb12
-rw-r--r--activerecord/lib/active_record/serializers/xml_serializer.rb5
-rw-r--r--activerecord/lib/active_record/store.rb16
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb109
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb110
-rw-r--r--activerecord/lib/active_record/tasks/postgresql_database_tasks.rb81
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb55
-rw-r--r--activerecord/lib/active_record/timestamp.rb8
54 files changed, 1020 insertions, 978 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index f8526bb691..a894d83ea7 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -36,7 +36,6 @@ module ActiveRecord
autoload :ConnectionNotEstablished, 'active_record/errors'
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
- autoload :Aggregations
autoload :Associations
autoload :AttributeMethods
autoload :AttributeAssignment
@@ -137,6 +136,16 @@ module ActiveRecord
end
end
+ module Tasks
+ extend ActiveSupport::Autoload
+
+ autoload :DatabaseTasks
+ autoload :SQLiteDatabaseTasks, 'active_record/tasks/sqlite_database_tasks'
+ autoload :MySQLDatabaseTasks, 'active_record/tasks/mysql_database_tasks'
+ autoload :PostgreSQLDatabaseTasks,
+ 'active_record/tasks/postgresql_database_tasks'
+ end
+
autoload :TestCase
autoload :TestFixtures, 'active_record/fixtures'
end
diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb
deleted file mode 100644
index 3ae7030caa..0000000000
--- a/activerecord/lib/active_record/aggregations.rb
+++ /dev/null
@@ -1,261 +0,0 @@
-module ActiveRecord
- # = Active Record Aggregations
- module Aggregations # :nodoc:
- extend ActiveSupport::Concern
-
- def clear_aggregation_cache #:nodoc:
- @aggregation_cache.clear if persisted?
- end
-
- # Active Record implements aggregation through a macro-like class method called +composed_of+
- # for representing attributes as value objects. It expresses relationships like "Account [is]
- # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call
- # to the macro adds a description of how the value objects are created from the attributes of
- # the entity object (when the entity is initialized either as a new object or from finding an
- # existing object) and how it can be turned back into attributes (when the entity is saved to
- # the database).
- #
- # class Customer < ActiveRecord::Base
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount)
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
- # end
- #
- # The customer class now has the following methods to manipulate the value objects:
- # * <tt>Customer#balance, Customer#balance=(money)</tt>
- # * <tt>Customer#address, Customer#address=(address)</tt>
- #
- # These methods will operate with value objects like the ones described below:
- #
- # class Money
- # include Comparable
- # attr_reader :amount, :currency
- # EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
- #
- # def initialize(amount, currency = "USD")
- # @amount, @currency = amount, currency
- # end
- #
- # def exchange_to(other_currency)
- # exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
- # Money.new(exchanged_amount, other_currency)
- # end
- #
- # def ==(other_money)
- # amount == other_money.amount && currency == other_money.currency
- # end
- #
- # def <=>(other_money)
- # if currency == other_money.currency
- # amount <=> other_money.amount
- # else
- # amount <=> other_money.exchange_to(currency).amount
- # end
- # end
- # end
- #
- # class Address
- # attr_reader :street, :city
- # def initialize(street, city)
- # @street, @city = street, city
- # end
- #
- # def close_to?(other_address)
- # city == other_address.city
- # end
- #
- # def ==(other_address)
- # city == other_address.city && street == other_address.street
- # end
- # end
- #
- # Now it's possible to access attributes from the database through the value objects instead. If
- # you choose to name the composition the same as the attribute's name, it will be the only way to
- # access that attribute. That's the case with our +balance+ attribute. You interact with the value
- # objects just like you would with any other attribute:
- #
- # customer.balance = Money.new(20) # sets the Money value object and the attribute
- # customer.balance # => Money value object
- # customer.balance.exchange_to("DKK") # => Money.new(120, "DKK")
- # customer.balance > Money.new(10) # => true
- # customer.balance == Money.new(20) # => true
- # customer.balance < Money.new(5) # => false
- #
- # Value objects can also be composed of multiple attributes, such as the case of Address. The order
- # of the mappings will determine the order of the parameters.
- #
- # customer.address_street = "Hyancintvej"
- # customer.address_city = "Copenhagen"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- #
- # customer.address_street = "Vesterbrogade"
- # customer.address # => Address.new("Hyancintvej", "Copenhagen")
- # customer.clear_aggregation_cache
- # customer.address # => Address.new("Vesterbrogade", "Copenhagen")
- #
- # customer.address = Address.new("May Street", "Chicago")
- # customer.address_street # => "May Street"
- # customer.address_city # => "Chicago"
- #
- # == Writing value objects
- #
- # Value objects are immutable and interchangeable objects that represent a given value, such as
- # a Money object representing $5. Two Money objects both representing $5 should be equal (through
- # methods such as <tt>==</tt> and <tt><=></tt> from Comparable if ranking makes sense). This is
- # unlike entity objects where equality is determined by identity. An entity class such as Customer can
- # easily have two different objects that both have an address on Hyancintvej. Entity identity is
- # determined by object or relational unique identifiers (such as primary keys). Normal
- # ActiveRecord::Base classes are entity objects.
- #
- # It's also important to treat the value objects as immutable. Don't allow the Money object to have
- # its amount changed after creation. Create a new Money object with the new value instead. The
- # Money#exchange_to method is an example of this. It returns a new value object instead of changing
- # its own values. Active Record won't persist value objects that have been changed through means
- # other than the writer method.
- #
- # The immutable requirement is enforced by Active Record by freezing any object assigned as a value
- # object. Attempting to change it afterwards will result in a ActiveSupport::FrozenObjectError.
- #
- # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not
- # keeping value objects immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable
- #
- # == Custom constructors and converters
- #
- # By default value objects are initialized by calling the <tt>new</tt> constructor of the value
- # class passing each of the mapped attributes, in the order specified by the <tt>:mapping</tt>
- # option, as arguments. If the value class doesn't support this convention then +composed_of+ allows
- # a custom constructor to be specified.
- #
- # When a new value is assigned to the value object, the default assumption is that the new value
- # is an instance of the value class. Specifying a custom converter allows the new value to be automatically
- # converted to an instance of value class if necessary.
- #
- # For example, the NetworkResource model has +network_address+ and +cidr_range+ attributes that
- # should be aggregated using the NetAddr::CIDR value class (http://netaddr.rubyforge.org). The constructor
- # for the value class is called +create+ and it expects a CIDR address string as a parameter. New
- # values can be assigned to the value object using either another NetAddr::CIDR object, a string
- # or an array. The <tt>:constructor</tt> and <tt>:converter</tt> options can be used to meet
- # these requirements:
- #
- # class NetworkResource < ActiveRecord::Base
- # composed_of :cidr,
- # :class_name => 'NetAddr::CIDR',
- # :mapping => [ %w(network_address network), %w(cidr_range bits) ],
- # :allow_nil => true,
- # :constructor => Proc.new { |network_address, cidr_range| NetAddr::CIDR.create("#{network_address}/#{cidr_range}") },
- # :converter => Proc.new { |value| NetAddr::CIDR.create(value.is_a?(Array) ? value.join('/') : value) }
- # end
- #
- # # This calls the :constructor
- # network_resource = NetworkResource.new(:network_address => '192.168.0.1', :cidr_range => 24)
- #
- # # These assignments will both use the :converter
- # network_resource.cidr = [ '192.168.2.1', 8 ]
- # network_resource.cidr = '192.168.0.1/24'
- #
- # # This assignment won't use the :converter as the value is already an instance of the value class
- # network_resource.cidr = NetAddr::CIDR.create('192.168.2.1/8')
- #
- # # Saving and then reloading will use the :constructor on reload
- # network_resource.save
- # network_resource.reload
- #
- # == Finding records by a value object
- #
- # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database
- # by specifying an instance of the value object in the conditions hash. The following example
- # finds all customers with +balance_amount+ equal to 20 and +balance_currency+ equal to "USD":
- #
- # Customer.where(:balance => Money.new(20, "USD")).all
- #
- module ClassMethods
- # Adds reader and writer methods for manipulating a value object:
- # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.
- #
- # Options are:
- # * <tt>:class_name</tt> - Specifies the class name of the association. Use it only if that name
- # can't be inferred from the part id. So <tt>composed_of :address</tt> will by default be linked
- # to the Address class, but if the real class name is CompanyAddress, you'll have to specify it
- # with this option.
- # * <tt>:mapping</tt> - Specifies the mapping of entity attributes to attributes of the value
- # object. Each mapping is represented as an array where the first item is the name of the
- # entity attribute and the second item is the name of the attribute in the value object. The
- # order in which mappings are defined determines the order in which attributes are sent to the
- # value class constructor.
- # * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
- # attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
- # mapped attributes.
- # This defaults to +false+.
- # * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
- # is called to initialize the value object. The constructor is passed all of the mapped attributes,
- # in the order that they are defined in the <tt>:mapping option</tt>, as arguments and uses them
- # to instantiate a <tt>:class_name</tt> object.
- # The default is <tt>:new</tt>.
- # * <tt>:converter</tt> - A symbol specifying the name of a class method of <tt>:class_name</tt>
- # or a Proc that is called when a new value is assigned to the value object. The converter is
- # passed the single value that is used in the assignment and is only called if the new value is
- # not an instance of <tt>:class_name</tt>. If <tt>:allow_nil</tt> is set to true, the converter
- # can return nil to skip the assignment.
- #
- # Option examples:
- # composed_of :temperature, :mapping => %w(reading celsius)
- # composed_of :balance, :class_name => "Money", :mapping => %w(balance amount),
- # :converter => Proc.new { |balance| balance.to_money }
- # composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ]
- # composed_of :gps_location
- # composed_of :gps_location, :allow_nil => true
- # composed_of :ip_address,
- # :class_name => 'IPAddr',
- # :mapping => %w(ip to_i),
- # :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
- # :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
- #
- def composed_of(part_id, options = {})
- options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
-
- name = part_id.id2name
- class_name = options[:class_name] || name.camelize
- mapping = options[:mapping] || [ name, name ]
- mapping = [ mapping ] unless mapping.first.is_a?(Array)
- allow_nil = options[:allow_nil] || false
- constructor = options[:constructor] || :new
- converter = options[:converter]
-
- reader_method(name, class_name, mapping, allow_nil, constructor)
- writer_method(name, class_name, mapping, allow_nil, converter)
-
- create_reflection(:composed_of, part_id, options, self)
- end
-
- private
- def reader_method(name, class_name, mapping, allow_nil, constructor)
- define_method(name) do
- if @aggregation_cache[name].nil? && (!allow_nil || mapping.any? {|pair| !read_attribute(pair.first).nil? })
- attrs = mapping.collect {|pair| read_attribute(pair.first)}
- object = constructor.respond_to?(:call) ?
- constructor.call(*attrs) :
- class_name.constantize.send(constructor, *attrs)
- @aggregation_cache[name] = object
- end
- @aggregation_cache[name]
- end
- end
-
- def writer_method(name, class_name, mapping, allow_nil, converter)
- define_method("#{name}=") do |part|
- klass = class_name.constantize
- unless part.is_a?(klass) || converter.nil? || part.nil?
- part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part)
- end
-
- if part.nil? && allow_nil
- mapping.each { |pair| self[pair.first] = nil }
- @aggregation_cache[name] = nil
- else
- mapping.each { |pair| self[pair.first] = part.send(pair.last) }
- @aggregation_cache[name] = part.freeze
- end
- end
- end
- end
- end
-end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 30fc44b4c2..f7656ecd47 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -6,7 +6,6 @@ module ActiveRecord::Associations::Builder
def build
reflection = super
- check_validity(reflection)
define_destroy_hook
reflection
end
@@ -24,34 +23,5 @@ module ActiveRecord::Associations::Builder
RUBY
})
end
-
- # TODO: These checks should probably be moved into the Reflection, and we should not be
- # redefining the options[:join_table] value - instead we should define a
- # reflection.join_table method.
- def check_validity(reflection)
- if reflection.association_foreign_key == reflection.foreign_key
- raise ActiveRecord::HasAndBelongsToManyAssociationForeignKeyNeeded.new(reflection)
- end
-
- reflection.options[:join_table] ||= join_table_name(
- model.send(:undecorated_table_name, model.to_s),
- model.send(:undecorated_table_name, reflection.class_name)
- )
- end
-
- # Generates a join table name from two provided table names.
- # The names in the join table names end up in lexicographic order.
- #
- # join_table_name("members", "clubs") # => "clubs_members"
- # join_table_name("members", "special_clubs") # => "members_special_clubs"
- def join_table_name(first_table_name, second_table_name)
- if first_table_name < second_table_name
- join_table = "#{first_table_name}_#{second_table_name}"
- else
- join_table = "#{second_table_name}_#{first_table_name}"
- end
-
- model.table_name_prefix + join_table + model.table_name_suffix
- end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index e94fe35170..2f6ddfeeb3 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -50,18 +50,7 @@ module ActiveRecord
end
else
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
- relation = scoped
-
- including = (relation.eager_load_values + relation.includes_values).uniq
-
- if including.any?
- join_dependency = ActiveRecord::Associations::JoinDependency.new(reflection.klass, including, [])
- relation = join_dependency.join_associations.inject(relation) do |r, association|
- association.join_relation(r)
- end
- end
-
- relation.pluck(column)
+ scoped.pluck(column)
end
end
@@ -197,7 +186,7 @@ module ActiveRecord
if options[:uniq]
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
column_name ||= reflection.klass.primary_key
- count_options.merge!(:distinct => true)
+ count_options[:distinct] = true
end
value = scoped.count(column_name, count_options)
diff --git a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
index 58d041ec1d..93618721bb 100644
--- a/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
+++ b/activerecord/lib/active_record/associations/has_and_belongs_to_many_association.rb
@@ -5,7 +5,7 @@ module ActiveRecord
attr_reader :join_table
def initialize(owner, reflection)
- @join_table = Arel::Table.new(reflection.options[:join_table])
+ @join_table = Arel::Table.new(reflection.join_table)
super
end
diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb
index 2131edbc20..f0d1120c68 100644
--- a/activerecord/lib/active_record/associations/has_one_association.rb
+++ b/activerecord/lib/active_record/associations/has_one_association.rb
@@ -36,7 +36,7 @@ module ActiveRecord
when :destroy
target.destroy
when :nullify
- target.update_attribute(reflection.foreign_key, nil)
+ target.update_column(reflection.foreign_key, nil)
end
end
end
diff --git a/activerecord/lib/active_record/associations/join_helper.rb b/activerecord/lib/active_record/associations/join_helper.rb
index cea6ad6944..5a41b40c8f 100644
--- a/activerecord/lib/active_record/associations/join_helper.rb
+++ b/activerecord/lib/active_record/associations/join_helper.rb
@@ -19,7 +19,7 @@ module ActiveRecord
if reflection.source_macro == :has_and_belongs_to_many
tables << alias_tracker.aliased_table_for(
- (reflection.source_reflection || reflection).options[:join_table],
+ (reflection.source_reflection || reflection).join_table,
table_alias_for(reflection, true)
)
end
diff --git a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
index b77b667219..8e8925f0a9 100644
--- a/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/preloader/has_and_belongs_to_many.rb
@@ -6,7 +6,7 @@ module ActiveRecord
def initialize(klass, records, reflection, preload_options)
super
- @join_table = Arel::Table.new(options[:join_table]).alias('t0')
+ @join_table = Arel::Table.new(reflection.join_table).alias('t0')
end
# Unlike the other associations, we want to get a raw array of rows so that we can
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index abc2fa546a..ab6d253ef8 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -1,11 +1,25 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :whitelist_attributes, instance_accessor: false
+ mattr_accessor :mass_assignment_sanitizer, instance_accessor: false
+ end
+
module AttributeAssignment
extend ActiveSupport::Concern
include ActiveModel::MassAssignmentSecurity
+ included do
+ initialize_mass_assignment_sanitizer
+ end
+
module ClassMethods
+ def inherited(child) # :nodoc:
+ child.send :initialize_mass_assignment_sanitizer if self == Base
+ super
+ end
+
private
# The primary key and inheritance column can never be set by mass-assignment for security reasons.
@@ -14,6 +28,11 @@ module ActiveRecord
default << 'id' unless primary_key.eql? 'id'
default
end
+
+ def initialize_mass_assignment_sanitizer
+ attr_accessible(nil) if Model.whitelist_attributes
+ self.mass_assignment_sanitizer = Model.mass_assignment_sanitizer if Model.mass_assignment_sanitizer
+ end
end
# Allows you to set all the attributes at once by passing in a hash with keys
@@ -113,7 +132,7 @@ module ActiveRecord
private
# Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
- # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
+ # by calling new on the column type or aggregation type object with these parameters.
# So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
# written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
# parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum,
@@ -148,7 +167,7 @@ module ActiveRecord
end
def read_value_from_parameter(name, values_hash_from_param)
- klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
+ klass = column_for_attribute(name).klass
if values_hash_from_param.values.all?{|v|v.nil?}
nil
elsif klass == Time
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 172026d150..f36df4a444 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -16,19 +16,6 @@ module ActiveRecord
include TimeZoneConversion
include Dirty
include Serialization
-
- # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
- # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
- # (Alias for the protected read_attribute method).
- def [](attr_name)
- read_attribute(attr_name)
- end
-
- # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
- # (Alias for the protected write_attribute method).
- def []=(attr_name, value)
- write_attribute(attr_name, value)
- end
end
module ClassMethods
@@ -192,6 +179,19 @@ module ActiveRecord
self.class.columns_hash[name.to_s]
end
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
+ # (Alias for the protected read_attribute method).
+ def [](attr_name)
+ read_attribute(attr_name)
+ end
+
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
+ # (Alias for the protected write_attribute method).
+ def []=(attr_name, value)
+ write_attribute(attr_name, value)
+ end
+
protected
def clone_attributes(reader_method = :read_attribute, attributes = {})
diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb
index 11c63591e3..a24b4b7839 100644
--- a/activerecord/lib/active_record/attribute_methods/dirty.rb
+++ b/activerecord/lib/active_record/attribute_methods/dirty.rb
@@ -1,12 +1,18 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :partial_updates, instance_accessor: false
+ self.partial_updates = true
+ end
+
module AttributeMethods
module Dirty
extend ActiveSupport::Concern
+
include ActiveModel::Dirty
- include AttributeMethods::Write
included do
if self < ::ActiveRecord::Timestamp
@@ -14,7 +20,6 @@ module ActiveRecord
end
config_attribute :partial_updates
- self.partial_updates = true
end
# Attempts to +save+ the record and clears changed attributes if successful.
@@ -72,11 +77,8 @@ module ActiveRecord
def _field_changed?(attr, old, value)
if column = column_for_attribute(attr)
- 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)
+ if column.number? && (changes_from_nil_to_empty_string?(column, old, value) ||
+ changes_from_zero_to_string?(old, value))
value = nil
else
value = column.type_cast(value)
@@ -85,6 +87,19 @@ module ActiveRecord
old != value
end
+
+ def changes_from_nil_to_empty_string?(column, old, value)
+ # 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)
+ column.null && (old.nil? || old == 0) && value.blank?
+ end
+
+ def changes_from_zero_to_string?(old, value)
+ # For columns with old 0 and value non-empty string
+ old == 0 && value.present? && value != '0'
+ end
end
end
end
diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb
index dcc3d79de9..a7af086e43 100644
--- a/activerecord/lib/active_record/attribute_methods/read.rb
+++ b/activerecord/lib/active_record/attribute_methods/read.rb
@@ -1,13 +1,17 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :attribute_types_cached_by_default, instance_accessor: false
+ end
+
module AttributeMethods
module Read
extend ActiveSupport::Concern
ATTRIBUTE_TYPES_CACHED_BY_DEFAULT = [:datetime, :timestamp, :time, :date]
+ ActiveRecord::Model.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
included do
- config_attribute :attribute_types_cached_by_default, :global => true
- self.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
+ config_attribute :attribute_types_cached_by_default
end
module ClassMethods
diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb
index 706fbf0546..4af4d28b74 100644
--- a/activerecord/lib/active_record/attribute_methods/serialization.rb
+++ b/activerecord/lib/active_record/attribute_methods/serialization.rb
@@ -6,7 +6,7 @@ module ActiveRecord
included do
# Returns a hash of all the attributes that have been specified for serialization as
# keys and their class restriction as values.
- config_attribute :serialized_attributes
+ class_attribute :serialized_attributes
self.serialized_attributes = {}
end
diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
index 58a5d82e14..e300c9721f 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -2,6 +2,14 @@ require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/object/inclusion'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :time_zone_aware_attributes, instance_accessor: false
+ self.time_zone_aware_attributes = false
+
+ mattr_accessor :skip_time_zone_conversion_for_attributes, instance_accessor: false
+ self.skip_time_zone_conversion_for_attributes = []
+ end
+
module AttributeMethods
module TimeZoneConversion
class Type # :nodoc:
@@ -22,11 +30,8 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :time_zone_aware_attributes, :global => true
- self.time_zone_aware_attributes = false
-
+ config_attribute :time_zone_aware_attributes, global: true
config_attribute :skip_time_zone_conversion_for_attributes
- self.skip_time_zone_conversion_for_attributes = []
end
module ClassMethods
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index b5e3f2b4b5..111208d0b9 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -231,30 +231,6 @@ module ActiveRecord
# Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
#
module Callbacks
- # We can't define callbacks directly on ActiveRecord::Model because
- # it is a module. So we queue up the definitions and execute them
- # when ActiveRecord::Model is included.
- module Register #:nodoc:
- def self.extended(base)
- base.config_attribute :_callbacks_register
- base._callbacks_register = []
- end
-
- def self.setup(base)
- base._callbacks_register.each do |item|
- base.send(*item)
- end
- end
-
- def define_callbacks(*args)
- self._callbacks_register << [:define_callbacks, *args]
- end
-
- def define_model_callbacks(*args)
- self._callbacks_register << [:define_model_callbacks, *args]
- end
- end
-
extend ActiveSupport::Concern
CALLBACKS = [
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 61ff603006..347d794fa3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -448,11 +448,11 @@ module ActiveRecord
end
def new_connection
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
+ ActiveRecord::Model.send(spec.adapter_method, spec.config)
end
def current_connection_id #:nodoc:
- ActiveRecord::Base.connection_id ||= Thread.current.object_id
+ ActiveRecord::Model.connection_id ||= Thread.current.object_id
end
def checkout_new_connection
@@ -563,10 +563,12 @@ module ActiveRecord
end
def retrieve_connection_pool(klass)
- pool = get_pool_for_class klass.name
- return pool if pool
- return nil if ActiveRecord::Model == klass
- retrieve_connection_pool klass.active_record_super
+ if !(klass < Model::Tag)
+ get_pool_for_class('ActiveRecord::Model') # default connection
+ else
+ pool = get_pool_for_class(klass.name)
+ pool || retrieve_connection_pool(klass.superclass)
+ end
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 4c6d03a1d2..b0b51f540c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -370,7 +370,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.rolledback!(rollback)
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
@@ -385,7 +385,7 @@ module ActiveRecord
records.uniq.each do |record|
begin
record.committed!
- rescue Exception => e
+ rescue => e
record.logger.error(e) if record.respond_to?(:logger) && record.logger
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index c6faae77cc..28a9821913 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -286,7 +286,7 @@ module ActiveRecord
:name => name,
:connection_id => object_id,
:binds => binds) { yield }
- rescue Exception => e
+ rescue => e
message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger
exception = translate_exception(e, message)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 692473abc5..921278d145 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -264,19 +264,19 @@ module ActiveRecord
def begin_db_transaction
execute "BEGIN"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def commit_db_transaction #:nodoc:
execute "COMMIT"
- rescue Exception
+ rescue
# Transactions aren't supported
end
def rollback_db_transaction #:nodoc:
execute "ROLLBACK"
- rescue Exception
+ rescue
# Transactions aren't supported
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
index df3d5e4657..6657491c06 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb
@@ -223,6 +223,7 @@ module ActiveRecord
alias_type 'bit', 'text'
alias_type 'varbit', 'text'
alias_type 'macaddr', 'text'
+ alias_type 'uuid', 'text'
# FIXME: I don't think this is correct. We should probably be returning a parsed date,
# but the tests pass with a string returned.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 03c318f5f7..43e243581c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -89,7 +89,6 @@ module ActiveRecord
else
string
end
-
end
def cidr_to_string(object)
@@ -256,7 +255,7 @@ module ActiveRecord
:integer
# UUID type
when 'uuid'
- :string
+ :uuid
# Small and big integer types
when /^(?:small|big)int$/
:integer
@@ -319,6 +318,10 @@ module ActiveRecord
def macaddr(name, options = {})
column(name, 'macaddr', options)
end
+
+ def uuid(name, options = {})
+ column(name, 'uuid', options)
+ end
end
ADAPTER_NAME = 'PostgreSQL'
@@ -341,7 +344,8 @@ module ActiveRecord
:hstore => { :name => "hstore" },
:inet => { :name => "inet" },
:cidr => { :name => "cidr" },
- :macaddr => { :name => "macaddr" }
+ :macaddr => { :name => "macaddr" },
+ :uuid => { :name => "uuid" }
}
# Returns 'PostgreSQL' as adapter name for identification purposes.
@@ -1446,7 +1450,7 @@ module ActiveRecord
if @config[:encoding]
@connection.set_client_encoding(@config[:encoding])
end
- self.client_min_messages = @config[:min_messages] if @config[:min_messages]
+ self.client_min_messages = @config[:min_messages] || 'warning'
self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
# Use standard-conforming strings if available so we don't have to do the E'...' dance.
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 7b218a5570..bda41df80f 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -90,6 +90,10 @@ module ActiveRecord
connection_handler.remove_connection(klass)
end
+ def clear_cache! # :nodoc:
+ connection.schema_cache.clear!
+ end
+
delegate :clear_active_connections!, :clear_reloadable_connections!,
:clear_all_connections!, :to => :connection_handler
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index dbad561ca2..90f156456e 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -1,80 +1,88 @@
require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/object/deep_dup'
+require 'active_support/core_ext/module/delegation'
require 'thread'
module ActiveRecord
- module Core
- extend ActiveSupport::Concern
+ ActiveSupport.on_load(:active_record_config) do
+ ##
+ # :singleton-method:
+ #
+ # Accepts a logger conforming to the interface of Log4r 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+.
+ mattr_accessor :logger, instance_accessor: false
- included do
- ##
- # :singleton-method:
- #
- # Accepts a logger conforming to the interface of Log4r 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+.
- config_attribute :logger, :global => true
+ ##
+ # :singleton-method:
+ # Contains the database configuration - as is typically stored in config/database.yml -
+ # as a Hash.
+ #
+ # For example, the following database.yml...
+ #
+ # development:
+ # adapter: sqlite3
+ # database: db/development.sqlite3
+ #
+ # production:
+ # adapter: sqlite3
+ # database: db/production.sqlite3
+ #
+ # ...would result in ActiveRecord::Base.configurations to look like this:
+ #
+ # {
+ # 'development' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/development.sqlite3'
+ # },
+ # 'production' => {
+ # 'adapter' => 'sqlite3',
+ # 'database' => 'db/production.sqlite3'
+ # }
+ # }
+ mattr_accessor :configurations, instance_accessor: false
+ self.configurations = {}
- ##
- # :singleton-method:
- # Contains the database configuration - as is typically stored in config/database.yml -
- # as a Hash.
- #
- # For example, the following database.yml...
- #
- # development:
- # adapter: sqlite3
- # database: db/development.sqlite3
- #
- # production:
- # adapter: sqlite3
- # database: db/production.sqlite3
- #
- # ...would result in ActiveRecord::Base.configurations to look like this:
- #
- # {
- # 'development' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/development.sqlite3'
- # },
- # 'production' => {
- # 'adapter' => 'sqlite3',
- # 'database' => 'db/production.sqlite3'
- # }
- # }
- config_attribute :configurations, :global => true
- self.configurations = {}
+ ##
+ # :singleton-method:
+ # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
+ # dates and times from the database. This is set to :utc by default.
+ mattr_accessor :default_timezone, instance_accessor: false
+ self.default_timezone = :utc
- ##
- # :singleton-method:
- # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling
- # dates and times from the database. This is set to :utc by default.
- config_attribute :default_timezone, :global => true
- self.default_timezone = :utc
+ ##
+ # :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
+ # ActiveRecord::Schema file which can be loaded into any database that
+ # supports migrations. Use :ruby if you want to have different database
+ # adapters for, e.g., your development and test environments.
+ mattr_accessor :schema_format, instance_accessor: false
+ self.schema_format = :ruby
- ##
- # :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
- # ActiveRecord::Schema file which can be loaded into any database that
- # supports migrations. Use :ruby if you want to have different database
- # adapters for, e.g., your development and test environments.
- config_attribute :schema_format, :global => true
- self.schema_format = :ruby
+ ##
+ # :singleton-method:
+ # Specify whether or not to use timestamps for migration versions
+ mattr_accessor :timestamped_migrations, instance_accessor: false
+ self.timestamped_migrations = true
- ##
- # :singleton-method:
- # Specify whether or not to use timestamps for migration versions
- config_attribute :timestamped_migrations, :global => true
- self.timestamped_migrations = true
+ mattr_accessor :connection_handler, instance_accessor: false
+ self.connection_handler = ConnectionAdapters::ConnectionHandler.new
+
+ mattr_accessor :dependent_restrict_raises, instance_accessor: false
+ self.dependent_restrict_raises = true
+ end
+ module Core
+ extend ActiveSupport::Concern
+
+ included do
##
# :singleton-method:
# The connection handler
config_attribute :connection_handler
- self.connection_handler = ConnectionAdapters::ConnectionHandler.new
##
# :singleton-method:
@@ -83,8 +91,11 @@ module ActiveRecord
# ActiveRecord::DeleteRestrictionError exception will be raised
# along with a DEPRECATION WARNING. If set to false, an error would
# be added to the model instead.
- config_attribute :dependent_restrict_raises, :global => true
- self.dependent_restrict_raises = true
+ config_attribute :dependent_restrict_raises
+
+ %w(logger configurations default_timezone schema_format timestamped_migrations).each do |name|
+ config_attribute name, global: true
+ end
end
module ClassMethods
@@ -255,7 +266,6 @@ module ActiveRecord
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
end
- @aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@@ -380,7 +390,6 @@ module ActiveRecord
@attributes[pk] = nil unless @attributes.key?(pk)
- @aggregation_cache = {}
@association_cache = {}
@attributes_cache = {}
@previously_changed = {}
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index b163ef3c12..b27a19f89a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -1,111 +1,115 @@
module ActiveRecord
# = Active Record Counter Cache
module CounterCache
- # Resets one or more counter caches to their correct value using an SQL
- # count query. This is useful when adding new counter caches, or if the
- # counter has been corrupted or modified directly by SQL.
- #
- # ==== Parameters
- #
- # * +id+ - The id of the object you wish to reset a counter on.
- # * +counters+ - One or more counter names to reset
- #
- # ==== Examples
- #
- # # For Post with id #1 records reset the comments_count
- # Post.reset_counters(1, :comments)
- def reset_counters(id, *counters)
- object = find(id)
- counters.each do |association|
- has_many_association = reflect_on_association(association.to_sym)
+ extend ActiveSupport::Concern
- foreign_key = has_many_association.foreign_key.to_s
- child_class = has_many_association.klass
- belongs_to = child_class.reflect_on_all_associations(:belongs_to)
- reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
- counter_name = reflection.counter_cache_column
+ module ClassMethods
+ # Resets one or more counter caches to their correct value using an SQL
+ # count query. This is useful when adding new counter caches, or if the
+ # counter has been corrupted or modified directly by SQL.
+ #
+ # ==== Parameters
+ #
+ # * +id+ - The id of the object you wish to reset a counter on.
+ # * +counters+ - One or more counter names to reset
+ #
+ # ==== Examples
+ #
+ # # For Post with id #1 records reset the comments_count
+ # Post.reset_counters(1, :comments)
+ def reset_counters(id, *counters)
+ object = find(id)
+ counters.each do |association|
+ has_many_association = reflect_on_association(association.to_sym)
- stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
- arel_table[counter_name] => object.send(association).count
- })
- connection.update stmt
- end
- return true
- end
+ foreign_key = has_many_association.foreign_key.to_s
+ child_class = has_many_association.klass
+ belongs_to = child_class.reflect_on_all_associations(:belongs_to)
+ reflection = belongs_to.find { |e| e.foreign_key.to_s == foreign_key }
+ counter_name = reflection.counter_cache_column
- # A generic "counter updater" implementation, intended primarily to be
- # used by increment_counter and decrement_counter, but which may also
- # be useful on its own. It simply does a direct SQL update for the record
- # with the given ID, altering the given hash of counters by the amount
- # given by the corresponding value:
- #
- # ==== Parameters
- #
- # * +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.
- #
- # ==== Examples
- #
- # # For the Post with id of 5, decrement the comment_count by 1, and
- # # increment the action_count by 1
- # Post.update_counters 5, :comment_count => -1, :action_count => 1
- # # Executes the following SQL:
- # # UPDATE posts
- # # SET comment_count = COALESCE(comment_count, 0) - 1,
- # # action_count = COALESCE(action_count, 0) + 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 = COALESCE(comment_count, 0) + 1
- # # WHERE id IN (10, 15)
- def update_counters(id, counters)
- updates = counters.map do |counter_name, value|
- operator = value < 0 ? '-' : '+'
- quoted_column = connection.quote_column_name(counter_name)
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ stmt = unscoped.where(arel_table[primary_key].eq(object.id)).arel.compile_update({
+ arel_table[counter_name] => object.send(association).count
+ })
+ connection.update stmt
+ end
+ return true
end
- where(primary_key => id).update_all updates.join(', ')
- end
+ # A generic "counter updater" implementation, intended primarily to be
+ # used by increment_counter and decrement_counter, but which may also
+ # be useful on its own. It simply does a direct SQL update for the record
+ # with the given ID, altering the given hash of counters by the amount
+ # given by the corresponding value:
+ #
+ # ==== Parameters
+ #
+ # * +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.
+ #
+ # ==== Examples
+ #
+ # # For the Post with id of 5, decrement the comment_count by 1, and
+ # # increment the action_count by 1
+ # Post.update_counters 5, :comment_count => -1, :action_count => 1
+ # # Executes the following SQL:
+ # # UPDATE posts
+ # # SET comment_count = COALESCE(comment_count, 0) - 1,
+ # # action_count = COALESCE(action_count, 0) + 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 = COALESCE(comment_count, 0) + 1
+ # # WHERE id IN (10, 15)
+ def update_counters(id, counters)
+ updates = counters.map do |counter_name, value|
+ operator = value < 0 ? '-' : '+'
+ quoted_column = connection.quote_column_name(counter_name)
+ "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ end
- # Increment a number field by one, usually representing a count.
- #
- # This is used for caching aggregate values, so that they don't need to be computed every time.
- # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
- # shown it would have to run an SQL query to find how many posts and comments there are.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be incremented.
- # * +id+ - The id of the object that should be incremented.
- #
- # ==== Examples
- #
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
- def increment_counter(counter_name, id)
- update_counters(id, counter_name => 1)
- end
+ where(primary_key => id).update_all updates.join(', ')
+ end
+
+ # Increment a number field by one, usually representing a count.
+ #
+ # This is used for caching aggregate values, so that they don't need to be computed every time.
+ # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
+ # shown it would have to run an SQL query to find how many posts and comments there are.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be incremented.
+ # * +id+ - The id of the object that should be incremented.
+ #
+ # ==== Examples
+ #
+ # # Increment the post_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:post_count, 5)
+ def increment_counter(counter_name, id)
+ update_counters(id, counter_name => 1)
+ end
- # Decrement a number field by one, usually representing a count.
- #
- # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
- #
- # ==== Parameters
- #
- # * +counter_name+ - The name of the field that should be decremented.
- # * +id+ - The id of the object that should be decremented.
- #
- # ==== Examples
- #
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
- def decrement_counter(counter_name, id)
- update_counters(id, counter_name => -1)
+ # Decrement a number field by one, usually representing a count.
+ #
+ # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
+ #
+ # ==== Parameters
+ #
+ # * +counter_name+ - The name of the field that should be decremented.
+ # * +id+ - The id of the object that should be decremented.
+ #
+ # ==== Examples
+ #
+ # # Decrement the post_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:post_count, 5)
+ def decrement_counter(counter_name, id)
+ update_counters(id, counter_name => -1)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/dynamic_matchers.rb b/activerecord/lib/active_record/dynamic_matchers.rb
index e278e62ce7..a37cde77ee 100644
--- a/activerecord/lib/active_record/dynamic_matchers.rb
+++ b/activerecord/lib/active_record/dynamic_matchers.rb
@@ -53,10 +53,11 @@ module ActiveRecord
@model = model
@name = name.to_s
@attribute_names = @name.match(self.class.pattern)[1].split('_and_')
+ @attribute_names.map! { |n| @model.attribute_aliases[n] || n }
end
def valid?
- attribute_names.all? { |name| model.columns_hash[name] || model.reflect_on_aggregation(name.to_sym) }
+ attribute_names.all? { |name| model.columns_hash[name] }
end
def define
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 9b88bb8178..858b667e22 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -196,4 +196,7 @@ module ActiveRecord
"Unknown primary key for table #{model.table_name} in model #{model}."
end
end
+
+ class ImmutableRelation < ActiveRecordError
+ end
end
diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb
index b0eda8ef34..7ade385c70 100644
--- a/activerecord/lib/active_record/explain.rb
+++ b/activerecord/lib/active_record/explain.rb
@@ -1,12 +1,13 @@
+require 'active_support/lazy_load_hooks'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :auto_explain_threshold_in_seconds, instance_accessor: false
+ end
+
module Explain
- def self.extended(base)
- # If a query takes longer than these many seconds we log its query plan
- # automatically. nil disables this feature.
- base.config_attribute :auto_explain_threshold_in_seconds, :global => true
- end
+ delegate :auto_explain_threshold_in_seconds, :auto_explain_threshold_in_seconds=, to: 'ActiveRecord::Model'
# If auto explain is enabled, this method triggers EXPLAIN logging for the
# queries triggered by the block if it takes more than the threshold as a
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 1f8c4fc203..d5ba343b4c 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -2,9 +2,12 @@ require 'active_support/notifications'
module ActiveRecord
class ExplainSubscriber # :nodoc:
- def call(*args)
+ def start(name, id, payload)
+ # unused
+ end
+
+ def finish(name, id, payload)
if queries = Thread.current[:available_queries_for_explain]
- payload = args.last
queries << payload.values_at(:sql, :binds) unless ignore_payload?(payload)
end
end
diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb
index 7e6512501c..96d24b72b3 100644
--- a/activerecord/lib/active_record/fixtures.rb
+++ b/activerecord/lib/active_record/fixtures.rb
@@ -594,7 +594,7 @@ module ActiveRecord
when :has_and_belongs_to_many
if (targets = row.delete(association.name.to_s))
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
- table_name = association.options[:join_table]
+ table_name = association.join_table
rows[table_name].concat targets.map { |target|
{ association.foreign_key => row[primary_key_name],
association.association_foreign_key => ActiveRecord::Fixtures.identify(target) }
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 46d253b0a7..770083ac13 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -1,13 +1,17 @@
require 'active_support/concern'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ # Determine whether to store the full constant name including namespace when using STI
+ mattr_accessor :store_full_sti_class, instance_accessor: false
+ self.store_full_sti_class = true
+ end
+
module Inheritance
extend ActiveSupport::Concern
included do
- # Determine whether to store the full constant name including namespace when using STI
config_attribute :store_full_sti_class
- self.store_full_sti_class = true
end
module ClassMethods
@@ -95,7 +99,7 @@ module ActiveRecord
# 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)
- unless klass < Model
+ unless klass < Model::Tag
raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
end
diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb
index 05e052b953..4ce42feb74 100644
--- a/activerecord/lib/active_record/locking/optimistic.rb
+++ b/activerecord/lib/active_record/locking/optimistic.rb
@@ -1,4 +1,9 @@
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :lock_optimistically, instance_accessor: false
+ self.lock_optimistically = true
+ end
+
module Locking
# == What is Optimistic Locking
#
@@ -51,8 +56,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :lock_optimistically, :global => true
- self.lock_optimistically = true
+ config_attribute :lock_optimistically
end
def locking_enabled? #:nodoc:
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index acec65991d..d58176bc62 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -238,7 +238,7 @@ module ActiveRecord
# add_column :people, :salary, :integer
# Person.reset_column_information
# Person.all.each do |p|
- # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # p.update_column :salary, SalaryCalculator.compute(p)
# end
# end
# end
@@ -258,7 +258,7 @@ module ActiveRecord
# ...
# say_with_time "Updating salaries..." do
# Person.all.each do |p|
- # p.update_attribute :salary, SalaryCalculator.compute(p)
+ # p.update_column :salary, SalaryCalculator.compute(p)
# end
# end
# ...
diff --git a/activerecord/lib/active_record/model.rb b/activerecord/lib/active_record/model.rb
index 105d1e0e2b..7b3d926d91 100644
--- a/activerecord/lib/active_record/model.rb
+++ b/activerecord/lib/active_record/model.rb
@@ -1,6 +1,34 @@
require 'active_support/deprecation'
+require 'active_support/concern'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/module/attribute_accessors'
module ActiveRecord
+ module Configuration # :nodoc:
+ # This just abstracts out how we define configuration options in AR. Essentially we
+ # have mattr_accessors on the ActiveRecord:Model constant that define global defaults.
+ # Classes that then use AR get class_attributes defined, which means that when they
+ # are assigned the default will be overridden for that class and subclasses. (Except
+ # when options[:global] == true, in which case there is one global value always.)
+ def config_attribute(name, options = {})
+ if options[:global]
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def self.#{name}; ActiveRecord::Model.#{name}; end
+ def #{name}; ActiveRecord::Model.#{name}; end
+ def self.#{name}=(val); ActiveRecord::Model.#{name} = val; end
+ CODE
+ else
+ options[:instance_writer] ||= false
+ class_attribute name, options
+
+ singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ remove_method :#{name}
+ def #{name}; ActiveRecord::Model.#{name}; end
+ CODE
+ end
+ end
+ end
+
# <tt>ActiveRecord::Model</tt> can be included into a class to add Active Record persistence.
# This is an alternative to inheriting from <tt>ActiveRecord::Base</tt>. Example:
#
@@ -9,41 +37,35 @@ module ActiveRecord
# end
#
module Model
- module ClassMethods #:nodoc:
- include ActiveSupport::Callbacks::ClassMethods
- include ActiveModel::Naming
- include QueryCache::ClassMethods
- include ActiveSupport::Benchmarkable
- include ActiveSupport::DescendantsTracker
-
- include Querying
- include Translation
- include DynamicMatchers
- include CounterCache
- include Explain
- include ConnectionHandling
- end
+ extend ActiveSupport::Concern
+ extend ConnectionHandling
+ extend ActiveModel::Observing::ClassMethods
- def self.included(base)
- return if base.singleton_class < ClassMethods
+ # This allows us to detect an ActiveRecord::Model while it's in the process of being included.
+ module Tag; end
+ def self.append_features(base)
base.class_eval do
- extend ClassMethods
- Callbacks::Register.setup(self)
- initialize_generated_modules unless self == Base
+ include Tag
+ extend Configuration
end
+
+ super
end
- extend ActiveModel::Configuration
- extend ActiveModel::Callbacks
- extend ActiveModel::MassAssignmentSecurity::ClassMethods
- extend ActiveModel::AttributeMethods::ClassMethods
- extend Callbacks::Register
- extend Explain
- extend ConnectionHandling
+ included do
+ extend ActiveModel::Naming
+ extend ActiveSupport::Benchmarkable
+ extend ActiveSupport::DescendantsTracker
+
+ extend QueryCache::ClassMethods
+ extend Querying
+ extend Translation
+ extend DynamicMatchers
+ extend Explain
+ extend ConnectionHandling
- def self.extend(*modules)
- ClassMethods.send(:include, *modules)
+ initialize_generated_modules unless self == Base
end
include Persistence
@@ -56,13 +78,21 @@ module ActiveRecord
include AttributeAssignment
include ActiveModel::Conversion
include Validations
- include Locking::Optimistic, Locking::Pessimistic
+ include CounterCache
+ include Locking::Optimistic
+ include Locking::Pessimistic
include AttributeMethods
- include Callbacks, ActiveModel::Observing, Timestamp
+ include Callbacks
+ include ActiveModel::Observing
+ include Timestamp
include Associations
include ActiveModel::SecurePassword
- include AutosaveAssociation, NestedAttributes
- include Aggregations, Transactions, Reflection, Serialization, Store
+ include AutosaveAssociation
+ include NestedAttributes
+ include Transactions
+ include Reflection
+ include Serialization
+ include Store
include Core
class << self
@@ -103,6 +133,15 @@ module ActiveRecord
end
end
+ # This hook is where config accessors on Model get defined.
+ #
+ # We don't want to just open the Model module and add stuff to it in other files, because
+ # that would cause Model to load, which causes all sorts of loading order issues.
+ #
+ # We need this hook rather than just using the :active_record one, because users of the
+ # :active_record hook may need to use config options.
+ ActiveSupport.run_load_hooks(:active_record_config, Model)
+
# Load Base at this point, because the active_record load hook is run in that file.
Base
end
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 7f38dda11e..e6b76ddc4c 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -1,7 +1,19 @@
require 'active_support/concern'
-require 'active_support/core_ext/class/attribute_accessors'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :primary_key_prefix_type, instance_accessor: false
+
+ mattr_accessor :table_name_prefix, instance_accessor: false
+ self.table_name_prefix = ""
+
+ mattr_accessor :table_name_suffix, instance_accessor: false
+ self.table_name_suffix = ""
+
+ mattr_accessor :pluralize_table_names, instance_accessor: false
+ self.pluralize_table_names = true
+ end
+
module ModelSchema
extend ActiveSupport::Concern
@@ -13,7 +25,7 @@ module ActiveRecord
# 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
# that this is a global setting for all Active Records.
- config_attribute :primary_key_prefix_type, :global => true
+ config_attribute :primary_key_prefix_type, global: true
##
# :singleton-method:
@@ -26,14 +38,12 @@ module ActiveRecord
# a namespace by defining a singleton method in the parent module called table_name_prefix which
# returns your chosen prefix.
config_attribute :table_name_prefix
- self.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.
config_attribute :table_name_suffix
- self.table_name_suffix = ""
##
# :singleton-method:
@@ -41,7 +51,6 @@ module ActiveRecord
# 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.
config_attribute :pluralize_table_names
- self.pluralize_table_names = true
end
module ClassMethods
@@ -308,10 +317,6 @@ module ActiveRecord
@relation = nil
end
- def clear_cache! # :nodoc:
- connection.schema_cache.clear!
- end
-
private
# Guesses the table name, but does not decorate it with prefix and suffix information.
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 95a2ddcc11..841681e542 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -5,6 +5,11 @@ require 'active_support/core_ext/hash/indifferent_access'
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :nested_attributes_options, instance_accessor: false
+ self.nested_attributes_options = {}
+ end
+
module NestedAttributes #:nodoc:
class TooManyRecords < ActiveRecordError
end
@@ -13,7 +18,6 @@ module ActiveRecord
included do
config_attribute :nested_attributes_options
- self.nested_attributes_options = {}
end
# = Active Record Nested Attributes
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index ec5670ba6e..a23597be28 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -167,21 +167,6 @@ module ActiveRecord
became
end
- # Updates a single attribute and saves the record.
- # This is especially useful for boolean flags on existing records. Also note that
- #
- # * Validation is skipped.
- # * Callbacks are invoked.
- # * updated_at/updated_on column is updated if that column is available.
- # * Updates all the attributes that are dirty in this object.
- #
- def update_attribute(name, value)
- name = name.to_s
- verify_readonly_attribute(name)
- send("#{name}=", value)
- save(:validate => false)
- end
-
# Updates a single attribute of an object, without calling save.
#
# * Validation is skipped.
@@ -240,7 +225,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def increment!(attribute, by = 1)
- increment(attribute, by).update_attribute(attribute, self[attribute])
+ increment(attribute, by).update_column(attribute, self[attribute])
end
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
@@ -257,7 +242,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def decrement!(attribute, by = 1)
- decrement(attribute, by).update_attribute(attribute, self[attribute])
+ decrement(attribute, by).update_column(attribute, self[attribute])
end
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
@@ -274,7 +259,7 @@ module ActiveRecord
# Saving is not subjected to validation checks. Returns +true+ if the
# record could be saved.
def toggle!(attribute)
- toggle(attribute).update_attribute(attribute, self[attribute])
+ toggle(attribute).update_column(attribute, self[attribute])
end
# Reloads the attributes of this object from the database.
@@ -282,7 +267,6 @@ module ActiveRecord
# may do e.g. record.reload(:lock => true) to reload the same record with
# an exclusive row lock.
def reload(options = nil)
- clear_aggregation_cache
clear_association_cache
fresh_object =
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index 319516413b..9432a70c41 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -68,9 +68,6 @@ module ActiveRecord
initializer "active_record.set_configs" do |app|
ActiveSupport.on_load(:active_record) do
- if app.config.active_record.delete(:whitelist_attributes)
- attr_accessible(nil)
- end
app.config.active_record.each do |k,v|
send "#{k}=", v
end
@@ -97,18 +94,12 @@ module ActiveRecord
end
initializer "active_record.set_reloader_hooks" do |app|
- hook = lambda do
- ActiveRecord::Base.clear_reloadable_connections!
- ActiveRecord::Base.clear_cache!
- end
+ hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
- if app.config.reload_classes_only_on_change
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_prepare(&hook)
- end
- else
- ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.to_cleanup(&hook)
+ ActiveSupport.on_load(:active_record) do
+ ActionDispatch::Reloader.send(hook) do
+ ActiveRecord::Model.clear_reloadable_connections!
+ ActiveRecord::Model.clear_cache!
end
end
end
@@ -119,20 +110,21 @@ module ActiveRecord
config.after_initialize do |app|
ActiveSupport.on_load(:active_record) do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
ActionDispatch::Reloader.to_prepare do
- ActiveRecord::Base.instantiate_observers
+ ActiveRecord::Model.instantiate_observers
end
end
ActiveSupport.on_load(:active_record) do
if app.config.use_schema_cache_dump
filename = File.join(app.config.paths["db"].first, "schema_cache.dump")
+
if File.file?(filename)
cache = Marshal.load File.binread filename
if cache.version == ActiveRecord::Migrator.current_version
- ActiveRecord::Base.connection.schema_cache = cache
+ ActiveRecord::Model.connection.schema_cache = cache
else
warn "schema_cache.dump is expired. Current version is #{ActiveRecord::Migrator.current_version}, but cache version is #{cache.version}."
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d8d4834d22..8199b5c2e0 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -14,138 +14,27 @@ db_namespace = namespace :db do
end
namespace :create do
- # desc 'Create all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key, such as the first entry here:
- #
- # defaults: &defaults
- # adapter: mysql
- # username: root
- # password:
- # host: localhost
- #
- # development:
- # database: blog_development
- # *defaults
- next unless config['database']
- # Only connect to local databases
- local_database?(config) { create_database(config) }
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_all
end
end
desc 'Create the database from config/database.yml for the current Rails.env (use db:create:all to create all dbs in the config)'
task :create => :load_config do
- configs_for_environment.each { |config| create_database(config) }
- ActiveRecord::Base.establish_connection(configs_for_environment.first)
- end
-
- def mysql_creation_options(config)
- @charset = ENV['CHARSET'] || 'utf8'
- @collation = ENV['COLLATION'] || 'utf8_unicode_ci'
- {:charset => (config['charset'] || @charset), :collation => (config['collation'] || @collation)}
- end
-
- def create_database(config)
- begin
- if config['adapter'] =~ /sqlite/
- if File.exist?(config['database'])
- $stderr.puts "#{config['database']} already exists"
- else
- begin
- # Create the SQLite database
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- return # Skip the else clause of begin/rescue
- else
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection
- end
- rescue
- case config['adapter']
- when /mysql/
- if config['adapter'] =~ /jdbc/
- #FIXME After Jdbcmysql gives this class
- require 'active_record/railties/jdbcmysql_error'
- error_class = ArJdbcMySQL::Error
- else
- error_class = config['adapter'] =~ /mysql2/ ? Mysql2::Error : Mysql::Error
- end
- access_denied_error = 1045
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => nil))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.establish_connection(config)
- rescue error_class => sqlerr
- if sqlerr.errno == access_denied_error
- print "#{sqlerr.error}. \nPlease provide the root password for your mysql installation\n>"
- root_password = $stdin.gets.strip
- grant_statement = "GRANT ALL PRIVILEGES ON #{config['database']}.* " \
- "TO '#{config['username']}'@'localhost' " \
- "IDENTIFIED BY '#{config['password']}' WITH GRANT OPTION;"
- ActiveRecord::Base.establish_connection(config.merge(
- 'database' => nil, 'username' => 'root', 'password' => root_password))
- ActiveRecord::Base.connection.create_database(config['database'], mysql_creation_options(config))
- ActiveRecord::Base.connection.execute grant_statement
- ActiveRecord::Base.establish_connection(config)
- else
- $stderr.puts sqlerr.error
- $stderr.puts "Couldn't create database for #{config.inspect}, charset: #{config['charset'] || @charset}, collation: #{config['collation'] || @collation}"
- $stderr.puts "(if you set the charset manually, make sure you have a matching collation)" if config['charset']
- end
- end
- when /postgresql/
- @encoding = config['encoding'] || ENV['CHARSET'] || 'utf8'
- begin
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.create_database(config['database'], config.merge('encoding' => @encoding))
- ActiveRecord::Base.establish_connection(config)
- rescue Exception => e
- $stderr.puts e, *(e.backtrace)
- $stderr.puts "Couldn't create database for #{config.inspect}"
- end
- end
- else
- $stderr.puts "#{config['database']} already exists"
- end
+ ActiveRecord::Tasks::DatabaseTasks.create_current
end
namespace :drop do
- # desc 'Drops all the local databases defined in config/database.yml'
task :all => :load_config do
- ActiveRecord::Base.configurations.each_value do |config|
- # Skip entries that don't have a database key
- next unless config['database']
- begin
- # Only connect to local databases
- local_database?(config) { drop_database(config) }
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_all
end
end
desc 'Drops the database for the current Rails.env (use db:drop:all to drop all databases)'
task :drop => :load_config do
- configs_for_environment.each { |config| drop_database_and_rescue(config) }
- end
-
- def local_database?(config, &block)
- if config['host'].in?(['127.0.0.1', 'localhost']) || config['host'].blank?
- yield
- else
- $stderr.puts "This task only modifies local databases. #{config['database']} is on a remote host."
- end
+ ActiveRecord::Tasks::DatabaseTasks.drop_current
end
-
desc "Migrate the database (options: VERSION=x, VERBOSE=false)."
task :migrate => [:environment, :load_config] do
ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
@@ -247,31 +136,18 @@ db_namespace = namespace :db do
end
# desc 'Drops and recreates the database from db/schema.rb for the current environment and loads the seeds.'
- task :reset => :environment do
+ task :reset => [:environment, :load_config] do
db_namespace["drop"].invoke
db_namespace["setup"].invoke
end
# desc "Retrieves the charset for the current environment's database"
- task :charset => :environment do
- config = ActiveRecord::Base.configurations[Rails.env || 'development']
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.charset
- when /postgresql/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.encoding
- when /sqlite/
- ActiveRecord::Base.establish_connection(config)
- puts ActiveRecord::Base.connection.encoding
- else
- $stderr.puts 'sorry, your database adapter is not supported yet, feel free to submit a patch'
- end
+ task :charset => [:environment, :load_config] do
+ puts ActiveRecord::Tasks::DatabaseTasks.charset_current
end
# desc "Retrieves the collation for the current environment's database"
- task :collation => :environment do
+ task :collation => [:environment, :load_config] do
config = ActiveRecord::Base.configurations[Rails.env || 'development']
case config['adapter']
when /mysql/
@@ -283,12 +159,12 @@ db_namespace = namespace :db do
end
desc 'Retrieves the current schema version number'
- task :version => :environment do
+ task :version => [:environment, :load_config] do
puts "Current version: #{ActiveRecord::Migrator.current_version}"
end
# desc "Raises an error if there are pending migrations"
- task :abort_if_pending_migrations => :environment do
+ task :abort_if_pending_migrations => [:environment, :load_config] do
pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations_paths).pending_migrations
if pending_migrations.any?
@@ -311,20 +187,20 @@ db_namespace = namespace :db do
namespace :fixtures do
desc "Load fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :load => :environment do
+ task :load => [:environment, :load_config] do
require 'active_record/fixtures'
ActiveRecord::Base.establish_connection(Rails.env)
base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten
fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact
- (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
+ (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file|
ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file)
end
end
# desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures."
- task :identify => :environment do
+ task :identify => [:environment, :load_config] do
require 'active_record/fixtures'
label, id = ENV['LABEL'], ENV['ID']
@@ -360,7 +236,7 @@ db_namespace = namespace :db do
end
desc 'Load a schema.rb file into the database'
- task :load => :environment do
+ task :load => [:environment, :load_config] do
file = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
if File.exists?(file)
load(file)
@@ -375,7 +251,7 @@ db_namespace = namespace :db do
namespace :cache do
desc 'Create a db/schema_cache.dump file.'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
con = ActiveRecord::Base.connection
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
@@ -385,7 +261,7 @@ db_namespace = namespace :db do
end
desc 'Clear a db/schema_cache.dump file.'
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
filename = File.join(Rails.application.config.paths["db"].first, "schema_cache.dump")
FileUtils.rm(filename) if File.exists?(filename)
end
@@ -395,25 +271,15 @@ db_namespace = namespace :db do
namespace :structure do
desc 'Dump the database structure to db/structure.sql. Specify another file with DB_STRUCTURE=db/my_structure.sql'
- task :dump => :environment do
+ task :dump => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[Rails.env]['adapter']
- when /mysql/, 'oci', 'oracle'
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.structure_dump(abcs[Rails.env], filename)
+ when 'oci', 'oracle'
ActiveRecord::Base.establish_connection(abcs[Rails.env])
File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
- when /postgresql/
- set_psql_env(abcs[Rails.env])
- search_path = abcs[Rails.env]['schema_search_path']
- unless search_path.blank?
- search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
- end
- `pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(abcs[Rails.env]['database'])}`
- raise 'Error dumping database' if $?.exitstatus == 1
- File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
- when /sqlite/
- dbfile = abcs[Rails.env]['database']
- `sqlite3 #{dbfile} .schema > #{filename}`
when 'sqlserver'
`smoscript -s #{abcs[Rails.env]['host']} -d #{abcs[Rails.env]['database']} -u #{abcs[Rails.env]['username']} -p #{abcs[Rails.env]['password']} -f #{filename} -A -U`
when "firebird"
@@ -437,18 +303,8 @@ db_namespace = namespace :db do
abcs = ActiveRecord::Base.configurations
filename = ENV['DB_STRUCTURE'] || File.join(Rails.root, "db", "structure.sql")
case abcs[env]['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(abcs[env])
- ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
- IO.read(filename).split("\n\n").each do |table|
- ActiveRecord::Base.connection.execute(table)
- end
- when /postgresql/
- set_psql_env(abcs[env])
- `psql -f "#{filename}" #{abcs[env]['database']}`
- when /sqlite/
- dbfile = abcs[env]['database']
- `sqlite3 #{dbfile} < "#{filename}"`
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.structure_load(abcs[Rails.env], filename)
when 'sqlserver'
`sqlcmd -S #{abcs[env]['host']} -d #{abcs[env]['database']} -U #{abcs[env]['username']} -P #{abcs[env]['password']} -i #{filename}`
when 'oci', 'oracle'
@@ -506,19 +362,11 @@ db_namespace = namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:load_structure" ]
# desc "Empty the test database"
- task :purge => :environment do
+ task :purge => [:environment, :load_config] do
abcs = ActiveRecord::Base.configurations
case abcs['test']['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(:test)
- ActiveRecord::Base.connection.recreate_database(abcs['test']['database'], mysql_creation_options(abcs['test']))
- when /postgresql/
- ActiveRecord::Base.clear_active_connections!
- drop_database(abcs['test'])
- create_database(abcs['test'])
- when /sqlite/
- dbfile = abcs['test']['database']
- File.delete(dbfile) if File.exist?(dbfile)
+ when /mysql/, /postgresql/, /sqlite/
+ ActiveRecord::Tasks::DatabaseTasks.purge abcs['test']
when 'sqlserver'
test = abcs.deep_dup['test']
test_database = test['database']
@@ -548,7 +396,7 @@ db_namespace = namespace :db do
namespace :sessions do
# desc "Creates a sessions migration for use with ActiveRecord::SessionStore"
- task :create => :environment do
+ task :create => [:environment, :load_config] do
raise 'Task unavailable to this database (no migration support)' unless ActiveRecord::Base.connection.supports_migrations?
Rails.application.load_generators
require 'rails/generators/rails/session_migration/session_migration_generator'
@@ -556,7 +404,7 @@ db_namespace = namespace :db do
end
# desc "Clear the sessions table"
- task :clear => :environment do
+ task :clear => [:environment, :load_config] do
ActiveRecord::Base.connection.execute "DELETE FROM #{session_table_name}"
end
end
@@ -592,37 +440,6 @@ end
task 'test:prepare' => 'db:test:prepare'
-def drop_database(config)
- case config['adapter']
- when /mysql/
- ActiveRecord::Base.establish_connection(config)
- ActiveRecord::Base.connection.drop_database config['database']
- when /sqlite/
- require 'pathname'
- path = Pathname.new(config['database'])
- file = path.absolute? ? path.to_s : File.join(Rails.root, path)
-
- FileUtils.rm(file)
- when /postgresql/
- ActiveRecord::Base.establish_connection(config.merge('database' => 'postgres', 'schema_search_path' => 'public'))
- ActiveRecord::Base.connection.drop_database config['database']
- end
-end
-
-def drop_database_and_rescue(config)
- begin
- drop_database(config)
- rescue Exception => e
- $stderr.puts "Couldn't drop #{config['database']} : #{e.inspect}"
- end
-end
-
-def configs_for_environment
- environments = [Rails.env]
- environments << 'test' if Rails.env.development?
- ActiveRecord::Base.configurations.values_at(*environments).compact.reject { |config| config['database'].blank? }
-end
-
def session_table_name
ActiveRecord::SessionStore::Session.table_name
end
@@ -635,10 +452,3 @@ end
def firebird_db_string(config)
FireRuby::Database.db_string_for(config.symbolize_keys)
end
-
-def set_psql_env(config)
- ENV['PGHOST'] = config['host'] if config['host']
- ENV['PGPORT'] = config['port'].to_s if config['port']
- ENV['PGPASSWORD'] = config['password'].to_s if config['password']
- ENV['PGUSER'] = config['username'].to_s if config['username']
-end
diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb
index 836b15e2ce..960b78dc38 100644
--- a/activerecord/lib/active_record/readonly_attributes.rb
+++ b/activerecord/lib/active_record/readonly_attributes.rb
@@ -6,7 +6,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :_attr_readonly
+ class_attribute :_attr_readonly, instance_writer: false
self._attr_readonly = []
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index c380b5c029..0d9534acd6 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -7,8 +7,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
- config_attribute :reflections
+ class_attribute :reflections
self.reflections = {}
end
@@ -18,36 +17,17 @@ module ActiveRecord
# and creates input fields for all of the attributes depending on their type
# and displays the associations to other objects.
#
- # MacroReflection class has info for AggregateReflection and AssociationReflection
- # classes.
+ # MacroReflection class has info for the AssociationReflection
+ # class.
module ClassMethods
def create_reflection(macro, name, options, active_record)
- case macro
- when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
- klass = options[:through] ? ThroughReflection : AssociationReflection
- reflection = klass.new(macro, name, options, active_record)
- when :composed_of
- reflection = AggregateReflection.new(macro, name, options, active_record)
- end
+ klass = options[:through] ? ThroughReflection : AssociationReflection
+ reflection = klass.new(macro, name, options, active_record)
self.reflections = self.reflections.merge(name => reflection)
reflection
end
- # Returns an array of AggregateReflection objects for all the aggregations in the class.
- def reflect_on_all_aggregations
- reflections.values.grep(AggregateReflection)
- end
-
- # Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
- #
- # Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
- #
- def reflect_on_aggregation(aggregation)
- reflection = reflections[aggregation]
- reflection if reflection.is_a?(AggregateReflection)
- end
-
# Returns an array of AssociationReflection objects for all the
# associations in the class. If you only want to reflect on a certain
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
@@ -79,24 +59,20 @@ module ActiveRecord
end
end
- # Abstract base class for AggregateReflection and AssociationReflection. Objects of
- # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
+ # Abstract base class for AssociationReflection. Objects of AssociationReflection are returned by the Reflection::ClassMethods.
class MacroReflection
# Returns the name of the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
attr_reader :name
# Returns the macro type.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
attr_reader :macro
# Returns the hash of options used for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
# <tt>has_many :clients</tt> returns +{}+
attr_reader :options
@@ -115,7 +91,6 @@ module ActiveRecord
# Returns the class for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
# <tt>has_many :clients</tt> returns the Client class
def klass
@klass ||= class_name.constantize
@@ -123,7 +98,6 @@ module ActiveRecord
# Returns the class name for the macro.
#
- # <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
def class_name
@class_name ||= (options[:class_name] || derive_class_name).to_s
@@ -149,16 +123,6 @@ module ActiveRecord
end
end
-
- # Holds all the meta-data about an aggregation as it was specified in the
- # Active Record class.
- class AggregateReflection < MacroReflection #:nodoc:
- def mapping
- mapping = options[:mapping] || [name, name]
- mapping.first.is_a?(Array) ? mapping : [mapping]
- end
- end
-
# Holds all the meta-data about an association as it was specified in the
# Active Record class.
class AssociationReflection < MacroReflection #:nodoc:
@@ -197,6 +161,10 @@ module ActiveRecord
@quoted_table_name ||= klass.quoted_table_name
end
+ def join_table
+ @join_table ||= options[:join_table] || derive_join_table
+ end
+
def foreign_key
@foreign_key ||= options[:foreign_key] || derive_foreign_key
end
@@ -244,6 +212,10 @@ module ActiveRecord
def check_validity!
check_validity_of_inverse!
+
+ if has_and_belongs_to_many? && association_foreign_key == foreign_key
+ raise HasAndBelongsToManyAssociationForeignKeyNeeded.new(self)
+ end
end
def check_validity_of_inverse!
@@ -326,6 +298,10 @@ module ActiveRecord
macro == :belongs_to
end
+ def has_and_belongs_to_many?
+ macro == :has_and_belongs_to_many
+ end
+
def association_class
case macro
when :belongs_to
@@ -368,6 +344,10 @@ module ActiveRecord
end
end
+ def derive_join_table
+ [active_record.table_name, klass.table_name].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').gsub("\0", "_")
+ end
+
def primary_key(klass)
klass.primary_key || raise(UnknownPrimaryKey.new(klass))
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 05ced3299b..fe3aa00a74 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -300,7 +300,7 @@ module ActiveRecord
# Person.update(people.keys, people.values)
def update(id, attributes)
if id.is_a?(Array)
- id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
+ id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
else
object = find(id)
object.update_attributes(attributes)
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 54c93332bb..86eb8f35b5 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -107,7 +107,7 @@ module ActiveRecord
relation = with_default_scope
if relation.equal?(self)
- if eager_loading? || (includes_values.present? && references_eager_loaded_tables?)
+ if has_include?(column_name)
construct_relation_for_association_calculations.calculate(operation, column_name, options)
else
perform_calculation(operation, column_name, options)
@@ -138,6 +138,10 @@ module ActiveRecord
# # SELECT people.id FROM people
# # => [1, 2, 3]
#
+ # Person.pluck(:id, :name)
+ # # SELECT people.id, people.name FROM people
+ # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
+ #
# Person.uniq.pluck(:role)
# # SELECT DISTINCT role FROM people
# # => ['admin', 'member', 'guest']
@@ -150,26 +154,35 @@ module ActiveRecord
# # SELECT DATEDIFF(updated_at, created_at) FROM people
# # => ['0', '27761', '173']
#
- def pluck(column_name)
- if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s)
- column_name = "#{table_name}.#{column_name}"
+ def pluck(*column_names)
+ column_names.map! do |column_name|
+ if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
+ "#{table_name}.#{column_name}"
+ else
+ column_name
+ end
end
- result = klass.connection.select_all(select(column_name).arel, nil, bind_values)
-
- key = result.columns.first
- column = klass.column_types.fetch(key) {
- result.column_types.fetch(key) {
- Class.new { def type_cast(v); v; end }.new
- }
- }
-
- result.map do |attributes|
- raise ArgumentError, "Pluck expects to select just one attribute: #{attributes.inspect}" unless attributes.one?
+ if has_include?(column_names.first)
+ construct_relation_for_association_calculations.pluck(*column_names)
+ else
+ result = klass.connection.select_all(select(column_names).arel, nil, bind_values)
+ columns = result.columns.map do |key|
+ klass.column_types.fetch(key) {
+ result.column_types.fetch(key) {
+ Class.new { def type_cast(v); v; end }.new
+ }
+ }
+ end
- value = klass.initialize_attributes(attributes).values.first
+ result = result.map do |attributes|
+ values = klass.initialize_attributes(attributes).values
- column.type_cast(value)
+ columns.zip(values).map do |column, value|
+ column.type_cast(value)
+ end
+ end
+ columns.one? ? result.map!(&:first) : result
end
end
@@ -185,6 +198,10 @@ module ActiveRecord
private
+ def has_include?(column_name)
+ eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
+ end
+
def perform_calculation(operation, column_name, options = {})
operation = operation.to_s.downcase
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 6fa64b39c3..5e5aca0396 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -7,34 +7,36 @@ module ActiveRecord
Relation::MULTI_VALUE_METHODS.each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_values # def select_values
- @values[:#{name}] || [] # @values[:select] || []
- end # end
- #
- def #{name}_values=(values) # def select_values=(values)
- @values[:#{name}] = values # @values[:select] = values
- end # end
+ def #{name}_values # def select_values
+ @values[:#{name}] || [] # @values[:select] || []
+ end # end
+ #
+ def #{name}_values=(values) # def select_values=(values)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = values # @values[:select] = values
+ end # end
CODE
end
(Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |name|
class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}_value # def readonly_value
- @values[:#{name}] # @values[:readonly]
- end # end
- #
- def #{name}_value=(value) # def readonly_value=(value)
- @values[:#{name}] = value # @values[:readonly] = value
- end # end
+ def #{name}_value # def readonly_value
+ @values[:#{name}] # @values[:readonly]
+ end # end
CODE
end
- def create_with_value
- @values[:create_with] || {}
+ Relation::SINGLE_VALUE_METHODS.each do |name|
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}_value=(value) # def readonly_value=(value)
+ raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded
+ @values[:#{name}] = value # @values[:readonly] = value
+ end # end
+ CODE
end
- def create_with_value=(value)
- @values[:create_with] = value
+ def create_with_value
+ @values[:create_with] || {}
end
alias extensions extending_values
@@ -191,10 +193,102 @@ module ActiveRecord
self
end
+ # Returns a new relation, which is the result of filtering the current relation
+ # according to the conditions in the arguments.
+ #
+ # #where accepts conditions in one of several formats. In the examples below, the resulting
+ # SQL is given as an illustration; the actual query generated may be different depending
+ # on the database adapter.
+ #
+ # === string
+ #
+ # A single string, without additional arguments, is passed to the query
+ # constructor as a SQL fragment, and used in the where clause of the query.
+ #
+ # Client.where("orders_count = '2'")
+ # # SELECT * from clients where orders_count = '2';
+ #
+ # Note that building your own string from user input may expose your application
+ # to injection attacks if not done properly. As an alternative, it is recommended
+ # to use one of the following methods.
+ #
+ # === array
+ #
+ # If an array is passed, then the first element of the array is treated as a template, and
+ # the remaining elements are inserted into the template to generate the condition.
+ # Active Record takes care of building the query to avoid injection attacks, and will
+ # convert from the ruby type to the database type where needed. Elements are inserted
+ # into the string in the order in which they appear.
+ #
+ # User.where(["name = ? and email = ?", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # Alternatively, you can use named placeholders in the template, and pass a hash as the
+ # second element of the array. The names in the template are replaced with the corresponding
+ # values from the hash.
+ #
+ # User.where(["name = :name and email = :email", { name: "Joe", email: "joe@example.com" }])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # This can make for more readable code in complex queries.
+ #
+ # Lastly, you can use sprintf-style % escapes in the template. This works slightly differently
+ # than the previous methods; you are responsible for ensuring that the values in the template
+ # are properly quoted. The values are passed to the connector for quoting, but the caller
+ # is responsible for ensuring they are enclosed in quotes in the resulting SQL. After quoting,
+ # the values are inserted using the same escapes as the Ruby core method <tt>Kernel::sprintf</tt>.
+ #
+ # User.where(["name = '%s' and email = '%s'", "Joe", "joe@example.com"])
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # If #where is called with multiple arguments, these are treated as if they were passed as
+ # the elements of a single array.
+ #
+ # User.where("name = :name and email = :email", { name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com';
+ #
+ # When using strings to specify conditions, you can use any operator available from
+ # the database. While this provides the most flexibility, you can also unintentionally introduce
+ # dependencies on the underlying database. If your code is intended for general consumption,
+ # test with multiple database backends.
+ #
+ # === hash
+ #
+ # #where will also accept a hash condition, in which the keys are fields and the values
+ # are values to be searched for.
+ #
+ # Fields can be symbols or strings. Values can be single values, arrays, or ranges.
+ #
+ # User.where({ name: "Joe", email: "joe@example.com" })
+ # # SELECT * FROM users WHERE name = 'Joe' AND email = 'joe@example.com'
+ #
+ # User.where({ name: ["Alice", "Bob"]})
+ # # SELECT * FROM users WHERE name IN ('Alice', 'Bob')
+ #
+ # User.where({ created_at: (Time.now.midnight - 1.day)..Time.now.midnight })
+ # # SELECT * FROM users WHERE (created_at BETWEEN '2012-06-09 07:00:00.000000' AND '2012-06-10 07:00:00.000000')
+ #
+ # === Joins
+ #
+ # If the relation is the result of a join, you may create a condition which uses any of the
+ # tables in the join. For string and array conditions, use the table name in the condition.
+ #
+ # User.joins(:posts).where("posts.created_at < ?", Time.now)
+ #
+ # For hash conditions, you can either use the table name in the key, or use a sub-hash.
+ #
+ # User.joins(:posts).where({ "posts.published" => true })
+ # User.joins(:posts).where({ :posts => { :published => true } })
+ #
+ # === empty condition
+ #
+ # If the condition returns true for blank?, then where is a no-op and returns the current relation.
def where(opts, *rest)
opts.blank? ? self : spawn.where!(opts, *rest)
end
+ # #where! is identical to #where, except that instead of returning a new relation, it adds
+ # the condition to the existing relation.
def where!(opts, *rest)
references!(PredicateBuilder.references(opts)) if Hash === opts
@@ -482,8 +576,7 @@ module ActiveRecord
when String, Array
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
when Hash
- attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
- PredicateBuilder.build_from_hash(table.engine, attributes, table)
+ PredicateBuilder.build_from_hash(table.engine, opts, table)
else
[opts]
end
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 5530be3219..46f6c283e3 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -43,36 +43,6 @@ module ActiveRecord
end
end
- # Accepts a hash of SQL conditions and replaces those attributes
- # that correspond to a +composed_of+ relationship with their expanded
- # aggregate attribute values.
- # Given:
- # class Person < ActiveRecord::Base
- # composed_of :address, :class_name => "Address",
- # :mapping => [%w(address_street street), %w(address_city city)]
- # end
- # Then:
- # { :address => Address.new("813 abc st.", "chicago") }
- # # => { :address_street => "813 abc st.", :address_city => "chicago" }
- def expand_hash_conditions_for_aggregates(attrs)
- expanded_attrs = {}
- attrs.each do |attr, value|
- if aggregation = reflect_on_aggregation(attr.to_sym)
- mapping = aggregation.mapping
- mapping.each do |field_attr, aggregate_attr|
- if mapping.size == 1 && !value.respond_to?(aggregate_attr)
- expanded_attrs[field_attr] = value
- else
- expanded_attrs[field_attr] = value.send(aggregate_attr)
- end
- end
- else
- expanded_attrs[attr] = value
- end
- end
- expanded_attrs
- end
-
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
# { :name => "foo'bar", :group_id => 4 }
# # => "name='foo''bar' and group_id= 4"
@@ -88,8 +58,6 @@ module ActiveRecord
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
- attrs = expand_hash_conditions_for_aggregates(attrs)
-
table = Arel::Table.new(table_name).alias(default_table_name)
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
connection.visitor.accept b
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 1cdaa516ba..a25a8d79bd 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -70,8 +70,8 @@ HEADER
@connection.tables.sort.each do |tbl|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
case ignored
- when String; tbl == ignored
- when Regexp; tbl =~ ignored
+ when String; remove_prefix_and_suffix(tbl) == ignored
+ when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
else
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
end
@@ -92,7 +92,7 @@ HEADER
pk = @connection.primary_key(table)
end
- tbl.print " create_table #{table.inspect}"
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
if columns.detect { |c| c.name == pk }
if pk != 'id'
tbl.print %Q(, :primary_key => "#{pk}")
@@ -185,7 +185,7 @@ HEADER
if (indexes = @connection.indexes(table)).any?
add_index_statements = indexes.map do |index|
statement_parts = [
- ('add_index ' + index.table.inspect),
+ ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
index.columns.inspect,
(':name => ' + index.name.inspect),
]
@@ -206,5 +206,9 @@ HEADER
stream.puts
end
end
+
+ def remove_prefix_and_suffix(table)
+ table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
+ end
end
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index db833fc7f1..af51c803a7 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -8,7 +8,7 @@ module ActiveRecord
included do
# Stores the default scope for the class
- config_attribute :default_scopes
+ class_attribute :default_scopes, instance_writer: false
self.default_scopes = []
end
diff --git a/activerecord/lib/active_record/serialization.rb b/activerecord/lib/active_record/serialization.rb
index 41e3b92499..e8dd312a47 100644
--- a/activerecord/lib/active_record/serialization.rb
+++ b/activerecord/lib/active_record/serialization.rb
@@ -1,9 +1,21 @@
module ActiveRecord #:nodoc:
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :include_root_in_json, instance_accessor: false
+ self.include_root_in_json = true
+ end
+
# = Active Record Serialization
module Serialization
extend ActiveSupport::Concern
include ActiveModel::Serializers::JSON
+ included do
+ singleton_class.class_eval do
+ remove_method :include_root_in_json
+ delegate :include_root_in_json, to: 'ActiveRecord::Model'
+ end
+ end
+
def serializable_hash(options = nil)
options = options.try(:clone) || {}
diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb
index b833af64fe..834d01a1e8 100644
--- a/activerecord/lib/active_record/serializers/xml_serializer.rb
+++ b/activerecord/lib/active_record/serializers/xml_serializer.rb
@@ -177,11 +177,6 @@ module ActiveRecord #:nodoc:
end
class XmlSerializer < ActiveModel::Serializers::Xml::Serializer #:nodoc:
- def initialize(*args)
- super
- options[:except] = Array(options[:except]) | Array(@serializable.class.inheritance_column)
- end
-
class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
def compute_type
klass = @serializable.class
diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb
index 542cb3187a..d13491502e 100644
--- a/activerecord/lib/active_record/store.rb
+++ b/activerecord/lib/active_record/store.rb
@@ -1,4 +1,6 @@
+require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/class/attribute'
module ActiveRecord
# Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
@@ -33,9 +35,18 @@ module ActiveRecord
# class SuperUser < User
# store_accessor :settings, :privileges, :servants
# end
+ #
+ # The stored attribute names can be retrieved using +stored_attributes+.
+ #
+ # User.stored_attributes[:settings] # [:color, :homepage]
module Store
extend ActiveSupport::Concern
+ included do
+ class_attribute :stored_attributes
+ self.stored_attributes = {}
+ end
+
module ClassMethods
def store(store_attribute, options = {})
serialize store_attribute, IndifferentCoder.new(options[:coder])
@@ -43,7 +54,8 @@ module ActiveRecord
end
def store_accessor(store_attribute, *keys)
- keys.flatten.each do |key|
+ keys = keys.flatten
+ keys.each do |key|
define_method("#{key}=") do |value|
initialize_store_attribute(store_attribute)
send(store_attribute)[key] = value
@@ -55,6 +67,8 @@ module ActiveRecord
send(store_attribute)[key]
end
end
+
+ self.stored_attributes[store_attribute] = keys
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
new file mode 100644
index 0000000000..999b2ebc85
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -0,0 +1,109 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ module DatabaseTasks # :nodoc:
+ extend self
+
+ TASKS_PATTERNS = {
+ /mysql/ => ActiveRecord::Tasks::MySQLDatabaseTasks,
+ /postgresql/ => ActiveRecord::Tasks::PostgreSQLDatabaseTasks,
+ /sqlite/ => ActiveRecord::Tasks::SQLiteDatabaseTasks
+ }
+ LOCAL_HOSTS = ['127.0.0.1', 'localhost']
+
+ def create(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).create
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't create database for #{configuration.inspect}"
+ end
+
+ def create_all
+ each_local_configuration { |configuration| create configuration }
+ end
+
+ def create_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ create configuration
+ }
+ ActiveRecord::Base.establish_connection environment
+ end
+
+ def drop(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).drop
+ rescue Exception => error
+ $stderr.puts error, *(error.backtrace)
+ $stderr.puts "Couldn't drop #{configuration['database']}"
+ end
+
+ def drop_all
+ each_local_configuration { |configuration| drop configuration }
+ end
+
+ def drop_current(environment = Rails.env)
+ each_current_configuration(environment) { |configuration|
+ drop configuration
+ }
+ end
+
+ def charset_current(environment = Rails.env)
+ charset ActiveRecord::Base.configurations[environment]
+ end
+
+ def charset(*arguments)
+ configuration = arguments.first
+ class_for_adapter(configuration['adapter']).new(*arguments).charset
+ end
+
+ def purge(configuration)
+ class_for_adapter(configuration['adapter']).new(configuration).purge
+ end
+
+ def structure_dump(*arguments)
+ configuration = arguments.first
+ filename = arguments.delete_at 1
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_dump(filename)
+ end
+
+ def structure_load(*arguments)
+ configuration = arguments.first
+ filename = arguments.delete_at 1
+ class_for_adapter(configuration['adapter']).new(*arguments).structure_load(filename)
+ end
+
+ private
+
+ def class_for_adapter(adapter)
+ key = TASKS_PATTERNS.keys.detect { |pattern| adapter[pattern] }
+ TASKS_PATTERNS[key]
+ end
+
+ def each_current_configuration(environment)
+ environments = [environment]
+ environments << 'test' if environment.development?
+
+ configurations = ActiveRecord::Base.configurations.values_at(*environments)
+ configurations.compact.each do |configuration|
+ yield configuration unless configuration['database'].blank?
+ end
+ end
+
+ def each_local_configuration
+ ActiveRecord::Base.configurations.each_value do |configuration|
+ next unless configuration['database']
+
+ if local_database?(configuration)
+ yield configuration
+ else
+ $stderr.puts "This task only modifies local databases. #{configuration['database']} is on a remote host."
+ end
+ end
+ end
+
+ def local_database?(configuration)
+ configuration['host'].in?(LOCAL_HOSTS) || configuration['host'].blank?
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
new file mode 100644
index 0000000000..b39cd2282f
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -0,0 +1,110 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class MySQLDatabaseTasks # :nodoc:
+
+ DEFAULT_CHARSET = ENV['CHARSET'] || 'utf8'
+ DEFAULT_COLLATION = ENV['COLLATION'] || 'utf8_unicode_ci'
+ ACCESS_DENIED_ERROR = 1045
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create
+ establish_connection configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ establish_connection configuration
+ rescue error_class => error
+ raise error unless error.errno == ACCESS_DENIED_ERROR
+
+ $stdout.print error.error
+ establish_connection root_configuration_without_database
+ connection.create_database configuration['database'], creation_options
+ connection.execute grant_statement.gsub(/\s+/, ' ').strip
+ establish_connection configuration
+ rescue error_class => error
+ $stderr.puts error.error
+ $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}"
+ $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration['charset']
+ end
+
+ def drop
+ establish_connection configuration
+ connection.drop_database configuration['database']
+ end
+
+ def purge
+ establish_connection :test
+ connection.recreate_database configuration['database'], creation_options
+ end
+
+ def charset
+ connection.charset
+ end
+
+ def structure_dump(filename)
+ establish_connection configuration
+ File.open(filename, "w:utf-8") { |f| f << ActiveRecord::Base.connection.structure_dump }
+ end
+
+ def structure_load(filename)
+ establish_connection(configuration)
+ connection.execute('SET foreign_key_checks = 0')
+ IO.read(filename).split("\n\n").each do |table|
+ connection.execute(table)
+ end
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def configuration_without_database
+ configuration.merge('database' => nil)
+ end
+
+ def creation_options
+ {
+ charset: (configuration['charset'] || DEFAULT_CHARSET),
+ collation: (configuration['collation'] || DEFAULT_COLLATION)
+ }
+ end
+
+ def error_class
+ case configuration['adapter']
+ when /jdbc/
+ require 'active_record/railties/jdbcmysql_error'
+ ArJdbcMySQL::Error
+ when /mysql2/
+ Mysql2::Error
+ else
+ Mysql::Error
+ end
+ end
+
+ def grant_statement
+ <<-SQL
+GRANT ALL PRIVILEGES ON #{configuration['database']}.*
+ TO '#{configuration['username']}'@'localhost'
+IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
+ SQL
+ end
+
+ def root_configuration_without_database
+ configuration_without_database.merge(
+ 'username' => 'root',
+ 'password' => root_password
+ )
+ end
+
+ def root_password
+ $stdout.print "Please provide the root password for your mysql installation\n>"
+ $stdin.gets.strip
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
new file mode 100644
index 0000000000..a210392e53
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb
@@ -0,0 +1,81 @@
+require 'shellwords'
+
+module ActiveRecord
+ module Tasks # :nodoc:
+ class PostgreSQLDatabaseTasks # :nodoc:
+
+ DEFAULT_ENCODING = ENV['CHARSET'] || 'utf8'
+
+ delegate :connection, :establish_connection, :clear_active_connections!,
+ to: ActiveRecord::Base
+
+ def initialize(configuration)
+ @configuration = configuration
+ end
+
+ def create(master_established = false)
+ establish_master_connection unless master_established
+ connection.create_database configuration['database'],
+ configuration.merge('encoding' => encoding)
+ establish_connection configuration
+ end
+
+ def drop
+ establish_master_connection
+ connection.drop_database configuration['database']
+ end
+
+ def charset
+ connection.encoding
+ end
+
+ def purge
+ clear_active_connections!
+ drop
+ create true
+ end
+
+ def structure_dump(filename)
+ set_psql_env
+ search_path = configuration['schema_search_path']
+ unless search_path.blank?
+ search_path = search_path.split(",").map{|search_path_part| "--schema=#{Shellwords.escape(search_path_part.strip)}" }.join(" ")
+ end
+
+ command = "pg_dump -i -s -x -O -f #{Shellwords.escape(filename)} #{search_path} #{Shellwords.escape(configuration['database'])}"
+ raise 'Error dumping database' unless Kernel.system(command)
+
+ File.open(filename, "a") { |f| f << "SET search_path TO #{ActiveRecord::Base.connection.schema_search_path};\n\n" }
+ end
+
+ def structure_load(filename)
+ set_psql_env
+ Kernel.system("psql -f #{filename} #{configuration['database']}")
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def encoding
+ configuration['encoding'] || DEFAULT_ENCODING
+ end
+
+ def establish_master_connection
+ establish_connection configuration.merge(
+ 'database' => 'postgres',
+ 'schema_search_path' => 'public'
+ )
+ end
+
+ def set_psql_env
+ ENV['PGHOST'] = configuration['host'] if configuration['host']
+ ENV['PGPORT'] = configuration['port'].to_s if configuration['port']
+ ENV['PGPASSWORD'] = configuration['password'].to_s if configuration['password']
+ ENV['PGUSER'] = configuration['username'].to_s if configuration['username']
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
new file mode 100644
index 0000000000..da01058a82
--- /dev/null
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -0,0 +1,55 @@
+module ActiveRecord
+ module Tasks # :nodoc:
+ class SQLiteDatabaseTasks # :nodoc:
+
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
+
+ def initialize(configuration, root = Rails.root)
+ @configuration, @root = configuration, root
+ end
+
+ def create
+ if File.exist?(configuration['database'])
+ $stderr.puts "#{configuration['database']} already exists"
+ return
+ end
+
+ establish_connection configuration
+ connection
+ end
+
+ def drop
+ require 'pathname'
+ path = Pathname.new configuration['database']
+ file = path.absolute? ? path.to_s : File.join(root, path)
+
+ FileUtils.rm(file) if File.exist?(file)
+ end
+ alias :purge :drop
+
+ def charset
+ connection.encoding
+ end
+
+ def structure_dump(filename)
+ dbfile = configuration['database']
+ `sqlite3 #{dbfile} .schema > #{filename}`
+ end
+
+ def structure_load(filename)
+ dbfile = configuration['database']
+ `sqlite3 #{dbfile} < "#{filename}"`
+ end
+
+ private
+
+ def configuration
+ @configuration
+ end
+
+ def root
+ @root
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index c717fdea47..e5b7a6bfba 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -1,6 +1,11 @@
require 'active_support/core_ext/class/attribute'
module ActiveRecord
+ ActiveSupport.on_load(:active_record_config) do
+ mattr_accessor :record_timestamps, instance_accessor: false
+ self.record_timestamps = true
+ end
+
# = Active Record Timestamp
#
# Active Record automatically timestamps create and update operations if the
@@ -33,8 +38,7 @@ module ActiveRecord
extend ActiveSupport::Concern
included do
- config_attribute :record_timestamps, :instance_writer => true
- self.record_timestamps = true
+ config_attribute :record_timestamps, instance_writer: true
end
def initialize_dup(other)