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/action_view/helpers/form_tag_helper.rb9
-rw-r--r--actionpack/lib/sprockets/compressors.rb30
-rw-r--r--actionpack/lib/sprockets/helpers/rails_helper.rb14
-rw-r--r--actionpack/test/template/form_tag_helper_test.rb6
-rw-r--r--activemodel/CHANGELOG2
-rw-r--r--activemodel/lib/active_model/errors.rb8
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb2
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb2
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/lib/active_model/validations/presence.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb18
-rw-r--r--activemodel/lib/active_model/validations/with.rb6
-rw-r--r--activemodel/test/cases/validations_test.rb33
-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/connection_adapters/sqlite_adapter.rb21
-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/adapters/sqlite3/sqlite3_adapter_test.rb16
-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/models/toy.rb2
-rw-r--r--activesupport/CHANGELOG14
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/object.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/object/public_send.rb25
-rw-r--r--activesupport/test/core_ext/module_test.rb20
-rw-r--r--activesupport/test/core_ext/object/public_send_test.rb117
-rw-r--r--railties/CHANGELOG17
-rw-r--r--railties/guides/source/active_model_basics.textile8
-rw-r--r--railties/guides/source/active_support_core_extensions.textile24
-rw-r--r--railties/guides/source/asset_pipeline.textile17
-rw-r--r--railties/lib/rails/plugin.rb4
-rw-r--r--railties/test/application/asset_debugging_test.rb56
-rw-r--r--railties/test/application/assets_test.rb42
60 files changed, 1158 insertions, 1519 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..59d5bbc5ed 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.beta.15')
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/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 9ed4611123..1ceb53fe9c 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -177,9 +177,12 @@ module ActionView
# label_tag 'name', nil, :class => 'small_label'
# # => <label for="name" class="small_label">Name</label>
def label_tag(name = nil, content_or_options = nil, options = nil, &block)
- options = content_or_options if block_given? && content_or_options.is_a?(Hash)
- options ||= {}
- options.stringify_keys!
+ if block_given? && content_or_options.is_a?(Hash)
+ options = content_or_options = content_or_options.stringify_keys
+ else
+ options ||= {}
+ options = options.stringify_keys
+ end
options["for"] = sanitize_to_id(name) unless name.blank? || options.has_key?("for")
content_tag :label, content_or_options || name.to_s.humanize, options, &block
end
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 53cc5ec019..7a2bf8bef6 100644
--- a/actionpack/lib/sprockets/helpers/rails_helper.rb
+++ b/actionpack/lib/sprockets/helpers/rails_helper.rb
@@ -26,10 +26,10 @@ 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.stringify_keys.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.stringify_keys))
+ super(source.to_s, { :src => asset_path(source, 'js', body) }.merge!(options))
end
end.join("\n").html_safe
end
@@ -42,10 +42,10 @@ 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.stringify_keys.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.stringify_keys))
+ super(source.to_s, { :href => asset_path(source, 'css', body, :request) }.merge!(options))
end
end.join("\n").html_safe
end
@@ -58,10 +58,8 @@ module Sprockets
private
def debug_assets?
- Rails.application.config.assets.allow_debugging &&
- (Rails.application.config.assets.debug ||
- params[:debug_assets] == '1' ||
- params[:debug_assets] == 'true')
+ config = Rails.application.config.assets
+ config.allow_debugging && (config.debug || params[:debug_assets])
end
# Override to specify an alternative prefix for asset path generation.
diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb
index eb569c7308..6eae9bf846 100644
--- a/actionpack/test/template/form_tag_helper_test.rb
+++ b/actionpack/test/template/form_tag_helper_test.rb
@@ -530,6 +530,12 @@ class FormTagHelperTest < ActionView::TestCase
assert_equal options, { :option => "random_option" }
end
+ def test_image_label_tag_options_symbolize_keys_side_effects
+ options = { :option => "random_option" }
+ label_tag "submit source", "title", options
+ assert_equal options, { :option => "random_option" }
+ end
+
def protect_against_forgery?
false
end
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
index 9b7d2d026d..20e5816532 100644
--- a/activemodel/CHANGELOG
+++ b/activemodel/CHANGELOG
@@ -1,3 +1,5 @@
+* Add ability to define strict validation(with :strict => true option) that always raises exception when fails [Bogdan Gusiev]
+
* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" [Grant Hutchins, Peter Jaros]
* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev]
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 36819553ee..843c0c3cb5 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -63,7 +63,7 @@ module ActiveModel
class Errors
include Enumerable
- CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
attr_reader :messages
@@ -218,6 +218,9 @@ module ActiveModel
elsif message.is_a?(Proc)
message = message.call
end
+ if options[:strict]
+ raise ActiveModel::StrictValidationFailed, message
+ end
self[attribute] << message
end
@@ -319,4 +322,7 @@ module ActiveModel
I18n.translate(key, options)
end
end
+
+ class StrictValidationFailed < StandardError
+ end
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 01907ac9da..e628c6f306 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -58,6 +58,8 @@ module ActiveModel
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or
# false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index a9dcb0b505..6573a7d264 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -58,6 +58,8 @@ module ActiveModel
# <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index d3b8d31502..644cc814a7 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -59,6 +59,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 090e8cfbae..d3faa8c6a6 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -84,6 +84,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 9a9270d615..147e2ecb69 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -59,6 +59,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 144e73904e..eb7aac709d 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -96,6 +96,8 @@ module ActiveModel
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to
# count words as in above example.)
# Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 0d1903362c..34d447a0fa 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -107,6 +107,8 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which corresponds to a method:
# * <tt>:greater_than</tt>
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index cfb4c33dcc..35af7152db 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -35,6 +35,8 @@ module ActiveModel
# * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
#
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 7ff42de00b..b85c2453fb 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -70,8 +70,8 @@ module ActiveModel
# validator's initializer as +options[:in]+ while other types including
# regular expressions and strings are passed as +options[:with]+
#
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+ and +:allow_nil+ can be given
- # to one specific validator, as a hash:
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
+ # can be given to one specific validator, as a hash:
#
# validates :password, :presence => { :if => :password_required? }, :confirmation => true
#
@@ -101,12 +101,24 @@ module ActiveModel
end
end
+ # This method is used to define validation that can not be corrected by end user
+ # and is considered exceptional.
+ # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt>
+ # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error
+ # when validation fails
+ # See <tt>validates</tt> for more information about validation itself.
+ def validates!(*attributes)
+ options = attributes.extract_options!
+ options[:strict] = true
+ validates(*(attributes << options))
+ end
+
protected
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
def _validates_default_keys
- [ :if, :unless, :on, :allow_blank, :allow_nil ]
+ [ :if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
def _parse_validates_options(options) #:nodoc:
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index a87b213fe4..83aae206a6 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -61,7 +61,9 @@ module ActiveModel
# (e.g. <tt>:unless => :skip_validation</tt>, or
# <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
# The method, proc or string should return or evaluate to a true or false value.
- #
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
+
# If you pass any additional configuration options, they will be passed
# to the class and available as <tt>options</tt>:
#
@@ -140,4 +142,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 0b50acf913..2f4376bd41 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -297,4 +297,37 @@ class ValidationsTest < ActiveModel::TestCase
assert auto.valid?
end
+
+ def test_strict_validation_in_validates
+ Topic.validates :title, :strict => true, :presence => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_strict_validation_not_fails
+ Topic.validates :title, :strict => true, :presence => true
+ assert Topic.new(:title => "hello").valid?
+ end
+
+ def test_strict_validation_particular_validator
+ Topic.validates :title, :presence => {:strict => true}
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_strict_validation_in_custom_validator_helper
+ Topic.validates_presence_of :title, :strict => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_validates_with_bang
+ Topic.validates! :title, :presence => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
end
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/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
index da86957028..1996e49296 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb
@@ -161,10 +161,25 @@ module ActiveRecord
end
end
- def type_cast(value, column) # :nodoc:
- return super unless BigDecimal === value
+ if "<3".encoding_aware?
+ def type_cast(value, column) # :nodoc:
+ return value.to_f if BigDecimal === value
+ return super unless String === value
+ return super unless column && value
+
+ value = super
+ if column.type == :string && value.encoding == Encoding::ASCII_8BIT
+ @logger.error "Binary data inserted for `string` type on column `#{column.name}`"
+ value.encode! 'utf-8'
+ end
+ value
+ end
+ else
+ def type_cast(value, column) # :nodoc:
+ return super unless BigDecimal === value
- value.to_f
+ value.to_f
+ end
end
# DATABASE STATEMENTS ======================================
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/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index 6ff04e3eb3..2b598220ee 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require "cases/helper"
+require 'models/owner'
module ActiveRecord
module ConnectionAdapters
@@ -19,6 +20,21 @@ module ActiveRecord
eosql
end
+ def test_column_types
+ return skip('only test encoding on 1.9') unless "<3".encoding_aware?
+
+ owner = Owner.create!(:name => "hello".encode('ascii-8bit'))
+ owner.reload
+ select = Owner.columns.map { |c| "typeof(#{c.name})" }.join ', '
+ result = Owner.connection.exec_query <<-esql
+ SELECT #{select}
+ FROM #{Owner.table_name}
+ WHERE #{Owner.primary_key} = #{owner.id}
+ esql
+
+ assert(!result.rows.first.include?("blob"), "should not store blobs")
+ end
+
def test_exec_insert
column = @conn.columns('items').find { |col| col.name == 'number' }
vals = [[column, 10]]
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/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 dba914da48..3508ec0f34 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -2,8 +2,6 @@
* Added Array#prepend as an alias for Array#unshift and Array#append as an alias for Array#<< [DHH]
-* Removed support for using Module#delegate to delegate to non-public methods [Jon Leighton]
-
* The definition of blank string for Ruby 1.9 has been extended to Unicode whitespace.
Also, in 1.8 the ideographic space U+3000 is considered to be whitespace. [Akira Matsuda, Damien Mathieu]
@@ -22,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/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 8350753f78..7de824a77f 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -1,6 +1,3 @@
-require 'active_support/core_ext/object/public_send'
-require 'active_support/core_ext/string/starts_ends_with'
-
class Module
# Provides a delegate class method to easily expose contained objects' methods
# as your own. Pass one or more methods (specified as symbols or strings)
@@ -127,13 +124,12 @@ class Module
methods.each do |method|
method = method.to_s
- call = method.ends_with?('=') ? "public_send(:#{method}, " : "#{method}("
if allow_nil
module_eval(<<-EOS, file, line - 2)
def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
- #{to}.#{call}*args, &block) # client.name(*args, &block)
+ #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
end # end
end # end
EOS
@@ -142,7 +138,7 @@ class Module
module_eval(<<-EOS, file, line - 1)
def #{method_prefix}#{method}(*args, &block) # def customer_name(*args, &block)
- #{to}.#{call}*args, &block) # client.name(*args, &block)
+ #{to}.__send__(:#{method}, *args, &block) # client.__send__(:name, *args, &block)
rescue NoMethodError # rescue NoMethodError
if #{to}.nil? # if client.nil?
#{exception} # # add helpful message to the exception
diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb
index 249c2e93c5..9ad1e12699 100644
--- a/activesupport/lib/active_support/core_ext/object.rb
+++ b/activesupport/lib/active_support/core_ext/object.rb
@@ -3,7 +3,6 @@ require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/duplicable'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/inclusion'
-require 'active_support/core_ext/object/public_send'
require 'active_support/core_ext/object/conversions'
require 'active_support/core_ext/object/instance_variables'
diff --git a/activesupport/lib/active_support/core_ext/object/public_send.rb b/activesupport/lib/active_support/core_ext/object/public_send.rb
deleted file mode 100644
index 2e77a22c4b..0000000000
--- a/activesupport/lib/active_support/core_ext/object/public_send.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'active_support/core_ext/kernel/singleton_class'
-
-class Object
- unless Object.public_method_defined?(:public_send)
- # Backports Object#public_send from 1.9
- def public_send(method, *args, &block)
- # Don't create a singleton class for the object if it doesn't already have one
- # (This also protects us from classes like Fixnum and Symbol, which cannot have a
- # singleton class.)
- klass = singleton_methods.any? ? self.singleton_class : self.class
-
- if klass.public_method_defined?(method)
- send(method, *args, &block)
- else
- if klass.private_method_defined?(method)
- raise NoMethodError, "private method `#{method}' called for #{inspect}"
- elsif klass.protected_method_defined?(method)
- raise NoMethodError, "protected method `#{method}' called for #{inspect}"
- else
- raise NoMethodError, "undefined method `#{method}' for #{inspect}"
- end
- end
- end
- end
-end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index d4ce81fdfa..449d3810e2 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -28,20 +28,10 @@ end
Somewhere = Struct.new(:street, :city) do
attr_accessor :name
-
- protected
-
- def protected_method
- end
-
- private
-
- def private_method
- end
end
class Someone < Struct.new(:name, :place)
- delegate :street, :city, :to_f, :protected_method, :private_method, :to => :place
+ delegate :street, :city, :to_f, :to => :place
delegate :name=, :to => :place, :prefix => true
delegate :upcase, :to => "place.city"
@@ -93,14 +83,6 @@ class ModuleTest < Test::Unit::TestCase
assert_equal "Fred", @david.place.name
end
- def test_delegation_to_protected_method
- assert_raise(NoMethodError) { @david.protected_method }
- end
-
- def test_delegation_to_private_method
- assert_raise(NoMethodError) { @david.private_method }
- end
-
def test_delegation_down_hierarchy
assert_equal "CHICAGO", @david.upcase
end
diff --git a/activesupport/test/core_ext/object/public_send_test.rb b/activesupport/test/core_ext/object/public_send_test.rb
deleted file mode 100644
index 7dc542e51c..0000000000
--- a/activesupport/test/core_ext/object/public_send_test.rb
+++ /dev/null
@@ -1,117 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/object/public_send'
-
-module PublicSendReceiver
- def receive_public_method(*args)
- return args + [yield]
- end
-
- protected
-
- def receive_protected_method(*args)
- return args + [yield]
- end
-
- private
-
- def receive_private_method(*args)
- return args + [yield]
- end
-end
-
-# Note, running this on 1.9 will be testing the Ruby core implementation, but it is good to
-# do this to ensure that our backport functions the same as Ruby core in 1.9
-class PublicSendTest < Test::Unit::TestCase
- def instance
- @instance ||= begin
- klass = Class.new do
- include PublicSendReceiver
- end
- klass.new
- end
- end
-
- def singleton_instance
- @singleton_instance ||= begin
- object = Object.new
- object.singleton_class.send(:include, PublicSendReceiver)
- object
- end
- end
-
- def test_should_receive_public_method
- assert_equal(
- [:foo, :bar, :baz],
- instance.public_send(:receive_public_method, :foo, :bar) { :baz }
- )
- end
-
- def test_should_receive_public_singleton_method
- assert_equal(
- [:foo, :bar, :baz],
- singleton_instance.public_send(:receive_public_method, :foo, :bar) { :baz }
- )
- end
-
- def test_should_raise_on_protected_method
- assert_raises(NoMethodError) do
- instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
- end
- end
-
- def test_should_raise_on_protected_singleton_method
- assert_raises(NoMethodError) do
- singleton_instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
- end
- end
-
- def test_should_raise_on_private_method
- assert_raises(NoMethodError) do
- instance.public_send(:receive_private_method, :foo, :bar) { :baz }
- end
- end
-
- def test_should_raise_on_singleton_private_method
- assert_raises(NoMethodError) do
- singleton_instance.public_send(:receive_private_method, :foo, :bar) { :baz }
- end
- end
-
- def test_should_raise_on_undefined_method
- assert_raises(NoMethodError) do
- instance.public_send(:receive_undefined_method, :foo, :bar) { :baz }
- end
- end
-
- def test_protected_method_message
- instance.public_send(:receive_protected_method, :foo, :bar) { :baz }
- rescue NoMethodError => exception
- assert_equal(
- "protected method `receive_protected_method' called for #{instance.inspect}",
- exception.message
- )
- end
-
- def test_private_method_message
- instance.public_send(:receive_private_method, :foo, :bar) { :baz }
- rescue NoMethodError => exception
- assert_equal(
- "private method `receive_private_method' called for #{instance.inspect}",
- exception.message
- )
- end
-
- def test_undefined_method_message
- instance.public_send(:receive_undefined_method, :foo, :bar) { :baz }
- rescue NoMethodError => exception
- assert_equal(
- "undefined method `receive_undefined_method' for #{instance.inspect}",
- exception.message
- )
- end
-
- def test_receiver_with_no_singleton
- assert_equal "5", 5.public_send(:to_s)
- assert_equal "foo", :foo.public_send(:to_s)
- end
-end
diff --git a/railties/CHANGELOG b/railties/CHANGELOG
index a9b4deb551..6ed76974b4 100644
--- a/railties/CHANGELOG
+++ b/railties/CHANGELOG
@@ -1,5 +1,9 @@
*Rails 3.2.0 (unreleased)*
+* Added destroy alias to Rails engines. [Guillermo Iguaran]
+
+* Added destroy alias for Rails command line. This allows the following: `rails d model post`. [Andrey Ognevsky]
+
* Attributes on scaffold and model generators default to string. This allows the following: "rails g scaffold Post title body:text author" [José Valim]
* Removed old plugin generator (`rails generate plugin`) in favor of `rails plugin new` command. [Guillermo Iguaran]
@@ -8,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/guides/source/active_model_basics.textile b/railties/guides/source/active_model_basics.textile
index 3c19fb5177..0672669dc5 100644
--- a/railties/guides/source/active_model_basics.textile
+++ b/railties/guides/source/active_model_basics.textile
@@ -183,22 +183,26 @@ Validations module adds the ability to class objects to validate them in Active
class Person
include ActiveModel::Validations
- attr_accessor :name, :email
+ attr_accessor :name, :email, :token
validates :name, :presence => true
validates_format_of :email, :with => /^([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})$/i
+ validates! :token, :presence => true
end
-person = Person.new
+person = Person.new(:token => "2b1f325")
person.valid? #=> false
person.name = 'vishnu'
person.email = 'me'
person.valid? #=> false
person.email = 'me@vishnuatrai.com'
person.valid? #=> true
+person.token = nil
+person.valid? #=> raises ActiveModel::StrictValidationFailed
</ruby>
h3. Changelog
+* August 24, 2011: Add strict validation usage example. "Bogdan Gusiev":http://gusiev.com
* August 5, 2011: Initial version by "Arun Agrawal":http://github.com/arunagw
diff --git a/railties/guides/source/active_support_core_extensions.textile b/railties/guides/source/active_support_core_extensions.textile
index df863935cf..b2436a2e68 100644
--- a/railties/guides/source/active_support_core_extensions.textile
+++ b/railties/guides/source/active_support_core_extensions.textile
@@ -452,30 +452,6 @@ Examples of +in?+:
NOTE: Defined in +active_support/core_ext/object/inclusion.rb+.
-h4. +public_send+
-
-This method is available by default in Ruby 1.9, and is backported to Ruby 1.8 by Active Support. Like the regular +send+ method, +public_send+ allows you to call a method when the name is not known until runtime. However, if the method is not public then a +NoMethodError+ exception will be raised.
-
-<ruby>
-class Greeter
- def hello(who)
- "Hello " + who
- end
-
- private
-
- def secret
- "sauce"
- end
-end
-
-greeter = Greeter.new
-greeter.public_send(:hello, 'Jim') # => "Hello Jim"
-greeter.public_send(:secret) # => NoMethodError
-</ruby>
-
-NOTE: Defined in +active_support/core_ext/object/public_send.rb+.
-
h3. Extensions to +Module+
h4. +alias_method_chain+
diff --git a/railties/guides/source/asset_pipeline.textile b/railties/guides/source/asset_pipeline.textile
index 012149c40e..554246acb3 100644
--- a/railties/guides/source/asset_pipeline.textile
+++ b/railties/guides/source/asset_pipeline.textile
@@ -29,7 +29,7 @@ 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.
@@ -133,7 +133,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 +143,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,7 +247,14 @@ 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.
-NOTE. Assets debugging is turned on by default in development and test environments.
+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.
h3. In Production
@@ -275,7 +282,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+:
diff --git a/railties/lib/rails/plugin.rb b/railties/lib/rails/plugin.rb
index 4988cc3378..3e27688bb9 100644
--- a/railties/lib/rails/plugin.rb
+++ b/railties/lib/rails/plugin.rb
@@ -74,8 +74,8 @@ module Rails
initializer :load_init_rb, :before => :load_config_initializers do |app|
init_rb = File.expand_path("init.rb", root)
if File.file?(init_rb)
- # FIXME: do we call this for side effects??
- app.config
+ # This double assignment is to prevent an "unused variable" warning on Ruby 1.9.3.
+ config = config = app.config
# TODO: think about evaling initrb in context of Engine (currently it's
# always evaled in context of Rails::Application)
eval(File.read(init_rb), binding, init_rb)
diff --git a/railties/test/application/asset_debugging_test.rb b/railties/test/application/asset_debugging_test.rb
new file mode 100644
index 0000000000..38e1e21d17
--- /dev/null
+++ b/railties/test/application/asset_debugging_test.rb
@@ -0,0 +1,56 @@
+require 'isolation/abstract_unit'
+require 'rack/test'
+
+module ApplicationTests
+ class AssetDebuggingTest < Test::Unit::TestCase
+ include ActiveSupport::Testing::Isolation
+ include Rack::Test::Methods
+
+ def setup
+ build_app(:initializers => true)
+
+ app_file "app/assets/javascripts/application.js", "//= require_tree ."
+ app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }"
+ app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>"
+
+ app_file "config/routes.rb", <<-RUBY
+ AppTemplate::Application.routes.draw do
+ match '/posts', :to => "posts#index"
+ end
+ RUBY
+
+ app_file "app/controllers/posts_controller.rb", <<-RUBY
+ class PostsController < ActionController::Base
+ end
+ RUBY
+
+ ENV["RAILS_ENV"] = "production"
+
+ boot_rails
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "assets are concatenated when debug is off and allow_debugging is off either if debug_assets param is provided" do
+ # config.assets.debug and config.assets.allow_debugging are false for production environment
+ require "#{app_path}/config/environment"
+
+ # the debug_assets params isn't used if allow_debugging is off
+ get '/posts?debug_assets=true'
+ assert_match %r{<script src="/assets/application-([0-z]+)\.js" type="text/javascript"></script>}, last_response.body
+ assert_no_match %r{<script src="/assets/xmlhr-([0-z]+)\.js" type="text/javascript"></script>}, last_response.body
+ end
+
+ test "assets aren't concatened when allow_debugging is on and debug_assets params is true" do
+ app_file "config/initializers/allow_debugging.rb", "Rails.application.config.assets.allow_debugging = true"
+
+ require "#{app_path}/config/environment"
+
+ get '/posts?debug_assets=true'
+ assert_match %r{<script src="/assets/application-([0-z]+)\.js\?body=1" type="text/javascript"></script>}, last_response.body
+ assert_match %r{<script src="/assets/xmlhr-([0-z]+)\.js\?body=1" type="text/javascript"></script>}, last_response.body
+ end
+ end
+end
diff --git a/railties/test/application/assets_test.rb b/railties/test/application/assets_test.rb
index 1e6a93dbdf..a8d1382e94 100644
--- a/railties/test/application/assets_test.rb
+++ b/railties/test/application/assets_test.rb
@@ -135,47 +135,5 @@ module ApplicationTests
assert_match "alert();", last_response.body
assert_equal 200, last_response.status
end
-
- test "assets are concatenated when debug is off and allow_debugging is off either if debug_assets param is provided" do
- app_with_assets_in_view
-
- # config.assets.debug and config.assets.allow_debugging are false for production environment
- ENV["RAILS_ENV"] = "production"
- require "#{app_path}/config/environment"
-
- class ::PostsController < ActionController::Base ; end
-
- # the debug_assets params isn't used if allow_debugging is off
- get '/posts?debug_assets=true'
- assert_match /<script src="\/assets\/application-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body
- assert_not_match /<script src="\/assets\/xmlhr-([0-z]+)\.js" type="text\/javascript"><\/script>/, last_response.body
- end
-
- test "assets aren't concatened when allow_debugging is on and debug_assets params is true" do
- app_with_assets_in_view
- app_file "config/initializers/allow_debugging.rb", "Rails.application.config.assets.allow_debugging = true"
-
- ENV["RAILS_ENV"] = "production"
- require "#{app_path}/config/environment"
-
- class ::PostsController < ActionController::Base ; end
-
- get '/posts?debug_assets=true'
- assert_match /<script src="\/assets\/application-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body
- assert_match /<script src="\/assets\/xmlhr-([0-z]+)\.js\?body=1" type="text\/javascript"><\/script>/, last_response.body
- end
-
- private
- def app_with_assets_in_view
- app_file "app/assets/javascripts/application.js", "//= require_tree ."
- app_file "app/assets/javascripts/xmlhr.js", "function f1() { alert(); }"
- app_file "app/views/posts/index.html.erb", "<%= javascript_include_tag 'application' %>"
-
- app_file "config/routes.rb", <<-RUBY
- AppTemplate::Application.routes.draw do
- match '/posts', :to => "posts#index"
- end
- RUBY
- end
end
end