aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--README.rdoc6
-rwxr-xr-xRakefile5
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/actionpack.gemspec8
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb16
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb17
-rw-r--r--actionpack/lib/action_dispatch/routing/url_for.rb12
-rw-r--r--actionpack/lib/sprockets/compressors.rb30
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb12
-rw-r--r--actionpack/test/abstract_unit.rb6
-rw-r--r--activerecord/CHANGELOG2
-rw-r--r--activerecord/lib/active_record.rb7
-rw-r--r--activerecord/lib/active_record/associations/alias_tracker.rb18
-rw-r--r--activerecord/lib/active_record/base.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb611
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb592
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb688
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/query_cache.rb6
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb9
-rw-r--r--activerecord/lib/active_record/timestamp.rb13
-rw-r--r--activerecord/test/cases/adapters/mysql/active_schema_test.rb8
-rw-r--r--activerecord/test/cases/adapters/postgresql/schema_test.rb15
-rw-r--r--activerecord/test/cases/adapters/postgresql/utils_test.rb18
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb8
-rw-r--r--activerecord/test/cases/column_definition_test.rb32
-rw-r--r--activerecord/test/cases/query_cache_test.rb28
-rw-r--r--activerecord/test/cases/relations_test.rb2
-rw-r--r--activerecord/test/models/toy.rb2
-rw-r--r--activesupport/CHANGELOG12
-rw-r--r--railties/CHANGELOG13
-rw-r--r--railties/README.rdoc8
-rw-r--r--railties/guides/source/3_1_release_notes.textile4
-rw-r--r--railties/guides/source/asset_pipeline.textile48
-rw-r--r--railties/guides/source/index.html.erb8
-rw-r--r--railties/guides/source/layout.html.erb4
39 files changed, 1006 insertions, 1286 deletions
diff --git a/Gemfile b/Gemfile
index e11514d5e6..f0880926a3 100644
--- a/Gemfile
+++ b/Gemfile
@@ -21,7 +21,7 @@ gem "mocha", ">= 0.9.8"
group :doc do
gem "rdoc", "~> 3.4"
- gem "horo", "= 1.0.3"
+ gem "sdoc", "~> 0.3"
gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3"
gem "w3c_validators"
end
diff --git a/README.rdoc b/README.rdoc
index f0aa962272..9f017cd05f 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -8,12 +8,12 @@ into three layers, each with a specific responsibility.
The View layer is composed of "templates" that are responsible for providing
appropriate representations of your application's resources. Templates
-can come in a variety of formats, but most view templates are HTML with embedded Ruby
+can come in a variety of formats, but most view templates are \HTML with embedded Ruby
code (.erb files).
The Model layer represents your domain model (such as Account, Product, Person, Post)
and encapsulates the business logic that is specific to your application. In Rails,
-database-backed model classes are derived from ActiveRecord::Base. ActiveRecord allows
+database-backed model classes are derived from ActiveRecord::Base. Active Record allows
you to present the data from database rows as objects and embellish these data objects
with business logic methods. Although most Rails models are backed by a database, models
can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as
@@ -21,7 +21,7 @@ provided by the ActiveModel module. You can read more about Active Record in its
{README}[link:/rails/rails/blob/master/activerecord/README.rdoc].
The Controller layer is responsible for handling incoming HTTP requests and providing a
-suitable response. Usually this means returning HTML, but Rails controllers can also
+suitable response. Usually this means returning \HTML, but Rails controllers can also
generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models
and render view templates in order to generate the appropriate HTTP response.
diff --git a/Rakefile b/Rakefile
index 4cf01cf063..f171949961 100755
--- a/Rakefile
+++ b/Rakefile
@@ -1,6 +1,7 @@
#!/usr/bin/env rake
require 'rdoc/task'
+require 'sdoc'
require 'net/http'
$:.unshift File.expand_path('..', __FILE__)
@@ -89,8 +90,10 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
- rdoc.options << '-f' << 'horo'
+ rdoc.options << '-f' << 'sdoc'
+ rdoc.options << '-T' << 'rails'
rdoc.options << '-c' << 'utf-8'
+ rdoc.options << '-g' # SDoc flag, link methods to GitHub
rdoc.options << '-m' << RDOC_MAIN
rdoc.rdoc_files.include('railties/CHANGELOG')
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index b1286d04cc..6b654e149e 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -30,6 +30,8 @@
*Rails 3.1.0 (unreleased)*
+* Param values are `paramified` in controller tests. [David Chelimsky]
+
* x_sendfile_header now defaults to nil and config/environments/production.rb doesn't set a particular value for it. This allows servers to set it through X-Sendfile-Type. [Santiago Pastorino]
* The submit form helper does not generate an id "object_name_id" anymore. [fbrusatti]
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 02758148c0..65b364f872 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -18,13 +18,13 @@ Gem::Specification.new do |s|
s.add_dependency('activesupport', version)
s.add_dependency('activemodel', version)
- s.add_dependency('rack-cache', '~> 1.0.2')
+ s.add_dependency('rack-cache', '~> 1.0.3')
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.6')
s.add_dependency('rack', '~> 1.3.2')
- s.add_dependency('rack-test', '~> 0.6.0')
- s.add_dependency('rack-mount', '~> 0.8.1')
- s.add_dependency('sprockets', '~> 2.0.0.beta.14')
+ s.add_dependency('rack-test', '~> 0.6.1')
+ s.add_dependency('rack-mount', '~> 0.8.2')
+ s.add_dependency('sprockets', '~> 2.0.0')
s.add_dependency('erubis', '~> 2.7.0')
s.add_development_dependency('tzinfo', '~> 0.3.29')
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index f24203db3a..6acbb23907 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -6,30 +6,30 @@ require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_types'
module ActionController
- # Wraps parameters hash into nested hash. This will allow client to submit
- # POST request without having to specify a root element in it.
+ # Wraps the parameters hash into a nested hash. This will allow clients to submit
+ # POST requests without having to specify any root elements.
#
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
- # and can be customized. If you are upgrading to Rails 3.1, this file will
+ # and can be customized. If you are upgrading to \Rails 3.1, this file will
# need to be created for the functionality to be enabled.
#
# You could also turn it on per controller by setting the format array to
- # non-empty array:
+ # a non-empty array:
#
# class UsersController < ApplicationController
# wrap_parameters :format => [:json, :xml]
# end
#
- # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to
+ # If you enable +ParamsWrapper+ for +:json+ format, instead of having to
# send JSON parameters like this:
#
# {"user": {"name": "Konata"}}
#
- # You can now just send a parameters like this:
+ # You can send parameters like this:
#
# {"name": "Konata"}
#
- # And it will be wrapped into a nested hash with the key name matching
+ # And it will be wrapped into a nested hash with the key name matching the
# controller's name. For example, if you're posting to +UsersController+,
# your new +params+ hash will look like this:
#
@@ -81,7 +81,7 @@ module ActionController
#
# ==== Examples
# wrap_parameters :format => :xml
- # # enables the parmeter wrapper for XML format
+ # # enables the parameter wrapper for XML format
#
# wrap_parameters :person
# # wraps parameters into +params[:person]+ hash
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 08132b1900..0b40b1fc4c 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -18,7 +18,7 @@
# @url = root_path # named route from the application.
# end
# end
-# =>
+#
module ActionController
module UrlFor
extend ActiveSupport::Concern
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index a15ad28f16..94fa747a79 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -11,24 +11,13 @@ module ActionDispatch
raise(ArgumentError, ':tempfile is required') unless @tempfile
end
- def open
- @tempfile.open
- end
-
- def path
- @tempfile.path
- end
-
def read(*args)
@tempfile.read(*args)
end
- def rewind
- @tempfile.rewind
- end
-
- def size
- @tempfile.size
+ # Delegate these methods to the tempfile.
+ [:open, :path, :rewind, :size].each do |method|
+ class_eval "def #{method}; @tempfile.#{method}; end"
end
private
diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb
index de14113c51..30048cd48a 100644
--- a/actionpack/lib/action_dispatch/routing/url_for.rb
+++ b/actionpack/lib/action_dispatch/routing/url_for.rb
@@ -131,10 +131,14 @@ module ActionDispatch
#
# Examples:
#
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080' # => 'http://somehost.org:8080/tasks/testing'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true # => '/tasks/testing#ok'
- # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true # => 'http://somehost.org/tasks/testing/'
- # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33' # => 'http://somehost.org/tasks/testing?number=33'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :port => '8080'
+ # # => 'http://somehost.org:8080/tasks/testing'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :anchor => 'ok', :only_path => true
+ # # => '/tasks/testing#ok'
+ # url_for :controller => 'tasks', :action => 'testing', :trailing_slash => true
+ # # => 'http://somehost.org/tasks/testing/'
+ # url_for :controller => 'tasks', :action => 'testing', :host => 'somehost.org', :number => '33'
+ # # => 'http://somehost.org/tasks/testing?number=33'
def url_for(options = nil)
case options
when String
diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb
index 6544953df4..351eff1085 100644
--- a/actionpack/lib/sprockets/compressors.rb
+++ b/actionpack/lib/sprockets/compressors.rb
@@ -1,21 +1,37 @@
module Sprockets
- class NullCompressor
+ # An asset compressor which does nothing.
+ #
+ # This compressor simply returns the asset as-is, without any compression
+ # whatsoever. It is useful in development mode, when compression isn't
+ # needed but using the same asset pipeline as production is desired.
+ class NullCompressor #:nodoc:
def compress(content)
content
end
end
- class LazyCompressor
+ # An asset compressor which only initializes the underlying compression
+ # engine when needed.
+ #
+ # This postpones the initialization of the compressor until
+ # <code>#compress</code> is called the first time.
+ class LazyCompressor #:nodoc:
+ # Initializes a new LazyCompressor.
+ #
+ # The block should return a compressor when called, i.e. an object
+ # which responds to <code>#compress</code>.
def initialize(&block)
@block = block
end
- def compressor
- @compressor ||= @block.call || NullCompressor.new
- end
-
def compress(content)
compressor.compress(content)
end
+
+ private
+
+ def compressor
+ @compressor ||= (@block.call || NullCompressor.new)
+ end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb
index 7ad4d30d9e..062aa4dae5 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -26,7 +26,7 @@ module Sprockets
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'js')
asset.to_a.map { |dep|
- javascript_include_tag(dep, options.merge({ :debug => false, :body => true }))
+ super(dep.to_s, { :src => asset_path(dep, 'js', true) }.merge!(options))
}
else
super(source.to_s, { :src => asset_path(source, 'js', body) }.merge!(options))
@@ -42,7 +42,7 @@ module Sprockets
sources.collect do |source|
if debug && asset = asset_paths.asset_for(source, 'css')
asset.to_a.map { |dep|
- stylesheet_link_tag(dep, options.merge({ :debug => false, :body => true }))
+ super(dep.to_s, { :href => asset_path(dep, 'css', true, :request) }.merge!(options))
}
else
super(source.to_s, { :href => asset_path(source, 'css', body, :request) }.merge!(options))
@@ -58,8 +58,12 @@ module Sprockets
private
def debug_assets?
- config = Rails.application.config.assets
- config.allow_debugging && (config.debug || params[:debug_assets])
+ begin
+ config = Rails.application.config.assets
+ config.allow_debugging && (config.debug || params[:debug_assets])
+ rescue NoMethodError
+ false
+ end
end
# Override to specify an alternative prefix for asset path generation.
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index aa7a01f6c9..24d071df39 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -142,11 +142,7 @@ class RoutedRackApp
end
class BasicController
- attr_accessor :request, :params
-
- def initialize
- @params = {}
- end
+ attr_accessor :request
def config
@config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config|
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index e8d4b9c04e..700e11ff94 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.2.0 (unreleased)*
+* Support bulk change_table in mysql2 adapter, as well as the mysql one. [Jon Leighton]
+
* If multiple parameters are sent representing a date, and some are blank, the
resulting object is nil. In previous releases those values defaulted to 1. This
only affects existing but blank parameters, missing ones still raise an error.
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 511d402ee5..132dc12680 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -21,13 +21,6 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-
-activesupport_path = File.expand_path('../../../activesupport/lib', __FILE__)
-$:.unshift(activesupport_path) if File.directory?(activesupport_path) && !$:.include?(activesupport_path)
-
-activemodel_path = File.expand_path('../../../activemodel/lib', __FILE__)
-$:.unshift(activemodel_path) if File.directory?(activemodel_path) && !$:.include?(activemodel_path)
-
require 'active_support'
require 'active_support/i18n'
require 'active_model'
diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb
index 92ed844a2e..0248c7483c 100644
--- a/activerecord/lib/active_record/associations/alias_tracker.rb
+++ b/activerecord/lib/active_record/associations/alias_tracker.rb
@@ -53,12 +53,18 @@ module ActiveRecord
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
quoted_name = connection.quote_table_name(name).downcase
- table_joins.map { |join|
- # Table names + table aliases
- join.left.downcase.scan(
- /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
- ).size
- }.sum
+ counts = table_joins.map do |join|
+ if join.is_a?(Arel::Nodes::StringJoin)
+ # Table names + table aliases
+ join.left.downcase.scan(
+ /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
+ ).size
+ else
+ join.left.table_name == name ? 1 : 0
+ end
+ end
+
+ counts.sum
end
def truncate(name)
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 03aea81d2c..374791deb1 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -1849,7 +1849,7 @@ MSG
ensure_proper_type
populate_with_current_scope_attributes
- clear_timestamp_attributes
+ super
end
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
@@ -2113,14 +2113,6 @@ MSG
send("#{att}=", value) if respond_to?("#{att}=")
end
end
-
- # Clear attributes and changed_attributes
- def clear_timestamp_attributes
- all_timestamp_attributes_in_model.each do |attribute_name|
- self[attribute_name] = nil
- changed_attributes.delete(attribute_name)
- end
- end
end
Base.class_eval do
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
new file mode 100644
index 0000000000..4b7c74e0b8
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -0,0 +1,611 @@
+require 'active_support/core_ext/object/blank'
+
+module ActiveRecord
+ module ConnectionAdapters
+ class AbstractMysqlAdapter < AbstractAdapter
+ class Column < ConnectionAdapters::Column # :nodoc:
+ def extract_default(default)
+ if sql_type =~ /blob/i || type == :text
+ if default.blank?
+ return null ? nil : ''
+ else
+ raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
+ end
+ elsif missing_default_forged_as_empty_string?(default)
+ nil
+ else
+ super
+ end
+ end
+
+ def has_default?
+ return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
+ super
+ end
+
+ # Must return the relevant concrete adapter
+ def adapter
+ raise NotImplementedError
+ end
+
+ private
+
+ def simplified_type(field_type)
+ return :boolean if adapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
+
+ case field_type
+ when /enum/i, /set/i then :string
+ when /year/i then :integer
+ when /bit/i then :binary
+ else
+ super
+ end
+ end
+
+ def extract_limit(sql_type)
+ case sql_type
+ when /blob|text/i
+ case sql_type
+ when /tiny/i
+ 255
+ when /medium/i
+ 16777215
+ when /long/i
+ 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
+ else
+ super # we could return 65535 here, but we leave it undecorated by default
+ end
+ when /^bigint/i; 8
+ when /^int/i; 4
+ when /^mediumint/i; 3
+ when /^smallint/i; 2
+ when /^tinyint/i; 1
+ else
+ super
+ end
+ end
+
+ # MySQL misreports NOT NULL column default when none is given.
+ # We can't detect this for columns which may have a legitimate ''
+ # default (string) but we can for others (integer, datetime, boolean,
+ # and the rest).
+ #
+ # Test whether the column has default '', is not null, and is not
+ # a type allowing default ''.
+ def missing_default_forged_as_empty_string?(default)
+ type != :string && !null && default == ''
+ end
+ end
+
+ ##
+ # :singleton-method:
+ # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
+ # as boolean. If you wish to disable this emulation (which was the default
+ # behavior in versions 0.13.1 and earlier) you can add the following line
+ # to your application.rb file:
+ #
+ # ActiveRecord::ConnectionAdapters::Mysql[2]Adapter.emulate_booleans = false
+ class_attribute :emulate_booleans
+ self.emulate_booleans = true
+
+ LOST_CONNECTION_ERROR_MESSAGES = [
+ "Server shutdown in progress",
+ "Broken pipe",
+ "Lost connection to MySQL server during query",
+ "MySQL server has gone away" ]
+
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ NATIVE_DATABASE_TYPES = {
+ :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
+ :string => { :name => "varchar", :limit => 255 },
+ :text => { :name => "text" },
+ :integer => { :name => "int", :limit => 4 },
+ :float => { :name => "float" },
+ :decimal => { :name => "decimal" },
+ :datetime => { :name => "datetime" },
+ :timestamp => { :name => "datetime" },
+ :time => { :name => "time" },
+ :date => { :name => "date" },
+ :binary => { :name => "blob" },
+ :boolean => { :name => "tinyint", :limit => 1 }
+ }
+
+ # FIXME: Make the first parameter more similar for the two adapters
+ def initialize(connection, logger, connection_options, config)
+ super(connection, logger)
+ @connection_options, @config = connection_options, config
+ @quoted_column_names, @quoted_table_names = {}, {}
+ end
+
+ def self.visitor_for(pool) # :nodoc:
+ Arel::Visitors::MySQL.new(pool)
+ end
+
+ def adapter_name #:nodoc:
+ self.class::ADAPTER_NAME
+ end
+
+ # Returns true, since this connection adapter supports migrations.
+ def supports_migrations?
+ true
+ end
+
+ def supports_primary_key?
+ true
+ end
+
+ # Returns true, since this connection adapter supports savepoints.
+ def supports_savepoints?
+ true
+ end
+
+ def supports_bulk_alter? #:nodoc:
+ true
+ end
+
+ def native_database_types
+ NATIVE_DATABASE_TYPES
+ end
+
+ # HELPER METHODS ===========================================
+
+ # The two drivers have slightly different ways of yielding hashes of results, so
+ # this method must be implemented to provide a uniform interface.
+ def each_hash(result) # :nodoc:
+ raise NotImplementedError
+ end
+
+ # Overridden by the adapters to instantiate their specific Column type.
+ def new_column(field, default, type, null) # :nodoc:
+ Column.new(field, default, type, null)
+ end
+
+ # Must return the Mysql error number from the exception, if the exception has an
+ # error number.
+ def error_number(exception) # :nodoc:
+ raise NotImplementedError
+ end
+
+ # QUOTING ==================================================
+
+ def quote(value, column = nil)
+ if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
+ s = column.class.string_to_binary(value).unpack("H*")[0]
+ "x'#{s}'"
+ elsif value.kind_of?(BigDecimal)
+ value.to_s("F")
+ else
+ super
+ end
+ end
+
+ def quote_column_name(name) #:nodoc:
+ @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
+ end
+
+ def quote_table_name(name) #:nodoc:
+ @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ # REFERENTIAL INTEGRITY ====================================
+
+ def disable_referential_integrity(&block) #:nodoc:
+ old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
+
+ begin
+ update("SET FOREIGN_KEY_CHECKS = 0")
+ yield
+ ensure
+ update("SET FOREIGN_KEY_CHECKS = #{old}")
+ end
+ end
+
+ # DATABASE STATEMENTS ======================================
+
+ # Executes the SQL statement in the context of this connection.
+ def execute(sql, name = nil)
+ if name == :skip_logging
+ @connection.query(sql)
+ else
+ log(sql, name) { @connection.query(sql) }
+ end
+ rescue ActiveRecord::StatementInvalid => exception
+ if exception.message.split(":").first =~ /Packets out of order/
+ raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
+ else
+ raise
+ end
+ end
+
+ # MysqlAdapter has to free a result after using it, so we use this method to write
+ # stuff in a abstract way without concerning ourselves about whether it needs to be
+ # explicitly freed or not.
+ def execute_and_free(sql, name = nil) #:nodoc:
+ yield execute(sql, name)
+ end
+
+ def update_sql(sql, name = nil) #:nodoc:
+ super
+ @connection.affected_rows
+ end
+
+ def begin_db_transaction
+ execute "BEGIN"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def commit_db_transaction #:nodoc:
+ execute "COMMIT"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def rollback_db_transaction #:nodoc:
+ execute "ROLLBACK"
+ rescue Exception
+ # Transactions aren't supported
+ end
+
+ def create_savepoint
+ execute("SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def rollback_to_savepoint
+ execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
+ end
+
+ def release_savepoint
+ execute("RELEASE SAVEPOINT #{current_savepoint_name}")
+ end
+
+ # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
+ # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
+ # these, we must use a subquery. However, MySQL is too stupid to create a
+ # temporary table for this automatically, so we have to give it some prompting
+ # in the form of a subsubquery. Ugh!
+ def join_to_update(update, select) #:nodoc:
+ if select.limit || select.offset || select.orders.any?
+ subsubselect = select.clone
+ subsubselect.projections = [update.key]
+
+ subselect = Arel::SelectManager.new(select.engine)
+ subselect.project Arel.sql(update.key.name)
+ subselect.from subsubselect.as('__active_record_temp')
+
+ update.where update.key.in(subselect)
+ else
+ update.table select.source
+ update.wheres = select.constraints
+ end
+ end
+
+ # SCHEMA STATEMENTS ========================================
+
+ def structure_dump #:nodoc:
+ if supports_views?
+ sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
+ else
+ sql = "SHOW TABLES"
+ end
+
+ select_all(sql).map do |table|
+ table.delete('Table_type')
+ sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
+ exec_without_stmt(sql).first['Create Table'] + ";\n\n"
+ end.join("")
+ end
+
+ # Drops the database specified on the +name+ attribute
+ # and creates it again using the provided +options+.
+ def recreate_database(name, options = {})
+ drop_database(name)
+ create_database(name, options)
+ end
+
+ # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
+ # Charset defaults to utf8.
+ #
+ # Example:
+ # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
+ # create_database 'matt_development'
+ # create_database 'matt_development', :charset => :big5
+ def create_database(name, options = {})
+ if options[:collation]
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ else
+ execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ end
+ end
+
+ # Drops a MySQL database.
+ #
+ # Example:
+ # drop_database('sebastian_development')
+ def drop_database(name) #:nodoc:
+ execute "DROP DATABASE IF EXISTS `#{name}`"
+ end
+
+ def current_database
+ select_value 'SELECT DATABASE() as db'
+ end
+
+ # Returns the database character set.
+ def charset
+ show_variable 'character_set_database'
+ end
+
+ # Returns the database collation strategy.
+ def collation
+ show_variable 'collation_database'
+ end
+
+ def tables(name = nil, database = nil) #:nodoc:
+ sql = ["SHOW TABLES", database].compact.join(' IN ')
+
+ execute_and_free(sql, 'SCHEMA') do |result|
+ result.collect { |field| field.first }
+ end
+ end
+
+ def table_exists?(name)
+ return true if super
+
+ name = name.to_s
+ schema, table = name.split('.', 2)
+
+ unless table # A table was provided without a schema
+ table = schema
+ schema = nil
+ end
+
+ tables(nil, schema).include? table
+ end
+
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil) #:nodoc:
+ indexes = []
+ current_index = nil
+ execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
+ each_hash(result) do |row|
+ if current_index != row[:Key_name]
+ next if row[:Key_name] == 'PRIMARY' # skip the primary key
+ current_index = row[:Key_name]
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
+ end
+
+ indexes.last.columns << row[:Column_name]
+ indexes.last.lengths << row[:Sub_part]
+ end
+ end
+
+ indexes
+ end
+
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
+ def columns(table_name, name = nil)#:nodoc:
+ sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
+ execute_and_free(sql, 'SCHEMA') do |result|
+ each_hash(result).map do |field|
+ new_column(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
+ end
+ end
+ end
+
+ def create_table(table_name, options = {}) #:nodoc:
+ super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ end
+
+ def bulk_change_table(table_name, operations) #:nodoc:
+ sqls = operations.map do |command, args|
+ table, arguments = args.shift, args
+ method = :"#{command}_sql"
+
+ if respond_to?(method)
+ send(method, table, *arguments)
+ else
+ raise "Unknown method called : #{method}(#{arguments.inspect})"
+ end
+ end.flatten.join(", ")
+
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
+ end
+
+ # Renames a table.
+ #
+ # Example:
+ # rename_table('octopuses', 'octopi')
+ def rename_table(table_name, new_name)
+ execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
+ end
+
+ def add_column(table_name, column_name, type, options = {})
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
+ end
+
+ def change_column_default(table_name, column_name, default)
+ column = column_for(table_name, column_name)
+ change_column table_name, column_name, column.sql_type, :default => default
+ end
+
+ def change_column_null(table_name, column_name, null, default = nil)
+ column = column_for(table_name, column_name)
+
+ unless null || default.nil?
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ end
+
+ change_column table_name, column_name, column.sql_type, :null => null
+ end
+
+ def change_column(table_name, column_name, type, options = {}) #:nodoc:
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
+ end
+
+ def rename_column(table_name, column_name, new_column_name) #:nodoc:
+ execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
+ end
+
+ # Maps logical Rails types to MySQL-specific data types.
+ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
+ return super unless type.to_s == 'integer'
+
+ case limit
+ when 1; 'tinyint'
+ when 2; 'smallint'
+ when 3; 'mediumint'
+ when nil, 4, 11; 'int(11)' # compatibility with MySQL default
+ when 5..8; 'bigint'
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ end
+ end
+
+ def add_column_position!(sql, options)
+ if options[:first]
+ sql << " FIRST"
+ elsif options[:after]
+ sql << " AFTER #{quote_column_name(options[:after])}"
+ end
+ end
+
+ # SHOW VARIABLES LIKE 'name'
+ def show_variable(name)
+ variables = select_all("SHOW VARIABLES LIKE '#{name}'")
+ variables.first['Value'] unless variables.empty?
+ end
+
+ # Returns a table's primary key and belonging sequence.
+ def pk_and_sequence_for(table)
+ execute_and_free("DESCRIBE #{quote_table_name(table)}", 'SCHEMA') do |result|
+ keys = each_hash(result).select { |row| row[:Key] == 'PRI' }.map { |row| row[:Field] }
+ keys.length == 1 ? [keys.first, nil] : nil
+ end
+ end
+
+ # Returns just a table's primary key
+ def primary_key(table)
+ pk_and_sequence = pk_and_sequence_for(table)
+ pk_and_sequence && pk_and_sequence.first
+ end
+
+ def case_sensitive_modifier(node)
+ Arel::Nodes::Bin.new(node)
+ end
+
+ def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
+ where_sql
+ end
+
+ protected
+
+ def quoted_columns_for_index(column_names, options = {})
+ length = options[:length] if options.is_a?(Hash)
+
+ case length
+ when Hash
+ column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
+ when Fixnum
+ column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
+ else
+ column_names.map {|name| quote_column_name(name) }
+ end
+ end
+
+ def translate_exception(exception, message)
+ case error_number(exception)
+ when 1062
+ RecordNotUnique.new(message, exception)
+ when 1452
+ InvalidForeignKey.new(message, exception)
+ else
+ super
+ end
+ end
+
+ def add_column_sql(table_name, column_name, type, options = {})
+ add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(add_column_sql, options)
+ add_column_position!(add_column_sql, options)
+ add_column_sql
+ end
+
+ def change_column_sql(table_name, column_name, type, options = {})
+ column = column_for(table_name, column_name)
+
+ unless options_include_default?(options)
+ options[:default] = column.default
+ end
+
+ unless options.has_key?(:null)
+ options[:null] = column.null
+ end
+
+ change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
+ add_column_options!(change_column_sql, options)
+ add_column_position!(change_column_sql, options)
+ change_column_sql
+ end
+
+ def rename_column_sql(table_name, column_name, new_column_name)
+ options = {}
+
+ if column = columns(table_name).find { |c| c.name == column_name.to_s }
+ options[:default] = column.default
+ options[:null] = column.null
+ else
+ raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
+ end
+
+ current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
+ rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
+ add_column_options!(rename_column_sql, options)
+ rename_column_sql
+ end
+
+ def remove_column_sql(table_name, *column_names)
+ columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
+ end
+ alias :remove_columns_sql :remove_column
+
+ def add_index_sql(table_name, column_name, options = {})
+ index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
+ "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
+ end
+
+ def remove_index_sql(table_name, options = {})
+ index_name = index_name_for_remove(table_name, options)
+ "DROP INDEX #{index_name}"
+ end
+
+ def add_timestamps_sql(table_name)
+ [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
+ end
+
+ def remove_timestamps_sql(table_name)
+ [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
+ end
+
+ private
+
+ def supports_views?
+ version[0] >= 5
+ end
+
+ def column_for(table_name, column_name)
+ unless column = columns(table_name).find { |c| c.name == column_name.to_s }
+ raise "No such column: #{table_name}.#{column_name}"
+ end
+ column
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index ef51f5ebca..8b574518e5 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+require 'active_record/connection_adapters/abstract_mysql_adapter'
gem 'mysql2', '~> 0.3.6'
require 'mysql2'
@@ -20,191 +20,51 @@ module ActiveRecord
end
module ConnectionAdapters
- class Mysql2IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths) #:nodoc:
- end
+ class Mysql2Adapter < AbstractMysqlAdapter
- class Mysql2Column < Column
- BOOL = "tinyint(1)"
- def extract_default(default)
- if sql_type =~ /blob/i || type == :text
- if default.blank?
- return null ? nil : ''
- else
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- elsif missing_default_forged_as_empty_string?(default)
- nil
- else
- super
+ class Column < AbstractMysqlAdapter::Column # :nodoc:
+ def adapter
+ Mysql2Adapter
end
end
- def has_default?
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
- super
- end
-
- private
- def simplified_type(field_type)
- return :boolean if Mysql2Adapter.emulate_booleans && field_type.downcase.index(BOOL)
-
- case field_type
- when /enum/i, /set/i then :string
- when /year/i then :integer
- when /bit/i then :binary
- else
- super
- end
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /blob|text/i
- case sql_type
- when /tiny/i
- 255
- when /medium/i
- 16777215
- when /long/i
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
- else
- super # we could return 65535 here, but we leave it undecorated by default
- end
- when /^bigint/i; 8
- when /^int/i; 4
- when /^mediumint/i; 3
- when /^smallint/i; 2
- when /^tinyint/i; 1
- else
- super
- end
- end
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
- end
-
- class Mysql2Adapter < AbstractAdapter
- cattr_accessor :emulate_booleans
- self.emulate_booleans = true
-
ADAPTER_NAME = 'Mysql2'
- PRIMARY = "PRIMARY"
-
- LOST_CONNECTION_ERROR_MESSAGES = [
- "Server shutdown in progress",
- "Broken pipe",
- "Lost connection to MySQL server during query",
- "MySQL server has gone away" ]
-
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
- NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "int", :limit => 4 },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "tinyint", :limit => 1 }
- }
def initialize(connection, logger, connection_options, config)
- super(connection, logger)
- @connection_options, @config = connection_options, config
- @quoted_column_names, @quoted_table_names = {}, {}
+ super
configure_connection
end
- def self.visitor_for(pool) # :nodoc:
- Arel::Visitors::MySQL.new(pool)
- end
-
- def adapter_name
- ADAPTER_NAME
- end
-
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations?
- true
- end
+ # HELPER METHODS ===========================================
- def supports_primary_key?
- true
- end
-
- # Returns true, since this connection adapter supports savepoints.
- def supports_savepoints?
- true
- end
-
- def native_database_types
- NATIVE_DATABASE_TYPES
- end
-
- # QUOTING ==================================================
-
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
- s = column.class.string_to_binary(value).unpack("H*")[0]
- "x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
+ def each_hash(result) # :nodoc:
+ if block_given?
+ result.each(:as => :hash, :symbolize_keys => true) do |row|
+ yield row
+ end
else
- super
+ to_enum(:each_hash, result)
end
end
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
+ def new_column(field, default, type, null) # :nodoc:
+ Column.new(field, default, type, null)
end
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
+ def error_number(exception)
+ exception.error_number if exception.respond_to?(:error_number)
end
+ # QUOTING ==================================================
+
def quote_string(string)
@connection.escape(string)
end
- def quoted_true
- QUOTED_TRUE
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
def substitute_at(column, index)
Arel.sql "\0"
end
- # REFERENTIAL INTEGRITY ====================================
-
- def disable_referential_integrity(&block) #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
-
- begin
- update("SET FOREIGN_KEY_CHECKS = 0")
- yield
- ensure
- update("SET FOREIGN_KEY_CHECKS = #{old}")
- end
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -217,11 +77,6 @@ module ActiveRecord
connect
end
- # this is set to true in 2.3, but we don't want it to be
- def requires_reloading?
- false
- end
-
# Disconnects from the database if already connected.
# Otherwise, this method does nothing.
def disconnect!
@@ -277,17 +132,22 @@ module ActiveRecord
# make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
# made since we established the connection
@connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
- if name == :skip_logging
- @connection.query(sql)
- else
- log(sql, name) { @connection.query(sql) }
- end
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
- else
- raise
- end
+
+ super
+ end
+
+ def exec_query(sql, name = 'SQL', binds = [])
+ result = execute(sql, name)
+ ActiveRecord::Result.new(result.fields, result.to_a)
+ end
+
+ alias exec_without_stmt exec_query
+
+ # Returns an array of record hashes with the column names as keys and
+ # column values as values.
+ def select(sql, name = nil, binds = [])
+ binds = binds.dup
+ exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -316,379 +176,35 @@ module ActiveRecord
@connection.last_id
end
- def update_sql(sql, name = nil)
- super
- @connection.affected_rows
- end
-
- def begin_db_transaction
- execute "BEGIN"
- rescue Exception
- # Transactions aren't supported
- end
-
- def commit_db_transaction
- execute "COMMIT"
- rescue Exception
- # Transactions aren't supported
- end
-
- def rollback_db_transaction
- execute "ROLLBACK"
- rescue Exception
- # Transactions aren't supported
- end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
- # SCHEMA STATEMENTS ========================================
-
- def structure_dump
- if supports_views?
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
- else
- sql = "SHOW TABLES"
- end
-
- select_all(sql).inject("") do |structure, table|
- table.delete('Table_type')
- structure += select_one("SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}")["Create Table"] + ";\n\n"
- end
- end
-
- # Drops the database specified on the +name+ attribute
- # and creates it again using the provided +options+.
- def recreate_database(name, options = {})
- drop_database(name)
- create_database(name, options)
- end
-
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
- # Charset defaults to utf8.
- #
- # Example:
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
- # create_database 'matt_development'
- # create_database 'matt_development', :charset => :big5
- def create_database(name, options = {})
- if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
- else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
- end
- end
-
- # Drops a MySQL database.
- #
- # Example:
- # drop_database('sebastian_development')
- def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
- end
-
- def current_database
- select_value 'SELECT DATABASE() as db'
- end
-
- # Returns the database character set.
- def charset
- show_variable 'character_set_database'
- end
-
- # Returns the database collation strategy.
- def collation
- show_variable 'collation_database'
- end
-
- def tables(name = nil, database = nil) #:nodoc:
- sql = ["SHOW TABLES", database].compact.join(' IN ')
- execute(sql, 'SCHEMA').collect do |field|
- field.first
- end
- end
-
- def table_exists?(name)
- return true if super
-
- name = name.to_s
- schema, table = name.split('.', 2)
-
- unless table # A table was provided without a schema
- table = schema
- schema = nil
- end
-
- tables(nil, schema).include? table
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
- indexes = []
- current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", 'SCHEMA')
- result.each(:symbolize_keys => true, :as => :hash) do |row|
- if current_index != row[:Key_name]
- next if row[:Key_name] == PRIMARY # skip the primary key
- current_index = row[:Key_name]
- indexes << Mysql2IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique] == 0, [], [])
- end
-
- indexes.last.columns << row[:Column_name]
- indexes.last.lengths << row[:Sub_part]
- end
- indexes
- end
-
- # Returns an array of +Mysql2Column+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- columns = []
- result = execute(sql, 'SCHEMA')
- result.each(:symbolize_keys => true, :as => :hash) { |field|
- columns << Mysql2Column.new(field[:Field], field[:Default], field[:Type], field[:Null] == "YES")
- }
- columns
- end
-
- def create_table(table_name, options = {})
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
- end
-
- # Renames a table.
- #
- # Example:
- # rename_table('octopuses', 'octopi')
- def rename_table(table_name, new_name)
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
- end
-
- def add_column(table_name, column_name, type, options = {})
- add_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- add_column_position!(add_column_sql, options)
- execute(add_column_sql)
- end
-
- def change_column_default(table_name, column_name, default)
- column = column_for(table_name, column_name)
- change_column table_name, column_name, column.sql_type, :default => default
- end
-
- def change_column_null(table_name, column_name, null, default = nil)
- column = column_for(table_name, column_name)
-
- unless null || default.nil?
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
- end
-
- change_column table_name, column_name, column.sql_type, :null => null
- end
-
- def change_column(table_name, column_name, type, options = {})
- column = column_for(table_name, column_name)
-
- unless options_include_default?(options)
- options[:default] = column.default
- end
-
- unless options.has_key?(:null)
- options[:null] = column.null
- end
-
- change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(change_column_sql, options)
- add_column_position!(change_column_sql, options)
- execute(change_column_sql)
- end
-
- def rename_column(table_name, column_name, new_column_name)
- options = {}
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- options[:null] = column.null
- else
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
- end
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
- rename_column_sql = "ALTER TABLE #{quote_table_name(table_name)} CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
- add_column_options!(rename_column_sql, options)
- execute(rename_column_sql)
- end
-
- # Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
- end
- end
+ private
- def add_column_position!(sql, options)
- if options[:first]
- sql << " FIRST"
- elsif options[:after]
- sql << " AFTER #{quote_column_name(options[:after])}"
- end
+ def connect
+ @connection = Mysql2::Client.new(@config)
+ configure_connection
end
- # SHOW VARIABLES LIKE 'name'.
- def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
- variables.first['Value'] unless variables.empty?
- end
+ def configure_connection
+ @connection.query_options.merge!(:as => :array)
- # Returns a table's primary key and belonging sequence.
- def pk_and_sequence_for(table)
- keys = []
- result = execute("DESCRIBE #{quote_table_name(table)}", 'SCHEMA')
- result.each(:symbolize_keys => true, :as => :hash) do |row|
- keys << row[:Field] if row[:Key] == "PRI"
- end
- keys.length == 1 ? [keys.first, nil] : nil
- end
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ variable_assignments = ['SQL_AUTO_IS_NULL=0']
+ encoding = @config[:encoding]
- # Returns just a table's primary key
- def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
- end
+ # make sure we set the encoding
+ variable_assignments << "NAMES '#{encoding}'" if encoding
- def case_sensitive_modifier(node)
- Arel::Nodes::Bin.new(node)
- end
+ # increase timeout so mysql server doesn't disconnect us
+ wait_timeout = @config[:wait_timeout]
+ wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
+ variable_assignments << "@@wait_timeout = #{wait_timeout}"
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
- where_sql
+ execute("SET #{variable_assignments.join(', ')}", :skip_logging)
end
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
- # these, we must use a subquery. However, MySQL is too stupid to create a
- # temporary table for this automatically, so we have to give it some prompting
- # in the form of a subsubquery. Ugh!
- def join_to_update(update, select) #:nodoc:
- if select.limit || select.offset || select.orders.any?
- subsubselect = select.clone
- subsubselect.projections = [update.key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(update.key.name)
- subselect.from subsubselect.as('__active_record_temp')
-
- update.where update.key.in(subselect)
- else
- update.table select.source
- update.wheres = select.constraints
- end
+ def version
+ @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
end
-
- protected
- def quoted_columns_for_index(column_names, options = {})
- length = options[:length] if options.is_a?(Hash)
-
- case length
- when Hash
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
- when Fixnum
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
- else
- column_names.map {|name| quote_column_name(name) }
- end
- end
-
- def translate_exception(exception, message)
- return super unless exception.respond_to?(:error_number)
-
- case exception.error_number
- when 1062
- RecordNotUnique.new(message, exception)
- when 1452
- InvalidForeignKey.new(message, exception)
- else
- super
- end
- end
-
- private
- def connect
- @connection = Mysql2::Client.new(@config)
- configure_connection
- end
-
- def configure_connection
- @connection.query_options.merge!(:as => :array)
-
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- variable_assignments = ['SQL_AUTO_IS_NULL=0']
- encoding = @config[:encoding]
-
- # make sure we set the encoding
- variable_assignments << "NAMES '#{encoding}'" if encoding
-
- # increase timeout so mysql server doesn't disconnect us
- wait_timeout = @config[:wait_timeout]
- wait_timeout = 2592000 unless wait_timeout.is_a?(Fixnum)
- variable_assignments << "@@wait_timeout = #{wait_timeout}"
-
- execute("SET #{variable_assignments.join(', ')}", :skip_logging)
- end
-
- # Returns an array of record hashes with the column names as keys and
- # column values as values.
- def select(sql, name = nil, binds = [])
- binds = binds.dup
- exec_query(sql.gsub("\0") { quote(*binds.shift.reverse) }, name).to_a
- end
-
- def exec_query(sql, name = 'SQL', binds = [])
- @connection.query_options[:database_timezone] = ActiveRecord::Base.default_timezone
-
- log(sql, name, binds) do
- begin
- result = @connection.query(sql)
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
- else
- raise
- end
- end
-
- ActiveRecord::Result.new(result.fields, result.to_a)
- end
- end
-
- def supports_views?
- version[0] >= 5
- end
-
- def version
- @version ||= @connection.info[:version].scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
- end
-
- def column_for(table_name, column_name)
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
- raise "No such column: #{table_name}.#{column_name}"
- end
- column
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index b844e5ab10..e9bdcc2104 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -1,6 +1,5 @@
-require 'active_record/connection_adapters/abstract_adapter'
-require 'active_support/core_ext/object/blank'
-require 'set'
+require 'active_record/connection_adapters/abstract_mysql_adapter'
+require 'active_support/core_ext/hash/keys'
gem 'mysql', '~> 2.8.1'
require 'mysql'
@@ -40,9 +39,29 @@ module ActiveRecord
end
module ConnectionAdapters
- class MysqlColumn < Column #:nodoc:
- class << self
- def string_to_time(value)
+ # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
+ # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
+ #
+ # Options:
+ #
+ # * <tt>:host</tt> - Defaults to "localhost".
+ # * <tt>:port</tt> - Defaults to 3306.
+ # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
+ # * <tt>:username</tt> - Defaults to "root"
+ # * <tt>:password</tt> - Defaults to nothing.
+ # * <tt>:database</tt> - The name of the database. No default, must be provided.
+ # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
+ # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
+ # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
+ # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
+ #
+ class MysqlAdapter < AbstractMysqlAdapter
+
+ class Column < AbstractMysqlAdapter::Column #:nodoc:
+ def self.string_to_time(value)
return super unless Mysql::Time === value
new_time(
value.year,
@@ -54,234 +73,69 @@ module ActiveRecord
value.second_part)
end
- def string_to_dummy_time(v)
+ def self.string_to_dummy_time(v)
return super unless Mysql::Time === v
new_time(2000, 01, 01, v.hour, v.minute, v.second, v.second_part)
end
- def string_to_date(v)
+ def self.string_to_date(v)
return super unless Mysql::Time === v
new_date(v.year, v.month, v.day)
end
- end
- def extract_default(default)
- if sql_type =~ /blob/i || type == :text
- if default.blank?
- return null ? nil : ''
- else
- raise ArgumentError, "#{type} columns cannot have a default value: #{default.inspect}"
- end
- elsif missing_default_forged_as_empty_string?(default)
- nil
- else
- super
+ def adapter
+ MysqlAdapter
end
end
- def has_default?
- return false if sql_type =~ /blob/i || type == :text #mysql forbids defaults on blob and text columns
- super
- end
-
- private
- def simplified_type(field_type)
- return :boolean if MysqlAdapter.emulate_booleans && field_type.downcase.index("tinyint(1)")
- return :string if field_type =~ /enum/i
- super
- end
-
- def extract_limit(sql_type)
- case sql_type
- when /blob|text/i
- case sql_type
- when /tiny/i
- 255
- when /medium/i
- 16777215
- when /long/i
- 2147483647 # mysql only allows 2^31-1, not 2^32-1, somewhat inconsistently with the tiny/medium/normal cases
- else
- super # we could return 65535 here, but we leave it undecorated by default
- end
- when /^bigint/i; 8
- when /^int/i; 4
- when /^mediumint/i; 3
- when /^smallint/i; 2
- when /^tinyint/i; 1
- else
- super
- end
- end
-
- # MySQL misreports NOT NULL column default when none is given.
- # We can't detect this for columns which may have a legitimate ''
- # default (string) but we can for others (integer, datetime, boolean,
- # and the rest).
- #
- # Test whether the column has default '', is not null, and is not
- # a type allowing default ''.
- def missing_default_forged_as_empty_string?(default)
- type != :string && !null && default == ''
- end
- end
-
- # The MySQL adapter will work with both Ruby/MySQL, which is a Ruby-based MySQL adapter that comes bundled with Active Record, and with
- # the faster C-based MySQL/Ruby adapter (available both as a gem and from http://www.tmtm.org/en/mysql/ruby/).
- #
- # Options:
- #
- # * <tt>:host</tt> - Defaults to "localhost".
- # * <tt>:port</tt> - Defaults to 3306.
- # * <tt>:socket</tt> - Defaults to "/tmp/mysql.sock".
- # * <tt>:username</tt> - Defaults to "root"
- # * <tt>:password</tt> - Defaults to nothing.
- # * <tt>:database</tt> - The name of the database. No default, must be provided.
- # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection.
- # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.0/en/auto-reconnect.html).
- # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcapath</tt> - Necessary to use MySQL with an SSL connection.
- # * <tt>:sslcipher</tt> - Necessary to use MySQL with an SSL connection.
- #
- class MysqlAdapter < AbstractAdapter
-
- ##
- # :singleton-method:
- # By default, the MysqlAdapter will consider all columns of type <tt>tinyint(1)</tt>
- # as boolean. If you wish to disable this emulation (which was the default
- # behavior in versions 0.13.1 and earlier) you can add the following line
- # to your application.rb file:
- #
- # ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans = false
- cattr_accessor :emulate_booleans
- self.emulate_booleans = true
-
ADAPTER_NAME = 'MySQL'
- LOST_CONNECTION_ERROR_MESSAGES = [
- "Server shutdown in progress",
- "Broken pipe",
- "Lost connection to MySQL server during query",
- "MySQL server has gone away" ]
-
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
- NATIVE_DATABASE_TYPES = {
- :primary_key => "int(11) DEFAULT NULL auto_increment PRIMARY KEY",
- :string => { :name => "varchar", :limit => 255 },
- :text => { :name => "text" },
- :integer => { :name => "int", :limit => 4 },
- :float => { :name => "float" },
- :decimal => { :name => "decimal" },
- :datetime => { :name => "datetime" },
- :timestamp => { :name => "datetime" },
- :time => { :name => "time" },
- :date => { :name => "date" },
- :binary => { :name => "blob" },
- :boolean => { :name => "tinyint", :limit => 1 }
- }
-
def initialize(connection, logger, connection_options, config)
- super(connection, logger)
- @connection_options, @config = connection_options, config
- @quoted_column_names, @quoted_table_names = {}, {}
+ super
@statements = {}
@client_encoding = nil
connect
end
- def self.visitor_for(pool) # :nodoc:
- Arel::Visitors::MySQL.new(pool)
- end
-
- def adapter_name #:nodoc:
- ADAPTER_NAME
- end
-
- def supports_bulk_alter? #:nodoc:
- true
- end
-
# Returns true, since this connection adapter supports prepared statement
# caching.
def supports_statement_cache?
true
end
- # Returns true, since this connection adapter supports migrations.
- def supports_migrations? #:nodoc:
- true
- end
+ # HELPER METHODS ===========================================
- # Returns true.
- def supports_primary_key? #:nodoc:
- true
+ def each_hash(result) # :nodoc:
+ if block_given?
+ result.each_hash do |row|
+ row.symbolize_keys!
+ yield row
+ end
+ else
+ to_enum(:each_hash, result)
+ end
end
- # Returns true, since this connection adapter supports savepoints.
- def supports_savepoints? #:nodoc:
- true
+ def new_column(field, default, type, null) # :nodoc:
+ Column.new(field, default, type, null)
end
- def native_database_types #:nodoc:
- NATIVE_DATABASE_TYPES
+ def error_number(exception) # :nodoc:
+ exception.errno if exception.respond_to?(:errno)
end
-
# QUOTING ==================================================
- def quote(value, column = nil)
- if value.kind_of?(String) && column && column.type == :binary && column.class.respond_to?(:string_to_binary)
- s = column.class.string_to_binary(value).unpack("H*")[0]
- "x'#{s}'"
- elsif value.kind_of?(BigDecimal)
- value.to_s("F")
- else
- super
- end
- end
-
def type_cast(value, column)
return super unless value == true || value == false
value ? 1 : 0
end
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
- end
-
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
- end
-
def quote_string(string) #:nodoc:
@connection.quote(string)
end
- def quoted_true
- QUOTED_TRUE
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
- # REFERENTIAL INTEGRITY ====================================
-
- def disable_referential_integrity #:nodoc:
- old = select_value("SELECT @@FOREIGN_KEY_CHECKS")
-
- begin
- update("SET FOREIGN_KEY_CHECKS = 0")
- yield
- ensure
- update("SET FOREIGN_KEY_CHECKS = #{old}")
- end
- end
-
# CONNECTION MANAGEMENT ====================================
def active?
@@ -425,20 +279,11 @@ module ActiveRecord
end
end
- # Executes an SQL query and returns a MySQL::Result object. Note that you have to free
- # the Result object after you're done using it.
- def execute(sql, name = nil) #:nodoc:
- if name == :skip_logging
- @connection.query(sql)
- else
- log(sql, name) { @connection.query(sql) }
- end
- rescue ActiveRecord::StatementInvalid => exception
- if exception.message.split(":").first =~ /Packets out of order/
- raise ActiveRecord::StatementInvalid, "'Packets out of order' error was received from the database. Please update your mysql bindings (gem install mysql) and read http://dev.mysql.com/doc/mysql/en/password-hashing.html for more information. If you're on Windows, use the Instant Rails installer to get the updated mysql bindings."
- else
- raise
- end
+ def execute_and_free(sql, name = nil)
+ result = execute(sql, name)
+ ret = yield result
+ result.free
+ ret
end
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) #:nodoc:
@@ -447,11 +292,6 @@ module ActiveRecord
end
alias :create :insert_sql
- def update_sql(sql, name = nil) #:nodoc:
- super
- @connection.affected_rows
- end
-
def exec_delete(sql, name, binds)
log(sql, name, binds) do
exec_stmt(sql, name, binds) do |cols, stmt|
@@ -467,358 +307,8 @@ module ActiveRecord
# Transactions aren't supported
end
- def commit_db_transaction #:nodoc:
- execute "COMMIT"
- rescue Exception
- # Transactions aren't supported
- end
-
- def rollback_db_transaction #:nodoc:
- execute "ROLLBACK"
- rescue Exception
- # Transactions aren't supported
- end
-
- def create_savepoint
- execute("SAVEPOINT #{current_savepoint_name}")
- end
-
- def rollback_to_savepoint
- execute("ROLLBACK TO SAVEPOINT #{current_savepoint_name}")
- end
-
- def release_savepoint
- execute("RELEASE SAVEPOINT #{current_savepoint_name}")
- end
-
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
- # these, we must use a subquery. However, MySQL is too stupid to create a
- # temporary table for this automatically, so we have to give it some prompting
- # in the form of a subsubquery. Ugh!
- def join_to_update(update, select) #:nodoc:
- if select.limit || select.offset || select.orders.any?
- subsubselect = select.clone
- subsubselect.projections = [update.key]
-
- subselect = Arel::SelectManager.new(select.engine)
- subselect.project Arel.sql(update.key.name)
- subselect.from subsubselect.as('__active_record_temp')
-
- update.where update.key.in(subselect)
- else
- update.table select.source
- update.wheres = select.constraints
- end
- end
-
- # SCHEMA STATEMENTS ========================================
-
- def structure_dump #:nodoc:
- if supports_views?
- sql = "SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'"
- else
- sql = "SHOW TABLES"
- end
-
- select_all(sql).map do |table|
- table.delete('Table_type')
- sql = "SHOW CREATE TABLE #{quote_table_name(table.to_a.first.last)}"
- exec_without_stmt(sql).first['Create Table'] + ";\n\n"
- end.join("")
- end
-
- # Drops the database specified on the +name+ attribute
- # and creates it again using the provided +options+.
- def recreate_database(name, options = {}) #:nodoc:
- drop_database(name)
- create_database(name, options)
- end
-
- # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
- # Charset defaults to utf8.
- #
- # Example:
- # create_database 'charset_test', :charset => 'latin1', :collation => 'latin1_bin'
- # create_database 'matt_development'
- # create_database 'matt_development', :charset => :big5
- def create_database(name, options = {})
- if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
- else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
- end
- end
-
- # Drops a MySQL database.
- #
- # Example:
- # drop_database 'sebastian_development'
- def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
- end
-
- def current_database
- select_value 'SELECT DATABASE() as db'
- end
-
- # Returns the database character set.
- def charset
- show_variable 'character_set_database'
- end
-
- # Returns the database collation strategy.
- def collation
- show_variable 'collation_database'
- end
-
- def tables(name = nil, database = nil) #:nodoc:
- result = execute(["SHOW TABLES", database].compact.join(' IN '), 'SCHEMA')
- tables = result.collect { |field| field[0] }
- result.free
- tables
- end
-
- def table_exists?(name)
- return true if super
-
- name = name.to_s
- schema, table = name.split('.', 2)
-
- unless table # A table was provided without a schema
- table = schema
- schema = nil
- end
-
- tables(nil, schema).include? table
- end
-
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)#:nodoc:
- indexes = []
- current_index = nil
- result = execute("SHOW KEYS FROM #{quote_table_name(table_name)}", name)
- result.each do |row|
- if current_index != row[2]
- next if row[2] == "PRIMARY" # skip the primary key
- current_index = row[2]
- indexes << IndexDefinition.new(row[0], row[2], row[1] == "0", [], [])
- end
-
- indexes.last.columns << row[4]
- indexes.last.lengths << row[7]
- end
- result.free
- indexes
- end
-
- # Returns an array of +MysqlColumn+ objects for the table specified by +table_name+.
- def columns(table_name, name = nil)#:nodoc:
- sql = "SHOW FIELDS FROM #{quote_table_name(table_name)}"
- result = execute(sql, 'SCHEMA')
- columns = result.collect { |field| MysqlColumn.new(field[0], field[4], field[1], field[2] == "YES") }
- result.free
- columns
- end
-
- def create_table(table_name, options = {}) #:nodoc:
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
- end
-
- # Renames a table.
- #
- # Example:
- # rename_table('octopuses', 'octopi')
- def rename_table(table_name, new_name)
- execute "RENAME TABLE #{quote_table_name(table_name)} TO #{quote_table_name(new_name)}"
- end
-
- def bulk_change_table(table_name, operations) #:nodoc:
- sqls = operations.map do |command, args|
- table, arguments = args.shift, args
- method = :"#{command}_sql"
-
- if respond_to?(method)
- send(method, table, *arguments)
- else
- raise "Unknown method called : #{method}(#{arguments.inspect})"
- end
- end.flatten.join(", ")
-
- execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}")
- end
-
- def add_column(table_name, column_name, type, options = {})
- execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)}")
- end
-
- def change_column_default(table_name, column_name, default) #:nodoc:
- column = column_for(table_name, column_name)
- change_column table_name, column_name, column.sql_type, :default => default
- end
-
- def change_column_null(table_name, column_name, null, default = nil)
- column = column_for(table_name, column_name)
-
- unless null || default.nil?
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
- end
-
- change_column table_name, column_name, column.sql_type, :null => null
- end
-
- def change_column(table_name, column_name, type, options = {}) #:nodoc:
- execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}")
- end
-
- def rename_column(table_name, column_name, new_column_name) #:nodoc:
- execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}")
- end
-
- # Maps logical Rails types to MySQL-specific data types.
- def type_to_sql(type, limit = nil, precision = nil, scale = nil)
- return super unless type.to_s == 'integer'
-
- case limit
- when 1; 'tinyint'
- when 2; 'smallint'
- when 3; 'mediumint'
- when nil, 4, 11; 'int(11)' # compatibility with MySQL default
- when 5..8; 'bigint'
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
- end
- end
-
- def add_column_position!(sql, options)
- if options[:first]
- sql << " FIRST"
- elsif options[:after]
- sql << " AFTER #{quote_column_name(options[:after])}"
- end
- end
-
- # SHOW VARIABLES LIKE 'name'
- def show_variable(name)
- variables = select_all("SHOW VARIABLES LIKE '#{name}'")
- variables.first['Value'] unless variables.empty?
- end
-
- # Returns a table's primary key and belonging sequence.
- def pk_and_sequence_for(table) #:nodoc:
- keys = []
- result = execute("describe #{quote_table_name(table)}", 'SCHEMA')
- result.each_hash do |h|
- keys << h["Field"]if h["Key"] == "PRI"
- end
- result.free
- keys.length == 1 ? [keys.first, nil] : nil
- end
-
- # Returns just a table's primary key
- def primary_key(table)
- pk_and_sequence = pk_and_sequence_for(table)
- pk_and_sequence && pk_and_sequence.first
- end
-
- def case_sensitive_modifier(node)
- Arel::Nodes::Bin.new(node)
- end
-
- def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
- where_sql
- end
-
- protected
- def quoted_columns_for_index(column_names, options = {})
- length = options[:length] if options.is_a?(Hash)
-
- case length
- when Hash
- column_names.map {|name| length[name] ? "#{quote_column_name(name)}(#{length[name]})" : quote_column_name(name) }
- when Fixnum
- column_names.map {|name| "#{quote_column_name(name)}(#{length})"}
- else
- column_names.map {|name| quote_column_name(name) }
- end
- end
-
- def translate_exception(exception, message)
- return super unless exception.respond_to?(:errno)
-
- case exception.errno
- when 1062
- RecordNotUnique.new(message, exception)
- when 1452
- InvalidForeignKey.new(message, exception)
- else
- super
- end
- end
-
- def add_column_sql(table_name, column_name, type, options = {})
- add_column_sql = "ADD #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(add_column_sql, options)
- add_column_position!(add_column_sql, options)
- add_column_sql
- end
-
- def remove_column_sql(table_name, *column_names)
- columns_for_remove(table_name, *column_names).map {|column_name| "DROP #{column_name}" }
- end
- alias :remove_columns_sql :remove_column
-
- def change_column_sql(table_name, column_name, type, options = {})
- column = column_for(table_name, column_name)
-
- unless options_include_default?(options)
- options[:default] = column.default
- end
-
- unless options.has_key?(:null)
- options[:null] = column.null
- end
-
- change_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
- add_column_options!(change_column_sql, options)
- add_column_position!(change_column_sql, options)
- change_column_sql
- end
-
- def rename_column_sql(table_name, column_name, new_column_name)
- options = {}
-
- if column = columns(table_name).find { |c| c.name == column_name.to_s }
- options[:default] = column.default
- options[:null] = column.null
- else
- raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
- end
-
- current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'")["Type"]
- rename_column_sql = "CHANGE #{quote_column_name(column_name)} #{quote_column_name(new_column_name)} #{current_type}"
- add_column_options!(rename_column_sql, options)
- rename_column_sql
- end
-
- def add_index_sql(table_name, column_name, options = {})
- index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
- "ADD #{index_type} INDEX #{index_name} (#{index_columns})"
- end
-
- def remove_index_sql(table_name, options = {})
- index_name = index_name_for_remove(table_name, options)
- "DROP INDEX #{index_name}"
- end
-
- def add_timestamps_sql(table_name)
- [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)]
- end
-
- def remove_timestamps_sql(table_name)
- [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]
- end
-
private
+
def exec_stmt(sql, name, binds)
cache = {}
if binds.empty?
@@ -830,7 +320,6 @@ module ActiveRecord
stmt = cache[:stmt]
end
-
begin
stmt.execute(*binds.map { |col, val| type_cast(val, col) })
rescue Mysql::Error => e
@@ -859,59 +348,48 @@ module ActiveRecord
result
end
- def connect
- encoding = @config[:encoding]
- if encoding
- @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
- end
-
- if @config[:sslca] || @config[:sslkey]
- @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
- end
-
- @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
- @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
- @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
+ def connect
+ encoding = @config[:encoding]
+ if encoding
+ @connection.options(Mysql::SET_CHARSET_NAME, encoding) rescue nil
+ end
- @connection.real_connect(*@connection_options)
+ if @config[:sslca] || @config[:sslkey]
+ @connection.ssl_set(@config[:sslkey], @config[:sslcert], @config[:sslca], @config[:sslcapath], @config[:sslcipher])
+ end
- # reconnect must be set after real_connect is called, because real_connect sets it to false internally
- @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
+ @connection.options(Mysql::OPT_CONNECT_TIMEOUT, @config[:connect_timeout]) if @config[:connect_timeout]
+ @connection.options(Mysql::OPT_READ_TIMEOUT, @config[:read_timeout]) if @config[:read_timeout]
+ @connection.options(Mysql::OPT_WRITE_TIMEOUT, @config[:write_timeout]) if @config[:write_timeout]
- configure_connection
- end
+ @connection.real_connect(*@connection_options)
- def configure_connection
- encoding = @config[:encoding]
- execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
+ # reconnect must be set after real_connect is called, because real_connect sets it to false internally
+ @connection.reconnect = !!@config[:reconnect] if @connection.respond_to?(:reconnect=)
- # By default, MySQL 'where id is null' selects the last inserted id.
- # Turn this off. http://dev.rubyonrails.org/ticket/6778
- execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
- end
+ configure_connection
+ end
- def select(sql, name = nil, binds = [])
- @connection.query_with_result = true
- rows = exec_query(sql, name, binds).to_a
- @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
- rows
- end
+ def configure_connection
+ encoding = @config[:encoding]
+ execute("SET NAMES '#{encoding}'", :skip_logging) if encoding
- def supports_views?
- version[0] >= 5
- end
+ # By default, MySQL 'where id is null' selects the last inserted id.
+ # Turn this off. http://dev.rubyonrails.org/ticket/6778
+ execute("SET SQL_AUTO_IS_NULL=0", :skip_logging)
+ end
- # Returns the version of the connected MySQL server.
- def version
- @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
- end
+ def select(sql, name = nil, binds = [])
+ @connection.query_with_result = true
+ rows = exec_query(sql, name, binds).to_a
+ @connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
+ rows
+ end
- def column_for(table_name, column_name)
- unless column = columns(table_name).find { |c| c.name == column_name.to_s }
- raise "No such column: #{table_name}.#{column_name}"
- end
- column
- end
+ # Returns the version of the connected MySQL server.
+ def version
+ @version ||= @connection.server_info.scan(/^(\d+)\.(\d+)\.(\d+)/).flatten.map { |v| v.to_i }
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 45c13bdcd6..ba4a6c7a78 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -954,6 +954,8 @@ module ActiveRecord
end
module Utils
+ extend self
+
# Returns an array of <tt>[schema_name, table_name]</tt> extracted from +name+.
# +schema_name+ is nil if not specified in +name+.
# +schema_name+ and +table_name+ exclude surrounding quotes (regardless of whether provided in +name+)
@@ -964,7 +966,7 @@ module ActiveRecord
# * <tt>schema_name.table_name</tt>
# * <tt>schema_name."table.name"</tt>
# * <tt>"schema.name"."table name"</tt>
- def self.extract_schema_and_table(name)
+ def extract_schema_and_table(name)
table, schema = name.scan(/[^".\s]+|"[^"]*"/)[0..1].collect{|m| m.gsub(/(^"|"$)/,'') }.reverse
[schema, table]
end
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index e485901440..10c0dc6f2a 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -61,6 +61,12 @@ module ActiveRecord
status, headers, body = @app.call(env)
[status, headers, BodyProxy.new(old, body)]
+ rescue Exception => e
+ ActiveRecord::Base.connection.clear_query_cache
+ unless old
+ ActiveRecord::Base.connection.disable_query_cache!
+ end
+ raise e
end
end
end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 1654ae1eac..7eda9ad8e8 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -254,12 +254,12 @@ module ActiveRecord
association_joins = buckets['association_join'] || []
stashed_association_joins = buckets['stashed_join'] || []
- join_nodes = buckets['join_node'] || []
+ join_nodes = (buckets['join_node'] || []).uniq
string_joins = (buckets['string_join'] || []).map { |x|
x.strip
}.uniq
- join_list = custom_join_ast(manager, string_joins)
+ join_list = join_nodes + custom_join_ast(manager, string_joins)
join_dependency = ActiveRecord::Associations::JoinDependency.new(
@klass,
@@ -267,10 +267,6 @@ module ActiveRecord
join_list
)
- join_nodes.each do |join|
- join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
- end
-
join_dependency.graft(*stashed_association_joins)
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
@@ -280,7 +276,6 @@ module ActiveRecord
association.join_to(manager)
end
- manager.join_sources.concat join_nodes.uniq
manager.join_sources.concat join_list
manager
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 1511c71ffc..cccac6ffd7 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -37,6 +37,10 @@ module ActiveRecord
self.record_timestamps = true
end
+ def initialize_dup(other)
+ clear_timestamp_attributes
+ end
+
private
def create #:nodoc:
@@ -95,6 +99,13 @@ module ActiveRecord
def current_time_from_proper_timezone #:nodoc:
self.class.default_timezone == :utc ? Time.now.utc : Time.now
end
+
+ # Clear attributes and changed_attributes
+ def clear_timestamp_attributes
+ all_timestamp_attributes_in_model.each do |attribute_name|
+ self[attribute_name] = nil
+ changed_attributes.delete(attribute_name)
+ end
+ end
end
end
-
diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
index 509baacaef..94fc3564df 100644
--- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb
+++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -2,7 +2,7 @@ require "cases/helper"
class ActiveSchemaTest < ActiveRecord::TestCase
def setup
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
alias_method :execute_without_stub, :execute
remove_method :execute
def execute(sql, name = nil) return sql end
@@ -10,7 +10,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
end
def teardown
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
remove_method :execute
alias_method :execute, :execute_without_stub
end
@@ -99,7 +99,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
private
def with_real_execute
#we need to actually modify some data, so we make execute point to the original method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
alias_method :execute_with_stub, :execute
remove_method :execute
alias_method :execute, :execute_without_stub
@@ -107,7 +107,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
yield
ensure
#before finishing, we restore the alias to the mock-up method
- ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
+ ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.class_eval do
remove_method :execute
alias_method :execute, :execute_with_stub
end
diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb
index 3a7f1badf0..4c6d865d59 100644
--- a/activerecord/test/cases/adapters/postgresql/schema_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb
@@ -219,21 +219,6 @@ class SchemaTest < ActiveRecord::TestCase
end
end
- def test_extract_schema_and_table
- {
- %(table_name) => [nil,'table_name'],
- %("table.name") => [nil,'table.name'],
- %(schema.table_name) => %w{schema table_name},
- %("schema".table_name) => %w{schema table_name},
- %(schema."table_name") => %w{schema table_name},
- %("schema"."table_name") => %w{schema table_name},
- %("even spaces".table) => ['even spaces','table'],
- %(schema."table.name") => ['schema', 'table.name']
- }.each do |given, expect|
- assert_equal expect, ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils.extract_schema_and_table(given)
- end
- end
-
def test_current_schema
{
%('$user',public) => 'public',
diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb
new file mode 100644
index 0000000000..5f08f79171
--- /dev/null
+++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb
@@ -0,0 +1,18 @@
+class PostgreSQLUtilsTest < ActiveSupport::TestCase
+ include ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::Utils
+
+ def test_extract_schema_and_table
+ {
+ %(table_name) => [nil,'table_name'],
+ %("table.name") => [nil,'table.name'],
+ %(schema.table_name) => %w{schema table_name},
+ %("schema".table_name) => %w{schema table_name},
+ %(schema."table_name") => %w{schema table_name},
+ %("schema"."table_name") => %w{schema table_name},
+ %("even spaces".table) => ['even spaces','table'],
+ %(schema."table.name") => ['schema', 'table.name']
+ }.each do |given, expect|
+ assert_equal expect, extract_schema_and_table(given)
+ end
+ end
+end
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 5f2328ff95..b703c96ec1 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -821,4 +821,8 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
assert person.posts.loaded?, 'person.posts should be loaded'
assert_equal [], person.posts
end
+
+ def test_explicitly_joining_join_table
+ assert_equal owners(:blackbeard).toys, owners(:blackbeard).toys.with_pet
+ end
end
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index bee183cc67..1e647b5970 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -81,7 +81,13 @@ class BasicsTest < ActiveRecord::TestCase
}
quoted = conn.quote_column_name "foo#{badchar}bar"
- assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
+ if current_adapter?(:OracleAdapter)
+ # Oracle does not allow double quotes in table and column names at all
+ # therefore quoting removes them
+ assert_equal("#{badchar}foobar#{badchar}", quoted)
+ else
+ assert_equal("#{badchar}foo#{badchar * 2}bar#{badchar}", quoted)
+ end
end
def test_columns_should_obey_set_primary_key
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index d1dddd4c2c..14884e42af 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -58,68 +58,68 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = MysqlColumn.new("title", "a", "binary(1)")
+ binary_column = MysqlAdapter::Column.new("title", "a", "binary(1)")
assert_equal "a", binary_column.default
- varbinary_column = MysqlColumn.new("title", "a", "varbinary(1)")
+ varbinary_column = MysqlAdapter::Column.new("title", "a", "varbinary(1)")
assert_equal "a", varbinary_column.default
end
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- MysqlColumn.new("title", "a", "blob")
+ MysqlAdapter::Column.new("title", "a", "blob")
end
assert_raise ArgumentError do
- MysqlColumn.new("title", "Hello", "text")
+ MysqlAdapter::Column.new("title", "Hello", "text")
end
- text_column = MysqlColumn.new("title", nil, "text")
+ text_column = MysqlAdapter::Column.new("title", nil, "text")
assert_equal nil, text_column.default
- not_null_text_column = MysqlColumn.new("title", nil, "text", false)
+ not_null_text_column = MysqlAdapter::Column.new("title", nil, "text", false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blog_and_test_data_types
- blob_column = MysqlColumn.new("title", nil, "blob")
+ blob_column = MysqlAdapter::Column.new("title", nil, "blob")
assert !blob_column.has_default?
- text_column = MysqlColumn.new("title", nil, "text")
+ text_column = MysqlAdapter::Column.new("title", nil, "text")
assert !text_column.has_default?
end
end
if current_adapter?(:Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types
- binary_column = Mysql2Column.new("title", "a", "binary(1)")
+ binary_column = Mysql2Adapter::Column.new("title", "a", "binary(1)")
assert_equal "a", binary_column.default
- varbinary_column = Mysql2Column.new("title", "a", "varbinary(1)")
+ varbinary_column = Mysql2Adapter::Column.new("title", "a", "varbinary(1)")
assert_equal "a", varbinary_column.default
end
def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do
- Mysql2Column.new("title", "a", "blob")
+ Mysql2Adapter::Column.new("title", "a", "blob")
end
assert_raise ArgumentError do
- Mysql2Column.new("title", "Hello", "text")
+ Mysql2Adapter::Column.new("title", "Hello", "text")
end
- text_column = Mysql2Column.new("title", nil, "text")
+ text_column = Mysql2Adapter::Column.new("title", nil, "text")
assert_equal nil, text_column.default
- not_null_text_column = Mysql2Column.new("title", nil, "text", false)
+ not_null_text_column = Mysql2Adapter::Column.new("title", nil, "text", false)
assert_equal "", not_null_text_column.default
end
def test_has_default_should_return_false_for_blog_and_test_data_types
- blob_column = Mysql2Column.new("title", nil, "blob")
+ blob_column = Mysql2Adapter::Column.new("title", nil, "blob")
assert !blob_column.has_default?
- text_column = Mysql2Column.new("title", nil, "text")
+ text_column = Mysql2Adapter::Column.new("title", nil, "text")
assert !text_column.has_default?
end
end
diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb
index ad17f6f83a..e3ad0cad90 100644
--- a/activerecord/test/cases/query_cache_test.rb
+++ b/activerecord/test/cases/query_cache_test.rb
@@ -13,6 +13,32 @@ class QueryCacheTest < ActiveRecord::TestCase
ActiveRecord::Base.connection.disable_query_cache!
end
+ def test_exceptional_middleware_clears_and_disables_cache_on_error
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ Task.find 1
+ Task.find 1
+ assert_equal 1, ActiveRecord::Base.connection.query_cache.length
+ raise "lol borked"
+ }
+ assert_raises(RuntimeError) { mw.call({}) }
+
+ assert_equal 0, ActiveRecord::Base.connection.query_cache.length
+ assert !ActiveRecord::Base.connection.query_cache_enabled, 'cache off'
+ end
+
+ def test_exceptional_middleware_leaves_enabled_cache_alone
+ ActiveRecord::Base.connection.enable_query_cache!
+
+ mw = ActiveRecord::QueryCache.new lambda { |env|
+ raise "lol borked"
+ }
+ assert_raises(RuntimeError) { mw.call({}) }
+
+ assert ActiveRecord::Base.connection.query_cache_enabled, 'cache on'
+ end
+
def test_middleware_delegates
called = false
mw = ActiveRecord::QueryCache.new lambda { |env|
@@ -213,4 +239,4 @@ class QueryCacheBodyProxyTest < ActiveRecord::TestCase
assert_equal proxy.to_path, "/path"
end
-end \ No newline at end of file
+end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 615551a279..da96afd718 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1018,7 +1018,7 @@ class RelationTest < ActiveRecord::TestCase
end
def test_update_all_with_joins_and_offset_and_order
- all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id')
+ all_comments = Comment.joins(:post).where('posts.id' => posts(:welcome).id).order('posts.id', 'comments.id')
count = all_comments.count
comments = all_comments.offset(1)
diff --git a/activerecord/test/models/toy.rb b/activerecord/test/models/toy.rb
index 79a88db0da..6c45e99671 100644
--- a/activerecord/test/models/toy.rb
+++ b/activerecord/test/models/toy.rb
@@ -1,4 +1,6 @@
class Toy < ActiveRecord::Base
set_primary_key :toy_id
belongs_to :pet
+
+ scope :with_pet, joins(:pet)
end
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 1697d346b5..3508ec0f34 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -20,6 +20,18 @@ Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. [Akira
*Rails 3.1.0 (unreleased)*
+* ActiveSupport::Dependencies#load and ActiveSupport::Dependencies#require now
+return the value from `super` [Aaron Patterson]
+
+* Fixed ActiveSupport::Gzip to work properly in Ruby 1.8 [Guillermo Iguaran]
+
+* Kernel.require_library_or_gem was deprecated and will be removed in Rails 3.2.0 [Josh Kalderimis]
+
+* ActiveSupport::Duration#duplicable? was fixed for Ruby 1.8 [thedarkone]
+
+* ActiveSupport::BufferedLogger set log encoding to BINARY, but still use text
+mode to output portable newlines. [fxn]
+
* ActiveSupport::Dependencies now raises NameError if it finds an existing constant in load_missing_constant. This better reflects the nature of the error which is usually caused by calling constantize on a nested constant. [Andrew White]
* Deprecated ActiveSupport::SecureRandom in favour of SecureRandom from the standard library [Jon Leighton]
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index 76453f4e9a..6ed76974b4 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -12,6 +12,19 @@
*Rails 3.1.0 (unreleased)*
+* The default database schema file is written as UTF-8. [Aaron Patterson]
+
+* Generated apps with --dev or --edge flags depend on git versions of
+sass-rails and coffee-rails. [Santiago Pastorino]
+
+* Rack::Sendfile middleware is used only if x_sendfile_header is present. [Santiago Pastorino]
+
+* Add JavaScript Runtime name to the Rails Info properties. [DHH]
+
+* Make pp enabled by default in Rails console. [Akira Matsuda]
+
+* Add alias `r` for rails runner. [Jordi Romero]
+
* Make sprockets/railtie require explicit and add --skip-sprockets to app generator [José Valim]
* Added Rails.groups that automatically handles Rails.env and ENV["RAILS_GROUPS"] [José Valim]
diff --git a/railties/README.rdoc b/railties/README.rdoc
index eb7ed961e3..501541eb06 100644
--- a/railties/README.rdoc
+++ b/railties/README.rdoc
@@ -1,12 +1,12 @@
= Railties -- Gluing the Engine to the Rails
-Railties is responsible to glue all frameworks together. Overall, it:
+Railties is responsible for gluing all frameworks together. Overall, it:
-* handles all the bootstrapping process for a Rails application;
+* handles the bootstrapping process for a Rails application;
-* manages rails command line interface;
+* manages the +rails+ command line interface;
-* provides Rails generators core;
+* and provides Rails generators core.
== Download
diff --git a/railties/guides/source/3_1_release_notes.textile b/railties/guides/source/3_1_release_notes.textile
index 087926f98d..00765239d6 100644
--- a/railties/guides/source/3_1_release_notes.textile
+++ b/railties/guides/source/3_1_release_notes.textile
@@ -170,7 +170,7 @@ class PostsController < ActionController::Base
end
</ruby>
-You can restrict it to some actions by using +:only+ or +:except+. Please read the docs at "<tt>ActionController::Streaming</tt>":http://edgeapi.rubyonrails.org/classes/ActionController/Streaming.html for more information.
+You can restrict it to some actions by using +:only+ or +:except+. Please read the docs at "<tt>ActionController::Streaming</tt>":http://api.rubyonrails.org/classes/ActionController/Streaming.html for more information.
* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused.
@@ -346,6 +346,7 @@ has_many :things, :conditions => proc { "foo = #{bar}" } # after
Inside the proc, +self+ is the object which is the owner of the association, unless you are eager loading the association, in which case +self+ is the class which the association is within.
You can have any "normal" conditions inside the proc, so the following will work too:
+
<ruby>
has_many :things, :conditions => proc { ["foo = ?", bar] }
</ruby>
@@ -353,6 +354,7 @@ has_many :things, :conditions => proc { ["foo = ?", bar] }
* Previously +:insert_sql+ and +:delete_sql+ on +has_and_belongs_to_many+ association allowed you to call 'record' to get the record being inserted or deleted. This is now passed as an argument to the proc.
* Added <tt>ActiveRecord::Base#has_secure_password</tt> (via <tt>ActiveModel::SecurePassword</tt>) to encapsulate dead-simple password usage with BCrypt encryption and salting.
+
<ruby>
# Schema: User(name:string, password_digest:string, password_salt:string)
class User < ActiveRecord::Base
diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile
index 4fbdda4c07..96b11dbf63 100644
--- a/railties/guides/source/asset_pipeline.textile
+++ b/railties/guides/source/asset_pipeline.textile
@@ -1,6 +1,6 @@
h2. Asset Pipeline
-This guide covers the ideology of the asset pipeline introduced in Rails 3.1.
+This guide covers the asset pipeline introduced in Rails 3.1.
By referring to this guide you will be able to:
* Understand what the asset pipeline is and what it does
@@ -15,7 +15,7 @@ h3. What is the Asset Pipeline?
The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages such as CoffeeScript, SCSS and ERB.
-Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 includes the +sprockets-rails+ gem, which depends on the +sprockets+ gem, by default.
+Prior to Rails 3.1 these features were added through third-party Ruby libraries such as Jammit and Sprockets. Rails 3.1 is integrated with Sprockets throught ActionPack which depends on the +sprockets+ gem, by default.
By having this as a core feature of Rails, all developers can benefit from the power of having their assets pre-processed, compressed and minified by one central library, Sprockets. This is part of Rails' "Fast by default" strategy as outlined by DHH in his 2011 keynote at Railsconf.
@@ -27,9 +27,10 @@ config.assets.enabled = false
It is recommended that you use the defaults for all new apps.
+
h4. Main Features
-The first feature of the pipeline is to concatenate assets. This is important in a production environment, as it reduces the number of requests that a browser must make to render a web page. While Rails already has a feature to concatenate these types of assetsi -- by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+ -- many people do not use it.
+The first feature of the pipeline is to concatenate assets. This is important in a production environment, as it reduces the number of requests that a browser must make to render a web page. While Rails already has a feature to concatenate these types of assets -- by placing +:cache => true+ at the end of tags such as +javascript_include_tag+ and +stylesheet_link_tag+ -- many people do not use it.
The default behavior in Rails 3.1 and onward is to concatenate all files into one master file each for JS and CSS. However, you can separate files or groups of files if required (see below). In production, an MD5 fingerprint is inserted into each filename so that the file is cached by the web browser but can be invalidated if the fingerprint is altered.
@@ -74,11 +75,14 @@ The other problem is that when static assets are deployed with each new release
Fingerprinting avoids all these problems by ensuring filenames are consistent based on their content.
+Fingerprinting is enabled by default for production and disabled for all the others environments. You can enable or disable it in your configuration through the +config.assets.digest+ option.
+
More reading:
* "Optimize caching":http://code.google.com/speed/page-speed/docs/caching.html
* "Revving Filenames: don’t use querystring":http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
+
h3. How to Use the Asset Pipeline
In previous versions of Rails, all assets were located in subdirectories of +public+ such as +images+, +javascripts+ and +stylesheets+. With the asset pipeline, the preferred location for these assets is now the +app/assets+ directory. Files in this directory are served by the Sprockets middleware included in the sprockets gem.
@@ -133,7 +137,7 @@ Otherwise, Sprockets looks through the available paths until it finds a file tha
If you want to use a "css data URI":http://en.wikipedia.org/wiki/Data_URI_scheme -- a method of embedding the image data directly into the CSS file -- you can use the +asset_data_uri+ helper.
<plain>
-#logo { background: url(<%= asset_data_uri 'logo.png' %>)
+#logo { background: url(<%= asset_data_uri 'logo.png' %>) }
</plain>
This inserts a correctly-formatted data URI into the CSS source.
@@ -143,7 +147,7 @@ h5. CSS and ERB
If you add an +erb+ extension to a CSS asset, making it something such as +application.css.erb+, then you can use the +asset_path+ helper in your CSS rules:
<plain>
-.class{background-image:<%= asset_path 'image.png' %>}
+.class { background-image: <%= asset_path 'image.png' %> }
</plain>
This writes the path to the particular asset being referenced. In this example, it would make sense to have an image in one of the asset load paths, such as +app/assets/images/image.png+, which would be referenced here. If this image is already available in +public/assets+ as a fingerprinted file, then that path is referenced.
@@ -247,14 +251,7 @@ When the +debug_assets+ parameter is set, this line is expanded out into three s
This allows the individual parts of an asset to be rendered and debugged separately.
-Additionally if the +config.assets.debug+ is set to true you can debug your assets passing the +:debug+ option to the assets tags:
-
-<erb>
-<%= javascript_include_tag :application, :debug => true %>
-</erb>
-
-
-NOTE. Assets debugging is turned on by default in development and test environments. You can set +config.assets.allow_debugging+ to false to turn it off.
+NOTE. Assets debugging is turned on by default in development and test environments.
h3. In Production
@@ -271,7 +268,7 @@ The MD5 is generated from the contents of the compiled files, and is included in
Sprockets also sets the +Cache-Control+ HTTP header to +max-age=31536000+. This signals all caches between your server and the client browser that this content (the file served) can be cached for 1 year. The effect of this is to reduce the number of requests for this asset from your server; the asset has a good chance of being in the local browser cache or some intermediate cache.
-This behavior is controlled by the setting of +config.action_controller.perform_caching+ setting in Rails (which is +true+ for production, +false+ for everything else). This value is propagated to Sprockets during initialization for use when action_controller is not available.
+This behavior is controlled by the setting of +config.assets.digest+ setting in Rails (which is +true+ for production, +false+ for everything else).
h4. Precompiling Assets
@@ -282,7 +279,7 @@ Rails comes bundled with a rake task to compile the manifests to files on disc.
The rake task is:
<plain>
-rake assets:precompile
+bundle exec rake assets:precompile
</plain>
Capistrano (v2.8.0+) has a recipe to handle this in deployment. Add the following line to +Capfile+:
@@ -307,6 +304,20 @@ If you have other manifests or individual stylesheets and JavaScript files to in
config.assets.precompile += ['admin.js', 'admin.css', 'swfObject.js']
</erb>
+The rake task also generates a +manifest.yml+ that contains a list with all your assets and their fingerprints, using this manifest file the assets helpers avoid hitting to Sprockets to recalculate MD5 hashes for files. Manifest file typically look like this:
+
+<plain>
+---
+rails.png: rails-bd9ad5a560b5a3a7be0808c5cd76a798.png
+jquery-ui.min.js: jquery-ui-7e33882a28fc84ad0e0e47e46cbf901c.min.js
+jquery.min.js: jquery-8a50feed8d29566738ad005e19fe1c2d.min.js
+application.js: application-3fdab497b8fb70d20cfc5495239dfc29.js
+application.css: application-8af74128f904600e41a6e39241464e03.css
+</plain>
+
+The manifest file is generated by default in same folder of your precompiled assets, you can change the location of the file setting the +config.assets.manifest+ option with the full path of the folder where you want save it.
+
+
Precompiled assets exist on the filesystem and are served directly by your webserver. They do not have far-future headers by default, so to get the benefit of fingerprinting you'll have to update your server configuration to add them.
For Apache:
@@ -325,12 +336,16 @@ For Apache:
</LocationMatch>
</plain>
-TODO: NGINX instructions
+TODO: nginx instructions
When files are precompiled, Sprockets also creates a "Gzip":http://en.wikipedia.org/wiki/Gzip (.gz) version of your assets. This avoids the server having to do this for any requests; it can simply read the compressed files from disc. You must configure your server to use gzip compression and serve the compressed assets that will be stored in the public/assets folder. The following configuration options can be used:
TODO: Apache instructions
+TODO: nginx instructions
+
+By default Rails assumes that you have your files precompiled in the production environment, if you want use live compiling (compile your assets during runtime) in production you must set the +config.assets.compile+ to true. You can use this option to fallback to Sprockets when you are using precompiled assets but there are any missing precompiled files. If +config.assets.compile+ option is set to false and there are missing precompiled files you will get an "AssetNoPrecompiledError" indicating the name of the missing file.
+
h3. Customizing the Pipeline
@@ -378,6 +393,7 @@ To enable this, pass a +new+ Object to the config option in +application.rb+:
config.assets.css_compressor = Transformer.new
</erb>
+
h4. Changing the _assets_ Path
The public path that Sprockets uses by default is +/assets+.
diff --git a/railties/guides/source/index.html.erb b/railties/guides/source/index.html.erb
index 684251962c..214155c088 100644
--- a/railties/guides/source/index.html.erb
+++ b/railties/guides/source/index.html.erb
@@ -131,6 +131,10 @@ Ruby on Rails Guides
<%= guide("Caching with Rails", 'caching_with_rails.html', :work_in_progress => true) do %>
<p>Various caching techniques provided by Rails.</p>
<% end %>
+
+<%= guide('Asset Pipeline', 'asset_pipeline.html') do %>
+ <p>This guide documents the asset pipeline.</p>
+<% end %>
</dl>
<h3>Extending Rails</h3>
@@ -170,6 +174,10 @@ Ruby on Rails Guides
<h3>Release Notes</h3>
<dl>
+<%= guide("Ruby on Rails 3.1 Release Notes", '3_1_release_notes.html') do %>
+ <p>Release notes for Rails 3.1.</p>
+<% end %>
+
<%= guide("Ruby on Rails 3.0 Release Notes", '3_0_release_notes.html') do %>
<p>Release notes for Rails 3.0.</p>
<% end %>
diff --git a/railties/guides/source/layout.html.erb b/railties/guides/source/layout.html.erb
index 5dcac8e74c..3ccbc3a477 100644
--- a/railties/guides/source/layout.html.erb
+++ b/railties/guides/source/layout.html.erb
@@ -5,7 +5,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
-<title><%= yield(:page_title) || 'Ruby on Rails guides' %></title>
+<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
@@ -71,6 +71,7 @@
<dd><a href="configuring.html">Configuring Rails Applications</a></dd>
<dd><a href="command_line.html">Rails Command Line Tools and Rake Tasks</a></dd>
<dd><a href="caching_with_rails.html">Caching with Rails</a></dd>
+ <dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dt>Extending Rails</dt>
<dd><a href="plugins.html">The Basics of Creating Rails Plugins</a></dd>
@@ -83,6 +84,7 @@
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails Guides Guidelines</a></dd>
<dt>Release Notes</dt>
+ <dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 Release Notes</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 Release Notes</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 Release Notes</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 Release Notes</a></dd>