diff options
71 files changed, 498 insertions, 248 deletions
diff --git a/.travis.yml b/.travis.yml index 730b9a1084..dd1d911b63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ before_install: rvm: - 1.9.3 - 2.0.0 - - 2.1.0-rc1 + - 2.1.0 - rbx - jruby env: @@ -36,9 +36,9 @@ group :test do gem 'ruby-prof', '~> 0.11.2' end - platforms :mri_19, :mri_20 do - gem 'debugger' - end + # platforms :mri_19, :mri_20 do + # gem 'debugger' + # end gem 'benchmark-ips' end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index d696656521..3e3df19a84 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,7 @@ +* `ActionController::Parameters#permit!` permits hashes in array values. + + *Xavier Noria* + * Converts hashes in arrays of unfiltered params to unpermitted params. Fixes #13382 @@ -82,14 +86,15 @@ *Łukasz Strzałkowski* -* Fix header `Content-Type: #<Mime::NullType:...>` in localized template. +* Fix render of localized templates without an explicit format using wrong + content header and not passing correct formats to template due to the + introduction of the `NullType` for mimes. - When localized template has no format in the template name, - the response now has the default and correct `content-type`. + Templates like `hello.it.erb` were subject to this issue. Fixes #13064. - *Angelo Capilleri* + *Angelo Capilleri*, *Carlos Antonio da Silva* * Try to escape each part of a url correctly when using a redirect route. diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 6f17e3fcd9..7be61d94c9 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -22,7 +22,7 @@ module AbstractController def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) - _process_format(rendered_format) + _process_format(rendered_format) if rendered_format self.response_body end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 66d34f3b67..5c48b4ab98 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -34,8 +34,7 @@ module ActionController def _process_format(format) super - # format is a Mime::NullType instance here then this condition can't be changed to `if format` - self.content_type ||= format.to_s unless format.nil? + self.content_type ||= format.to_s end # Normalize arguments by catching blocks and setting them on :update. diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 41be1b121e..48a916f2b1 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -157,8 +157,10 @@ module ActionController # Person.new(params) # => #<Person id: nil, name: "Francesco"> def permit! each_pair do |key, value| - convert_hashes_to_parameters(key, value) - self[key].permit! if self[key].respond_to? :permit! + value = convert_hashes_to_parameters(key, value) + Array.wrap(value).each do |_| + _.permit! if _.respond_to? :permit! + end end @permitted = true diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 0833e65d23..a2fc814221 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -3,6 +3,7 @@ require "action_controller" require "action_dispatch/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/helpers" +require "action_view/railtie" module ActionController class Railtie < Rails::Railtie #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 346598b6de..c33ba201e1 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -50,7 +50,7 @@ module ActionDispatch # GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first # def format(view_path = []) - formats.first + formats.first || Mime::NullType.instance end def formats diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 2a8ff0a5d2..3d2dd2d632 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -28,7 +28,7 @@ module Mime class << self def [](type) return type if type.is_a?(Type) - Type.lookup_by_extension(type) || NullType.instance + Type.lookup_by_extension(type) end def fetch(type) diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 87db9a3de4..37e993b4e5 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -334,7 +334,6 @@ class ThreadsController < ResourcesController; end class MessagesController < ResourcesController; end class CommentsController < ResourcesController; end class ReviewsController < ResourcesController; end -class AuthorsController < ResourcesController; end class LogosController < ResourcesController; end class AccountsController < ResourcesController; end @@ -345,8 +344,6 @@ class PreferencesController < ResourcesController; end module Backoffice class ProductsController < ResourcesController; end - class TagsController < ResourcesController; end - class ManufacturersController < ResourcesController; end class ImagesController < ResourcesController; end module Admin diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 3b5d7ef446..d3efca5b6f 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -893,17 +893,6 @@ class ControllerWithFilterInstance < PostsController around_filter YieldingFilter.new, :only => :raises_after end -class ControllerWithFilterMethod < PostsController - class YieldingFilter < DefaultFilter - def around(controller) - yield - raise After - end - end - - around_filter YieldingFilter.new.method(:around), :only => :raises_after -end - class ControllerWithProcFilter < PostsController around_filter(:only => :no_raise) do |c,b| c.instance_variable_set(:"@before", true) diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index 9c1828e9aa..33a91d72d9 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -8,9 +8,16 @@ class ParametersPermitTest < ActiveSupport::TestCase end setup do - @params = ActionController::Parameters.new({ person: { - age: "32", name: { first: "David", last: "Heinemeier Hansson" } - }}) + @params = ActionController::Parameters.new( + person: { + age: '32', + name: { + first: 'David', + last: 'Heinemeier Hansson' + }, + addresses: [{city: 'Chicago', state: 'Illinois'}] + } + ) @struct_fields = [] %w(0 1 12).each do |number| @@ -233,6 +240,7 @@ class ParametersPermitTest < ActiveSupport::TestCase assert @params.permitted? assert @params[:person].permitted? assert @params[:person][:name].permitted? + assert @params[:person][:addresses][0].permitted? end test "permitted takes a default value when Parameters.permit_all_parameters is set" do diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 8ecc1c7d73..a4c84c29d7 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -144,7 +144,7 @@ class SendFileTest < ActionController::TestCase } @controller.headers = {} - assert !@controller.send(:send_file_headers!, options) + assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) } end def test_send_file_headers_guess_type_from_extension diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb index 5dd23c4b31..b44f57a23e 100644 --- a/actionview/test/actionpack/controller/layout_test.rb +++ b/actionview/test/actionpack/controller/layout_test.rb @@ -215,12 +215,6 @@ class LayoutSetInResponseTest < ActionController::TestCase end end -class RenderWithTemplateOptionController < LayoutTest - def hello - render :template => 'alt/hello' - end -end - class SetsNonExistentLayoutFile < LayoutTest layout "nofile" end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 7e594d5030..45b8049b83 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -44,8 +44,6 @@ module Quiz end end - class Store < Question; end - # Controller class QuestionsController < ApplicationController def new diff --git a/actionview/test/fixtures/developer.rb b/actionview/test/fixtures/developer.rb index 4941463015..8b3f0a8039 100644 --- a/actionview/test/fixtures/developer.rb +++ b/actionview/test/fixtures/developer.rb @@ -4,7 +4,3 @@ class Developer < ActiveRecord::Base has_many :topics, :through => :replies accepts_nested_attributes_for :projects end - -class DeVeLoPeR < ActiveRecord::Base - self.table_name = "developers" -end diff --git a/actionview/test/template/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 541fca02d4..3ca71d3376 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -2,14 +2,6 @@ require 'zlib' require 'abstract_unit' require 'active_support/ordered_options' -class FakeController - attr_accessor :request - - def config - @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config) - end -end - class AssetTagHelperTest < ActionView::TestCase tests ActionView::Helpers::AssetTagHelper diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 7a63674757..522a7cebb4 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -7,4 +7,7 @@ require 'active_support/core_ext/string/access' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true +# Disable available locale checks to avoid warnings running the test suite. +I18n.enforce_available_locales = false + require 'active_support/testing/autorun' diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 614059a94f..6b17d6b265 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,65 @@ +* Deprecated use of string argument as a configuration lookup in `ActiveRecord::Base.establish_connection`. Instead, a symbol must be given. + + *José Valim* + +* Fixed `update_column`, `update_columns`, and `update_all` to correctly serialize + values for `array`, `hstore` and `json` column types in PostgreSQL. + + Fixes #12261. + + *Tadas Tamosauskas*, *Carlos Antonio da Silva* + +* Do not consider PostgreSQL array columns as number or text columns. + + The code uses these checks in several places to know what to do with a + particular column, for instance AR attribute query methods has a branch + like this: + + if column.number? + !value.zero? + end + + This should never be true for array columns, since it would be the same + as running [].zero?, which results in a NoMethodError exception. + + Fixing this by ensuring that array columns in PostgreSQL never return + true for number?/text? checks. + + *Carlos Antonio da Silva* + +* When connecting to a non-existant database, the error: + `ActiveRecord::NoDatabaseError` will now be raised. When being used with Rails + the error message will include information on how to create a database: + `rake db:create`. Supported adapters: postgresql, mysql, mysql2, sqlite3 + + *Richard Schneeman* + +* Do not raise `'can not touch on a new record object'` exception on destroying already destroyed + `belongs_to` association with `touch: true` option + + Fixes: #13445 + + Example: + + # Given Comment has belongs_to :post, touch: true + comment.post.destroy + comment.destroy # no longer raises an error + + *Paul Nikitochkin* + +* Fix a bug when assigning an array containing string numbers to a + PostgreSQL integer array column. + + Fixes #13444. + + Example: + + # Given Book#ratings is of type :integer, array: true + Book.new(ratings: [1, 2]) # worked before + Book.new(ratings: ['1', '2']) # now works as well + + *Damien Mathieu* + * Improve the default select when `from` is used. Previously, if you did something like Topic.from(:temp_topics), it diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index 62cc1e3a8d..5ccaa55a32 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -112,7 +112,7 @@ module ActiveRecord::Associations::Builder end record = o.send name - unless record.nil? || record.new_record? + if record && record.persisted? if touch != true record.touch touch else diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index 66d7f04fc3..7e16156408 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -16,38 +16,24 @@ module ActiveRecord ## # Builds a ConnectionSpecification from user input class Resolver # :nodoc: - attr_reader :config, :klass, :configurations + attr_reader :configurations - def initialize(config, configurations) - @config = config + def initialize(configurations) @configurations = configurations end - def spec - case config - when nil - raise AdapterNotSpecified unless defined?(Rails.env) - resolve_string_connection Rails.env - when Symbol, String - resolve_string_connection config.to_s - when Hash - resolve_hash_connection config - end - end - - private - def resolve_string_connection(spec) # :nodoc: - hash = configurations.fetch(spec) do |k| - connection_url_to_hash(k) + def resolve(config) + if config + resolve_connection config + elsif defined?(Rails.env) + resolve_env_connection Rails.env.to_sym + else + raise AdapterNotSpecified end - - raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash - - resolve_hash_connection hash end - def resolve_hash_connection(spec) # :nodoc: - spec = spec.symbolize_keys + def spec(config) + spec = resolve(config).symbolize_keys raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) @@ -61,12 +47,45 @@ module ActiveRecord end adapter_method = "#{spec[:adapter]}_connection" - ConnectionSpecification.new(spec, adapter_method) end - def connection_url_to_hash(url) # :nodoc: - config = URI.parse url + private + + def resolve_connection(spec) #:nodoc: + case spec + when Symbol, String + resolve_env_connection spec + when Hash + resolve_hash_connection spec + end + end + + def resolve_env_connection(spec) # :nodoc: + # Rails has historically accepted a string to mean either + # an environment key or a url spec, so we have deprecated + # this ambiguous behaviour and in the future this function + # can be removed in favor of resolve_string_connection and + # resolve_symbol_connection. + if config = configurations[spec.to_s] + if spec.is_a?(String) + ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \ + "for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead" + end + resolve_connection(config) + elsif spec.is_a?(String) + resolve_string_connection(spec) + else + raise(AdapterNotSpecified, "#{spec} database is not configured") + end + end + + def resolve_hash_connection(spec) # :nodoc: + spec + end + + def resolve_string_connection(spec) # :nodoc: + config = URI.parse spec adapter = config.scheme adapter = "postgresql" if adapter == "postgres" @@ -80,12 +99,12 @@ module ActiveRecord config.path.sub(%r{^/},"") end - spec = { :adapter => adapter, - :username => config.user, - :password => config.password, - :port => config.port, - :database => database, - :host => config.host } + spec = { "adapter" => adapter, + "username" => config.user, + "password" => config.password, + "port" => config.port, + "database" => database, + "host" => config.host } spec.reject!{ |_,value| value.blank? } @@ -94,8 +113,7 @@ module ActiveRecord spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) } if config.query - options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys - + options = Hash[config.query.split("&").map{ |pair| pair.split("=") }] spec.merge!(options) end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index e790f731ea..6d8e994654 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -18,6 +18,12 @@ module ActiveRecord client = Mysql2::Client.new(config) options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0] ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config) + rescue Mysql2::Error => error + if error.message.include?("Unknown database") + raise ActiveRecord::NoDatabaseError.new(error.message) + else + raise error + 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 760f1435eb..7dbaa272a3 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -34,6 +34,12 @@ module ActiveRecord default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS) options = [host, username, password, database, port, socket, default_flags] ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config) + rescue Mysql::Error => error + if error.message.include?("Unknown database") + raise ActiveRecord::NoDatabaseError.new(error.message) + else + raise error + end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb index bf34f2bdae..35ce881302 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/cast.rb @@ -144,7 +144,7 @@ module ActiveRecord def quote_and_escape(value) case value - when "NULL" + when "NULL", Numeric value else "\"#{value.gsub(/"/,"\\\"")}\"" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index dd3bfa5546..11a5eba464 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -46,7 +46,7 @@ module ActiveRecord # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: attr_accessor :array - # Instantiates a new PostgreSQL column definition in a table. + def initialize(name, default, oid_type, sql_type = nil, null = true) @oid_type = oid_type default_value = self.class.extract_value_from_default(default) @@ -62,6 +62,14 @@ module ActiveRecord @default_function = default if has_default_function?(default_value, default) end + def number? + !array && super + end + + def text? + !array && super + end + # :stopdoc: class << self include ConnectionAdapters::PostgreSQLColumn::Cast @@ -865,6 +873,12 @@ module ActiveRecord PostgreSQLColumn.money_precision = (postgresql_version >= 80300) ? 19 : 10 configure_connection + rescue ::PG::Error => error + if error.message.include?("does not exist") + raise ActiveRecord::NoDatabaseError.new(error.message) + else + raise error + end end # Configures the encoding, verbosity, schema search path, and time zone of the connection. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index a02eda5603..92bb70ba53 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -31,6 +31,12 @@ module ActiveRecord db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout] ConnectionAdapters::SQLite3Adapter.new(db, logger, config) + rescue Errno::ENOENT => error + if error.message.include?("No such file or directory") + raise ActiveRecord::NoDatabaseError.new(error.message) + else + raise error + end end end diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index a1943dfcb0..c4afadbd9b 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -32,11 +32,18 @@ module ActiveRecord # "postgres://myuser:mypass@localhost/somedatabase" # ) # + # In case <tt>ActiveRecord::Base.configurations</tt> is set (Rails + # automatically loads the contents of config/database.yml into it), + # a symbol can also be given as argument, representing a key in the + # configuration hash: + # + # ActiveRecord::Base.establish_connection(:production) + # # The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError # may be returned on an error. def establish_connection(spec = ENV["DATABASE_URL"]) - resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new spec, configurations - spec = resolver.spec + resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new configurations + spec = resolver.spec(spec) unless respond_to?(spec.adapter_method) raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter" diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 837989aaa7..2f8439892b 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -55,12 +55,12 @@ module ActiveRecord def enum(definitions) klass = self definitions.each do |name, values| - # DIRECTION = { } + # STATUS = { } enum_values = _enum_methods_module.const_set name.to_s.upcase, ActiveSupport::HashWithIndifferentAccess.new name = name.to_sym _enum_methods_module.module_eval do - # def direction=(value) self[:direction] = DIRECTION[value] end + # def status=(value) self[:status] = STATUS[value] end define_method("#{name}=") { |value| unless enum_values.has_key?(value) raise ArgumentError, "'#{value}' is not a valid #{name}" @@ -68,20 +68,20 @@ module ActiveRecord self[name] = enum_values[value] } - # def direction() DIRECTION.key self[:direction] end + # def status() STATUS.key self[:status] end define_method(name) { enum_values.key self[name] } pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index pairs.each do |value, i| enum_values[value] = i - # scope :incoming, -> { where direction: 0 } + # scope :active, -> { where status: 0 } klass.scope value, -> { klass.where name => i } - # def incoming?() direction == 0 end + # def active?() status == 0 end define_method("#{value}?") { self[name] == i } - # def incoming! update! direction: :incoming end + # def active!() update! status: :active end define_method("#{value}!") { update! name => value } end end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 2602f79db9..7f6228131f 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -94,6 +94,18 @@ module ActiveRecord class PreparedStatementInvalid < ActiveRecordError end + # Raised when a given database does not exist + class NoDatabaseError < ActiveRecordError + def initialize(message) + super extend_message(message) + end + + # can be over written to add additional error information. + def extend_message(message) + message + end + end + # Raised on attempt to save stale record. Record is stale when it's being saved in another query after # instantiation, for example, when two users edit the same wiki page and one starts editing and saves # the page before the other. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 35fbad466e..0cd5c09399 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -361,7 +361,7 @@ module ActiveRecord # assert_equal 25, account.credit # check it is updated in memory # assert_equal 25, account.reload.credit # check it is also persisted # - # Another commom use case is optimistic locking handling: + # Another common use case is optimistic locking handling: # # def with_optimistic_retry # begin diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index eef08aea88..2a8cd83285 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -43,11 +43,24 @@ module ActiveRecord namespace :db do task :load_config do ActiveRecord::Tasks::DatabaseTasks.db_dir = Rails.application.config.paths["db"].first - ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures' ActiveRecord::Tasks::DatabaseTasks.root = Rails.root + configuration = if ENV["DATABASE_URL"] + { Rails.env => ENV["DATABASE_URL"] } + else + Rails.application.config.database_configuration || {} + end + + resolver = ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new(configuration) + + configuration.each do |key, value| + configuration[key] = resolver.resolve(value) if value + end + + ActiveRecord::Tasks::DatabaseTasks.database_configuration = configuration + if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) if engine.paths['db/migrate'].existent ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a @@ -122,6 +135,15 @@ module ActiveRecord # and then establishes the connection. initializer "active_record.initialize_database" do |app| ActiveSupport.on_load(:active_record) do + + class ActiveRecord::NoDatabaseError + remove_possible_method :extend_message + def extend_message(message) + message << "Run `$ bin/rake db:create db:migrate` to create your database" + message + end + end + self.configurations = app.config.database_configuration || {} establish_connection end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 0fdfed991c..90dfc177e5 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -14,11 +14,7 @@ db_namespace = namespace :db do desc 'Create the database from DATABASE_URL or config/database.yml for the current Rails.env (use db:create:all to create all databases in the config)' task :create => [:load_config] do - if ENV['DATABASE_URL'] - ActiveRecord::Tasks::DatabaseTasks.create_database_url - else - ActiveRecord::Tasks::DatabaseTasks.create_current - end + ActiveRecord::Tasks::DatabaseTasks.create_current end namespace :drop do @@ -29,11 +25,7 @@ db_namespace = namespace :db do desc 'Drops the database using DATABASE_URL or the current Rails.env (use db:drop:all to drop all databases)' task :drop => [:load_config] do - if ENV['DATABASE_URL'] - ActiveRecord::Tasks::DatabaseTasks.drop_database_url - else - ActiveRecord::Tasks::DatabaseTasks.drop_current - end + ActiveRecord::Tasks::DatabaseTasks.drop_current end desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index cab8fd745a..dacaec26b7 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -100,8 +100,9 @@ module ActiveRecord # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" def sanitize_sql_hash_for_assignment(attrs, table) + c = connection attrs.map do |attr, value| - "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}" + "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}" end.join(', ') end @@ -152,8 +153,10 @@ module ActiveRecord end end - def quote_bound_value(value, c = connection) #:nodoc: - if value.respond_to?(:map) && !value.acts_like?(:string) + def quote_bound_value(value, c = connection, column = nil) #:nodoc: + if column + c.quote(value, column) + elsif value.respond_to?(:map) && !value.acts_like?(:string) if value.respond_to?(:empty?) && value.empty? c.quote(nil) else diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index be7d496d15..acb7c65af5 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -56,11 +56,7 @@ module ActiveRecord if options.has_key?(:config) @current_config = options[:config] else - @current_config ||= if ENV['DATABASE_URL'] - database_url_config - else - ActiveRecord::Base.configurations[options[:env]] - end + @current_config ||= ActiveRecord::Base.configurations[options[:env]] end end @@ -82,11 +78,7 @@ module ActiveRecord each_current_configuration(environment) { |configuration| create configuration } - ActiveRecord::Base.establish_connection environment - end - - def create_database_url - create database_url_config + ActiveRecord::Base.establish_connection(environment.to_sym) end def drop(*arguments) @@ -107,10 +99,6 @@ module ActiveRecord } end - def drop_database_url - drop database_url_config - end - def charset_current(environment = env) charset ActiveRecord::Base.configurations[environment] end @@ -165,11 +153,6 @@ module ActiveRecord private - def database_url_config - @database_url_config ||= - ConnectionAdapters::ConnectionSpecification::Resolver.new(ENV["DATABASE_URL"], {}).spec.config.stringify_keys - end - def class_for_adapter(adapter) key = @tasks.keys.detect { |pattern| adapter[pattern] } unless key diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 34be68a97f..b67e70ec7e 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -92,7 +92,7 @@ module ActiveRecord ) end ensure - ActiveRecord::Base.establish_connection 'arunit' + ActiveRecord::Base.establish_connection :arunit end end end @@ -191,7 +191,7 @@ module ActiveRecord end def setup - Klass.establish_connection 'arunit' + Klass.establish_connection :arunit @connection = Klass.connection end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index a2c933d96a..a85f3d4298 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -16,6 +16,13 @@ module ActiveRecord eosql end + def test_bad_connection_mysql + assert_raise ActiveRecord::NoDatabaseError do + connection = ActiveRecord::Base.mysql_connection(adapter: "mysql", database: "should_not_exist-cinco-dog-db") + connection.exec_query('drop table if exists ex') + end + end + def test_valid_column column = @conn.columns('ex').find { |col| col.name == 'id' } assert @conn.valid_type?(column.type) diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 8dc1df1851..91b780a7ee 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -13,6 +13,13 @@ class MysqlConnectionTest < ActiveRecord::TestCase super end + def test_bad_connection + assert_raise ActiveRecord::NoDatabaseError do + connection = ActiveRecord::Base.mysql2_connection(adapter: "mysql2", database: "should_not_exist-cinco-dog-db") + connection.exec_query('drop table if exists ex') + end + end + def test_no_automatic_reconnection_after_timeout assert @connection.active? @connection.update('set @@wait_timeout=1') diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 06901a8990..d71e2aa2bb 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -26,6 +26,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_column assert_equal :string, @column.type assert @column.array + assert_not @column.text? + + ratings_column = PgArray.columns_hash['ratings'] + assert_equal :integer, ratings_column.type + assert ratings_column.array + assert_not ratings_column.number? end def test_change_column_with_array @@ -50,8 +56,6 @@ class PostgresqlArrayTest < ActiveRecord::TestCase end def test_type_cast_array - assert @column - data = '{1,2,3}' oid_type = @column.instance_variable_get('@oid_type').subtype # we are getting the instance variable in this test, but in the @@ -66,6 +70,12 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_equal([nil], @column.type_cast('{NULL}')) end + def test_type_cast_integers + x = PgArray.new(ratings: ['1', '2']) + assert x.save! + assert_equal(['1', '2'], x.ratings) + end + def test_rewrite @connection.execute "insert into pg_arrays (tags) VALUES ('{1,2,3}')" x = PgArray.first @@ -118,6 +128,16 @@ class PostgresqlArrayTest < ActiveRecord::TestCase assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings)) end + def test_update_all + pg_array = PgArray.create! tags: ["one", "two", "three"] + + PgArray.update_all tags: ["four", "five"] + assert_equal ["four", "five"], pg_array.reload.tags + + PgArray.update_all tags: [] + assert_equal [], pg_array.reload.tags + end + private def assert_cycle field, array # test creation diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 2845413575..6df5d8f533 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -206,6 +206,16 @@ class PostgresqlHstoreTest < ActiveRecord::TestCase def test_multiline assert_cycle("a\nb" => "c\nd") end + + def test_update_all + hstore = Hstore.create! tags: { "one" => "two" } + + Hstore.update_all tags: { "three" => "four" } + assert_equal({ "three" => "four" }, hstore.reload.tags) + + Hstore.update_all tags: { } + assert_equal({ }, hstore.reload.tags) + end end private diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index c33c7ef968..01e7334aad 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -121,4 +121,14 @@ class PostgresqlJSONTest < ActiveRecord::TestCase x = JsonDataType.first assert_equal "640×1136", x.resolution end + + def test_update_all + json = JsonDataType.create! payload: { "one" => "two" } + + JsonDataType.update_all payload: { "three" => "four" } + assert_equal({ "three" => "four" }, json.reload.payload) + + JsonDataType.update_all payload: { } + assert_equal({ }, json.reload.payload) + end end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 5372f8821f..778175e2e8 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -10,6 +10,13 @@ module ActiveRecord @connection.exec_query('create table ex(id serial primary key, number integer, data character varying(255))') end + def test_bad_connection + assert_raise ActiveRecord::NoDatabaseError do + connection = ActiveRecord::Base.postgresql_connection(database: "should_not_exist-cinco-dog-db", adapter: "postgresql") + connection.exec_query('drop table if exists ex') + end + end + def test_valid_column column = @connection.columns('ex').find { |col| col.name == 'id' } assert @connection.valid_type?(column.type) diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 48ffbd1103..0598ff25f8 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -26,6 +26,13 @@ module ActiveRecord ActiveSupport::Notifications.subscribe('sql.active_record', @subscriber) end + def test_bad_connection + assert_raise ActiveRecord::NoDatabaseError do + connection = ActiveRecord::Base.sqlite3_connection(adapter: "sqlite3", database: "/tmp/should/_not/_exist/-cinco-dog.db") + connection.exec_query('drop table if exists ex') + end + end + def test_connect_with_url original_connection = ActiveRecord::Base.remove_connection tf = Tempfile.open 'whatever' diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 6d01fcf50c..3205d0c28b 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -356,6 +356,14 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_queries(2) { line_item.destroy } end + def test_belongs_to_with_touch_option_on_destroy_with_destroyed_parent + line_item = LineItem.create! + invoice = Invoice.create!(line_items: [line_item]) + invoice.destroy + + assert_queries(1) { line_item.destroy } + end + def test_belongs_to_with_touch_option_on_touch_and_reassigned_parent line_item = LineItem.create! Invoice.create!(line_items: [line_item]) diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index c8dfc3244b..ba8440a16b 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -4,58 +4,74 @@ module ActiveRecord module ConnectionAdapters class ConnectionSpecification class ResolverTest < ActiveRecord::TestCase - def resolve(spec) - Resolver.new(spec, {}).spec.config + def resolve(spec, config={}) + Resolver.new(config).resolve(spec) + end + + def spec(spec, config={}) + Resolver.new(config).spec(spec) end def test_url_invalid_adapter - assert_raises(LoadError) do - resolve 'ridiculous://foo?encoding=utf8' + error = assert_raises(LoadError) do + spec 'ridiculous://foo?encoding=utf8' end + + assert_match "Could not load 'active_record/connection_adapters/ridiculous_adapter'", error.message end # The abstract adapter is used simply to bypass the bit of code that # checks that the adapter file can be required in. + def test_url_from_environment + spec = resolve :production, 'production' => 'abstract://foo?encoding=utf8' + assert_equal({ + "adapter" => "abstract", + "host" => "foo", + "encoding" => "utf8" }, spec) + end + def test_url_host_no_db spec = resolve 'abstract://foo?encoding=utf8' assert_equal({ - adapter: "abstract", - host: "foo", - encoding: "utf8" }, spec) + "adapter" => "abstract", + "host" => "foo", + "encoding" => "utf8" }, spec) end def test_url_host_db spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ - adapter: "abstract", - database: "bar", - host: "foo", - encoding: "utf8" }, spec) + "adapter" => "abstract", + "database" => "bar", + "host" => "foo", + "encoding" => "utf8" }, spec) end def test_url_port spec = resolve 'abstract://foo:123?encoding=utf8' assert_equal({ - adapter: "abstract", - port: 123, - host: "foo", - encoding: "utf8" }, spec) + "adapter" => "abstract", + "port" => 123, + "host" => "foo", + "encoding" => "utf8" }, spec) end def test_encoded_password password = 'am@z1ng_p@ssw0rd#!' encoded_password = URI.encode_www_form_component(password) spec = resolve "abstract://foo:#{encoded_password}@localhost/bar" - assert_equal password, spec[:password] + assert_equal password, spec["password"] end - def test_descriptive_error_message_when_adapter_is_missing - error = assert_raise(LoadError) do - resolve(adapter: 'non-existing') - end + def test_url_host_db_for_sqlite3 + spec = resolve 'sqlite3://foo:bar@dburl:9000/foo_test' + assert_equal('/foo_test', spec["database"]) + end - assert_match "Could not load 'active_record/connection_adapters/non-existing_adapter'", error.message + def test_url_host_memory_db_for_sqlite3 + spec = resolve 'sqlite3://foo:bar@dburl:9000/:memory:' + assert_equal(':memory:', spec["database"]) end end end diff --git a/activerecord/test/cases/multiple_db_test.rb b/activerecord/test/cases/multiple_db_test.rb index 2e386a172a..3831de6ae3 100644 --- a/activerecord/test/cases/multiple_db_test.rb +++ b/activerecord/test/cases/multiple_db_test.rb @@ -101,7 +101,7 @@ class MultipleDbTest < ActiveRecord::TestCase College.first.courses.first end ensure - ActiveRecord::Base.establish_connection 'arunit' + ActiveRecord::Base.establish_connection :arunit end end end diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index a9a114328c..3257b782a9 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -143,7 +143,7 @@ module ActiveRecord def test_establishes_connection_for_the_given_environment ActiveRecord::Tasks::DatabaseTasks.stubs(:create).returns true - ActiveRecord::Base.expects(:establish_connection).with('development') + ActiveRecord::Base.expects(:establish_connection).with(:development) ActiveRecord::Tasks::DatabaseTasks.create_current( ActiveSupport::StringInquirer.new('development') diff --git a/activerecord/test/cases/transaction_isolation_test.rb b/activerecord/test/cases/transaction_isolation_test.rb index 84c16fb109..f89c26532d 100644 --- a/activerecord/test/cases/transaction_isolation_test.rb +++ b/activerecord/test/cases/transaction_isolation_test.rb @@ -28,8 +28,8 @@ if ActiveRecord::Base.connection.supports_transaction_isolation? end setup do - Tag.establish_connection 'arunit' - Tag2.establish_connection 'arunit' + Tag.establish_connection :arunit + Tag2.establish_connection :arunit Tag.destroy_all end diff --git a/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index 196b3a9493..d11fd9cfc1 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -15,7 +15,7 @@ module ARTest puts "Using #{connection_name}" ActiveRecord::Base.logger = ActiveSupport::Logger.new("debug.log", 0, 100 * 1024 * 1024) ActiveRecord::Base.configurations = connection_config - ActiveRecord::Base.establish_connection 'arunit' - ARUnit2Model.establish_connection 'arunit2' + ActiveRecord::Base.establish_connection :arunit + ARUnit2Model.establish_connection :arunit2 end end diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 279a977f6f..afb3bb22bf 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -86,7 +86,7 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 2.0.0p247 +ruby 2.0.0p353 ``` To install Rails, use the `gem install` command provided by RubyGems: @@ -1703,8 +1703,8 @@ Deleting Comments ----------------- Another important feature of a blog is being able to delete spam comments. To do -this, we need to implement a link of some sort in the view and a `DELETE` action -in the `CommentsController`. +this, we need to implement a link of some sort in the view and a `destroy` +action in the `CommentsController`. So first, let's add the delete link in the `app/views/comments/_comment.html.erb` partial: @@ -1729,7 +1729,7 @@ So first, let's add the delete link in the Clicking this new "Destroy Comment" link will fire off a `DELETE /posts/:post_id/comments/:id` to our `CommentsController`, which can then use -this to find the comment we want to delete, so let's add a destroy action to our +this to find the comment we want to delete, so let's add a `destroy` action to our controller (`app/controllers/comments_controller.rb`): ```ruby diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index aa26017bda..a83c2b2355 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Configure `secrets.yml` and `database.yml` to read configuration + from the system environment by default for production. + + *José Valim* + * `config.assets.raise_runtime_errors` is set to true by default This option has been introduced in diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index 5b20c178eb..05acd78d98 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -414,7 +414,7 @@ module Rails def validate_secret_key_config! #:nodoc: if secrets.secret_key_base.blank? && config.secret_token.blank? - raise "You must set secret_key_base in your app's config" + raise "Missing `secret_key_base` for '#{Rails.env}' environment, set this value in `config/secrets.yml`" end end end diff --git a/railties/lib/rails/commands/dbconsole.rb b/railties/lib/rails/commands/dbconsole.rb index 847447fdad..c265ed8f36 100644 --- a/railties/lib/rails/commands/dbconsole.rb +++ b/railties/lib/rails/commands/dbconsole.rb @@ -83,9 +83,8 @@ module Rails @config ||= begin require APP_PATH ActiveRecord::ConnectionAdapters::ConnectionSpecification::Resolver.new( - ENV['DATABASE_URL'], - (Rails.application.config.database_configuration || {}) - ).spec.config.stringify_keys + Rails.application.config.database_configuration || {} + ).resolve(ENV["DATABASE_URL"]) end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml index c6dfd50d40..334d2ba51c 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/frontbase.yml @@ -23,6 +23,6 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml index fe53cd0ea2..0a2dc1b19e 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/ibm_db.yml @@ -61,6 +61,6 @@ test: <<: *default database: <%= app_name[0,4] %>_tst -production: - <<: *default - database: <%= app_name[0,8] %> +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml index be1dae332f..cda6bcab06 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbc.yml @@ -53,6 +53,6 @@ test: <<: *default url: jdbc:db://localhost/<%= app_name %>_test -production: - <<: *default - url: jdbc:db://localhost/<%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index 26e2a5976c..b316b3ade1 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -26,6 +26,6 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 2776ba477f..d76632f0ac 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -42,6 +42,6 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml index 28c36eb82f..4aaa97b104 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml @@ -18,6 +18,6 @@ test: <<: *default database: db/test.sqlite3 -production: - <<: *default - database: db/production.sqlite3 +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml index 3fc7ce28a1..28a5418b49 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -32,6 +32,10 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Avoid production credentials in the repository, +# instead read the configuration from the environment. +# +# Example: +# mysql2://myuser:mypass@localhost/somedatabase +# +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml index d5b52c969b..31abbe8ba6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/oracle.yml @@ -32,6 +32,6 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index bdf53726c0..7a7a8c08e0 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -59,6 +59,10 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +# +# Example: +# postgres://myuser:mypass@localhost/somedatabase +# +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml index 1c1a37ca8d..d8b0b61e14 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml @@ -20,6 +20,10 @@ test: <<: *default database: db/test.sqlite3 -production: - <<: *default - database: db/production.sqlite3 +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +# +# Example: +# sqlite3://myuser:mypass@localhost/somedatabase +# +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml index 4855f66c0d..c270497879 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -42,6 +42,6 @@ test: <<: *default database: <%= app_name %>_test -production: - <<: *default - database: <%= app_name %>_production +# Do not keep production credentials in the repository, +# instead read the configuration from the environment. +production: <%%= ENV["RAILS_DATABASE_URL"] %> diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml index b32e4bf2a6..6e2c45e119 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml @@ -16,11 +16,7 @@ development: test: secret_key_base: <%= app_secret %> -# This YAML file is processed by ERB first (as others). In particular the -# production environment can set the secret via an environment variable -# this way: -# -# secret_key_base: <%%= ENV['SECRET_KEY_BASE'] %> -# +# Do not keep production secrets in the repository, +# instead read values from the environment. production: - secret_key_base: <%= app_secret %> + secret_key_base: <%%= ENV["RAILS_SECRET_KEY_BASE"] %> diff --git a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb index ac14644145..3bfd5426e8 100644 --- a/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb +++ b/railties/lib/rails/generators/test_unit/mailer/templates/preview.rb @@ -1,7 +1,9 @@ <% module_namespacing do -%> +# Preview all emails at http://localhost:3000/rails/mailers/<%= file_path %> class <%= class_name %>Preview < ActionMailer::Preview <% actions.each do |action| -%> + # Preview this email at http://localhost:3000/rails/mailers/<%= file_path %>/<%= action %> def <%= action %> <%= class_name %>.<%= action %> end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 7a8fed6732..6158c416d7 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -303,7 +303,7 @@ module ApplicationTests assert_not_equal default_verifier.object_id, text_verifier.object_id end - test "secrets.secret_key_base is used when config/tokens.yml is present" do + test "secrets.secret_key_base is used when config/secrets.yml is present" do app_file 'config/secrets.yml', <<-YAML development: secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 @@ -323,7 +323,7 @@ module ApplicationTests assert_equal '3b7cd727ee24e8444053437c36cc66c3', app.secrets.secret_key_base end - test "custom secrets saved in config/tokens.yml are loaded in app secrets" do + test "custom secrets saved in config/secrets.yml are loaded in app secrets" do app_file 'config/secrets.yml', <<-YAML development: secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 83104acf3c..3601a58f67 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -216,7 +216,7 @@ module ApplicationTests require "#{app_path}/config/environment" orig_database_url = ENV.delete("DATABASE_URL") orig_rails_env, Rails.env = Rails.env, 'development' - database_url_db_name = "db/database_url_db.sqlite3" + database_url_db_name = File.join(app_path, "db/database_url_db.sqlite3") ENV["DATABASE_URL"] = "sqlite3://:@localhost/#{database_url_db_name}" ActiveRecord::Base.establish_connection assert ActiveRecord::Base.connection diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 4d348c6b4b..0f4f01df1b 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -16,13 +16,16 @@ module ApplicationTests end def database_url_db_name - "db/database_url_db.sqlite3" + File.join(app_path, "db/database_url_db.sqlite3") end def set_database_url - ENV['DATABASE_URL'] = "sqlite3://:@localhost/#{database_url_db_name}" + ENV['RAILS_DATABASE_URL'] = File.join("sqlite3://:@localhost", database_url_db_name) # ensure it's using the DATABASE_URL FileUtils.rm_rf("#{app_path}/config/database.yml") + File.open("#{app_path}/config/database.yml", 'w') do |f| + f << {ENV['RAILS_ENV'] => %Q{<%= ENV['RAILS_DATABASE_URL'] %>}}.to_yaml + end end def expected @@ -60,7 +63,7 @@ module ApplicationTests `rails generate model book title:string; bundle exec rake db:migrate` output = `bundle exec rake db:migrate:status` - assert_match(/database:\s+\S+#{expected[:database]}/, output) + assert_match(%r{database:\s+\S*#{Regexp.escape(expected[:database])}}, output) assert_match(/up\s+\d{14}\s+Create books/, output) end end @@ -126,7 +129,7 @@ module ApplicationTests bundle exec rake db:migrate db:structure:dump` structure_dump = File.read("db/structure.sql") assert_match(/CREATE TABLE \"books\"/, structure_dump) - `bundle exec rake db:drop db:structure:load` + `bundle exec rake environment db:drop db:structure:load` assert_match(/#{expected[:database]}/, ActiveRecord::Base.connection_config[:database]) require "#{app_path}/app/models/book" @@ -153,7 +156,7 @@ module ApplicationTests `rails generate model book title:string; bundle exec rake db:migrate db:structure:dump db:test:load_structure` ActiveRecord::Base.configurations = Rails.application.config.database_configuration - ActiveRecord::Base.establish_connection 'test' + ActiveRecord::Base.establish_connection :test require "#{app_path}/app/models/book" #if structure is not loaded correctly, exception would be raised assert Book.count, 0 diff --git a/railties/test/commands/dbconsole_test.rb b/railties/test/commands/dbconsole_test.rb index a6cd6ec61d..7ad83a8b5d 100644 --- a/railties/test/commands/dbconsole_test.rb +++ b/railties/test/commands/dbconsole_test.rb @@ -38,36 +38,38 @@ class Rails::DBConsoleTest < ActiveSupport::TestCase end def test_config_with_database_url_only - ENV['DATABASE_URL'] = 'sqlite3://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000' + ENV['DATABASE_URL'] = 'postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000' app_db_config(nil) - assert_equal Rails::DBConsole.new.config.sort, { - "adapter"=> "sqlite3", - "host"=> "localhost", - "port"=> 9000, - "database"=> "foo_test", - "username"=> "foo", - "password"=> "bar", - "pool"=> "5", - "timeout"=> "3000" + expected = { + "adapter" => "postgresql", + "host" => "localhost", + "port" => 9000, + "database" => "foo_test", + "username" => "foo", + "password" => "bar", + "pool" => "5", + "timeout" => "3000" }.sort + assert_equal expected, Rails::DBConsole.new.config.sort end def test_config_choose_database_url_if_exists - ENV['DATABASE_URL'] = 'sqlite3://foo:bar@dburl:9000/foo_test?pool=5&timeout=3000' + host = "database-url-host.com" + ENV['DATABASE_URL'] = "postgresql://foo:bar@#{host}:9000/foo_test?pool=5&timeout=3000" sample_config = { "test" => { - "adapter"=> "sqlite3", - "host"=> "localhost", - "port"=> 9000, - "database"=> "foo_test", - "username"=> "foo", - "password"=> "bar", - "pool"=> "5", - "timeout"=> "3000" + "adapter" => "postgresql", + "host" => "not-the-#{host}", + "port" => 9000, + "database" => "foo_test", + "username" => "foo", + "password" => "bar", + "pool" => "5", + "timeout" => "3000" } } app_db_config(sample_config) - assert_equal Rails::DBConsole.new.config["host"], "dburl" + assert_equal host, Rails::DBConsole.new.config["host"] end def test_env diff --git a/railties/test/generators/mailer_generator_test.rb b/railties/test/generators/mailer_generator_test.rb index 207628021a..d209801f60 100644 --- a/railties/test/generators/mailer_generator_test.rb +++ b/railties/test/generators/mailer_generator_test.rb @@ -36,12 +36,15 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(/test "foo"/, test) assert_match(/test "bar"/, test) end - assert_file "test/mailers/previews/notifier_preview.rb" do |mailer| - assert_match(/class NotifierPreview < ActionMailer::Preview/, mailer) - assert_instance_method :foo, mailer do |foo| + assert_file "test/mailers/previews/notifier_preview.rb" do |preview| + assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/notifier/, preview) + assert_match(/class NotifierPreview < ActionMailer::Preview/, preview) + assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/foo/, preview) + assert_instance_method :foo, preview do |foo| assert_match(/Notifier.foo/, foo) end - assert_instance_method :bar, mailer do |bar| + assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/notifier\/bar/, preview) + assert_instance_method :bar, preview do |bar| assert_match(/Notifier.bar/, bar) end end @@ -105,8 +108,10 @@ class MailerGeneratorTest < Rails::Generators::TestCase assert_match(/class Farm::Animal < ActionMailer::Base/, mailer) assert_match(/en\.farm\.animal\.moos\.subject/, mailer) end - assert_file "test/mailers/previews/farm/animal_preview.rb" do |mailer| - assert_match(/class Farm::AnimalPreview < ActionMailer::Preview/, mailer) + assert_file "test/mailers/previews/farm/animal_preview.rb" do |preview| + assert_match(/\# Preview all emails at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal/, preview) + assert_match(/class Farm::AnimalPreview < ActionMailer::Preview/, preview) + assert_match(/\# Preview this email at http:\/\/localhost\:3000\/rails\/mailers\/farm\/animal\/moos/, preview) end assert_file "app/views/farm/animal/moos.text.erb" assert_file "app/views/farm/animal/moos.html.erb" diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 362c2c510a..a1f1973563 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -93,7 +93,8 @@ module TestHelpers # Build an application by invoking the generator and going through the whole stack. def build_app(options = {}) @prev_rails_env = ENV['RAILS_ENV'] - ENV['RAILS_ENV'] = 'development' + ENV['RAILS_ENV'] = 'development' + ENV['RAILS_SECRET_KEY_BASE'] ||= SecureRandom.hex(16) FileUtils.rm_rf(app_path) FileUtils.cp_r(app_template_path, app_path) @@ -117,6 +118,24 @@ module TestHelpers end end + File.open("#{app_path}/config/database.yml", "w") do |f| + f.puts <<-YAML + default: &default + adapter: sqlite3 + pool: 5 + timeout: 5000 + development: + <<: *default + database: db/development.sqlite3 + test: + <<: *default + database: db/test.sqlite3 + production: + <<: *default + database: db/production.sqlite3 + YAML + end + add_to_config <<-RUBY config.eager_load = false config.session_store :cookie_store, key: "_myapp_session" |