diff options
46 files changed, 617 insertions, 280 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index e38f7405e0..8685cd6495 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -14,11 +14,12 @@ *Ville Lautanala* -* Add http_cache_forever to Action Controller, so we can cache a response that never gets expired. +* Add `http_cache_forever` to Action Controller, so we can cache a response + that never gets expired. *arthurnn* -* ActionController#translate supports symbols as shortcuts. +* `ActionController#translate` supports symbols as shortcuts. When shortcut is given it also lookups without action name. *Max Melentiev* @@ -47,14 +48,14 @@ request causing an incorrect behavior during the fall back to GET requests. Example: - ```ruby - draw do - get '/home' => 'test#index' - mount rack_app, at: '/' - end - head '/home' - assert_response :success - ``` + + draw do + get '/home' => 'test#index' + mount rack_app, at: '/' + end + head '/home' + assert_response :success + In this case, a HEAD request runs through the routes the first time and fails to match anything. Then, it runs through the list with the fallback and matches `get '/home'`. The original behavior would match the rack app in the first pass. diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 047a17937a..7e585aa244 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -45,7 +45,7 @@ module Mime # # respond_to do |format| # format.html - # format.ics { render text: @post.to_ics, mime_type: Mime::Type["text/calendar"] } + # format.ics { render text: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") } # format.xml { render xml: @post } # end # end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 2d69ceed3a..918589f916 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -54,23 +54,8 @@ I18n.enforce_available_locales = false # Register danish language for testing I18n.backend.store_translations 'da', {} I18n.backend.store_translations 'pt-BR', {} -ORIGINAL_LOCALES = I18n.available_locales.map(&:to_s).sort FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') -FIXTURES = Pathname.new(FIXTURE_LOAD_PATH) - -module RackTestUtils - def body_to_string(body) - if body.respond_to?(:each) - str = "" - body.each {|s| str << s } - str - else - body - end - end - extend self -end SharedTestRoutes = ActionDispatch::Routing::RouteSet.new @@ -129,22 +114,6 @@ class RoutedRackApp end end -class BasicController - attr_accessor :request - - def config - @config ||= ActiveSupport::InheritableOptions.new(ActionController::Base.config).tap do |config| - # VIEW TODO: View tests should not require a controller - public_dir = File.expand_path("../fixtures/public", __FILE__) - config.assets_dir = public_dir - config.javascripts_dir = "#{public_dir}/javascripts" - config.stylesheets_dir = "#{public_dir}/stylesheets" - config.assets = ActiveSupport::InheritableOptions.new({ :prefix => "assets" }) - config - end - end -end - class ActionDispatch::IntegrationTest < ActiveSupport::TestCase include ActionDispatch::SharedRoutes diff --git a/actionpack/test/controller/new_base/metal_test.rb b/actionpack/test/controller/new_base/metal_test.rb index 45a6619eb4..537b93387a 100644 --- a/actionpack/test/controller/new_base/metal_test.rb +++ b/actionpack/test/controller/new_base/metal_test.rb @@ -18,8 +18,6 @@ module MetalTest end class TestMiddleware < ActiveSupport::TestCase - include RackTestUtils - def setup @app = Rack::Builder.new do use MetalTest::MetalMiddleware @@ -31,14 +29,14 @@ module MetalTest env = Rack::MockRequest.env_for("/authed") response = @app.call(env) - assert_equal "Hello World", body_to_string(response[2]) + assert_equal ["Hello World"], response[2] end test "it can return a response using the normal AC::Metal techniques" do env = Rack::MockRequest.env_for("/") response = @app.call(env) - assert_equal "Not authed!", body_to_string(response[2]) + assert_equal ["Not authed!"], response[2] assert_equal 401, response[0] end end diff --git a/actionpack/test/controller/new_base/middleware_test.rb b/actionpack/test/controller/new_base/middleware_test.rb index 6b7b5e10e3..a30e937bb3 100644 --- a/actionpack/test/controller/new_base/middleware_test.rb +++ b/actionpack/test/controller/new_base/middleware_test.rb @@ -75,7 +75,7 @@ module MiddlewareTest test "middleware that is 'use'd is called as part of the Rack application" do result = @app.call(env_for("/")) - assert_equal "Hello World", RackTestUtils.body_to_string(result[2]) + assert_equal ["Hello World"], result[2] assert_equal "Success", result[1]["Middleware-Test"] end diff --git a/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb b/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb deleted file mode 100644 index bda57d0fae..0000000000 --- a/actionpack/test/fixtures/symlink_parent/symlinked_layout.erb +++ /dev/null @@ -1,5 +0,0 @@ -This is my layout - -<%= yield %> - -End. diff --git a/activejob/Rakefile b/activejob/Rakefile index 1922f256ec..6c7e6aae6e 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -28,7 +28,7 @@ namespace :test do end ACTIVEJOB_ADAPTERS.each do |adapter| - task("env:#{adapter}") { ENV['AJADAPTER'] = adapter } + task("env:#{adapter}") { ENV['AJ_ADAPTER'] = adapter } Rake::TestTask.new(adapter => "test:env:#{adapter}") do |t| t.description = "Run adapter tests for #{adapter}" diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index d610d30e01..aa3ebdbc7b 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -17,8 +17,6 @@ module ActiveJob def queue_adapter=(name_or_adapter) @@queue_adapter = \ case name_or_adapter - when :test - ActiveJob::QueueAdapters::TestAdapter.new when Symbol, String load_adapter(name_or_adapter) else diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb index c9e2bdca27..fd7c0b207a 100644 --- a/activejob/lib/active_job/queue_adapters/test_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -10,40 +10,39 @@ module ActiveJob # # Rails.application.config.active_job.queue_adapter = :test class TestAdapter - delegate :name, to: :class - attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter) - attr_writer(:enqueued_jobs, :performed_jobs) + class << self + attr_accessor(:perform_enqueued_jobs, :perform_enqueued_at_jobs, :filter) + attr_writer(:enqueued_jobs, :performed_jobs) - def initialize - self.perform_enqueued_jobs = false - self.perform_enqueued_at_jobs = false - end + # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. + def enqueued_jobs + @enqueued_jobs ||= [] + end - # Provides a store of all the enqueued jobs with the TestAdapter so you can check them. - def enqueued_jobs - @enqueued_jobs ||= [] - end + # Provides a store of all the performed jobs with the TestAdapter so you can check them. + def performed_jobs + @performed_jobs ||= [] + end - # Provides a store of all the performed jobs with the TestAdapter so you can check them. - def performed_jobs - @performed_jobs ||= [] - end + def enqueue(job) #:nodoc: + return if filtered?(job) - def enqueue(job) #:nodoc: - return if filtered?(job) + job_data = job_to_hash(job) + enqueue_or_perform(perform_enqueued_jobs, job, job_data) + end - job_data = { job: job.class, args: job.serialize['arguments'], queue: job.queue_name } - enqueue_or_perform(perform_enqueued_jobs, job, job_data) - end + def enqueue_at(job, timestamp) #:nodoc: + return if filtered?(job) - def enqueue_at(job, timestamp) #:nodoc: - return if filtered?(job) + job_data = job_to_hash(job, at: timestamp) + enqueue_or_perform(perform_enqueued_at_jobs, job, job_data) + end - job_data = { job: job.class, args: job.serialize['arguments'], queue: job.queue_name, at: timestamp } - enqueue_or_perform(perform_enqueued_at_jobs, job, job_data) - end + private - private + def job_to_hash(job, extras = {}) + { job: job.class, args: job.serialize.fetch('arguments'), queue: job.queue_name }.merge!(extras) + end def enqueue_or_perform(perform, job, job_data) if perform @@ -57,6 +56,7 @@ module ActiveJob def filtered?(job) filter && !Array(filter).include?(job.class) end + end end end end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 25bc99a4f8..66508114d1 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -7,10 +7,12 @@ module ActiveJob included do def before_setup - @old_queue_adapter = queue_adapter + @old_queue_adapter = queue_adapter ActiveJob::Base.queue_adapter = :test clear_enqueued_jobs clear_performed_jobs + queue_adapter.perform_enqueued_jobs = false + queue_adapter.perform_enqueued_at_jobs = false super end @@ -281,7 +283,7 @@ module ActiveJob def enqueued_jobs_size(only: nil) if only - enqueued_jobs.select { |job| job[:job] == only }.size + enqueued_jobs.select { |job| job.fetch(:job) == only }.size else enqueued_jobs.size end diff --git a/activejob/test/cases/adapter_test.rb b/activejob/test/cases/adapter_test.rb index 6570c55a83..f0c710f9ed 100644 --- a/activejob/test/cases/adapter_test.rb +++ b/activejob/test/cases/adapter_test.rb @@ -1,7 +1,7 @@ require 'helper' class AdapterTest < ActiveSupport::TestCase - test "should load #{ENV['AJADAPTER']} adapter" do - assert_equal "active_job/queue_adapters/#{ENV['AJADAPTER']}_adapter".classify, ActiveJob::Base.queue_adapter.name + test "should load #{ENV['AJ_ADAPTER']} adapter" do + assert_equal "active_job/queue_adapters/#{ENV['AJ_ADAPTER']}_adapter".classify, ActiveJob::Base.queue_adapter.name end end diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb index 1d0fdbd22d..7d1702990e 100644 --- a/activejob/test/cases/test_case_test.rb +++ b/activejob/test/cases/test_case_test.rb @@ -9,6 +9,6 @@ class ActiveJobTestCaseTest < ActiveJob::TestCase end def test_set_test_adapter - assert_instance_of ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter + assert_equal ActiveJob::QueueAdapters::TestAdapter, self.queue_adapter end end diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index db5265d7b2..72ec2b8904 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -5,7 +5,7 @@ require 'support/job_buffer' GlobalID.app = 'aj' -@adapter = ENV['AJADAPTER'] || 'inline' +@adapter = ENV['AJ_ADAPTER'] || 'inline' if ENV['AJ_INTEGRATION_TESTS'] require 'support/integration/helper' diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb index 65994d6a1c..09a68738ad 100644 --- a/activejob/test/support/integration/dummy_app_template.rb +++ b/activejob/test/support/integration/dummy_app_template.rb @@ -1,4 +1,4 @@ -if ENV['AJADAPTER'] == 'delayed_job' +if ENV['AJ_ADAPTER'] == 'delayed_job' generate "delayed_job:active_record", "--quiet" rake("db:migrate") end diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb index 39e41b6d29..8c2e5a86c2 100644 --- a/activejob/test/support/integration/helper.rb +++ b/activejob/test/support/integration/helper.rb @@ -1,4 +1,4 @@ -puts "*** rake aj:integration:#{ENV['AJADAPTER']} ***\n" +puts "*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n" ENV["RAILS_ENV"] = "test" ActiveJob::Base.queue_name_prefix = nil diff --git a/activejob/test/support/integration/jobs_manager.rb b/activejob/test/support/integration/jobs_manager.rb index 4df34aaeb1..78d48e8d9a 100644 --- a/activejob/test/support/integration/jobs_manager.rb +++ b/activejob/test/support/integration/jobs_manager.rb @@ -3,7 +3,7 @@ class JobsManager attr :adapter_name def self.current_manager - @@managers[ENV['AJADAPTER']] ||= new(ENV['AJADAPTER']) + @@managers[ENV['AJ_ADAPTER']] ||= new(ENV['AJ_ADAPTER']) end def initialize(adapter_name) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index c21083698e..80b42859e2 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -14,7 +14,7 @@ * Assigning an unknown attribute key to an `ActiveModel` instance during initialization will now raise `ActiveModel::AttributeAssignment::UnknownAttributeError` instead of - `NoMethodError` + `NoMethodError`. Example: diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 52aaa2371d..6cdf9ebd9b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,46 @@ -* Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type column +* PostgreSQL, no longer disables user triggers if system triggers can't be + disabled. Disabling user triggers does not fulfill what the method promises. + Rails currently requires superuser privileges for this method. + + If you absolutely rely on this behavior, consider patching + `disable_referential_integrity`. + + *Yves Senn* + +* Restore aborted transaction state when `disable_referential_integrity` fails + due to missing permissions. + + *Toby Ovod-Everett*, *Yves Senn* + +* PostgreSQL, print warning message if `disable_referential_integrity` fails + due to missing permissions. + + *Andrey Nering*, *Yves Senn* + +* Allow `:limit` option for MySQL bigint primary key support. + + Example: + + create_table :foos, id: :primary_key, limit: 8 do |t| + end + + # or + + create_table :foos, id: false do |t| + t.primary_key :id, limit: 8 + end + + *Ryuta Kamizono* + +* `belongs_to` will now trigger a validation error by default if the association is not present. + You can turn this off on a per-association basis with `optional: true`. + (Note this new default only applies to new Rails apps that will be generated with + `config.active_record.belongs_to_required_by_default = true` in initializer.) + + *Josef Šimánek* + +* Fixed ActiveRecord::Relation#becomes! and changed_attributes issues for type + column. Fixes #17139. @@ -41,7 +83,8 @@ *Michael Ryan* -* `:time` option added for `#touch` +* `:time` option added for `#touch`. + Fixes #18905. *Hyonjee Joo* @@ -85,9 +128,9 @@ * `scoping` no longer pollutes the current scope of sibling classes when using STI. e.x. - StiOne.none.scoping do - StiTwo.all - end + StiOne.none.scoping do + StiTwo.all + end Fixes #18806. diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 499b00a815..0b33ee881b 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1520,10 +1520,16 @@ module ActiveRecord # object that is the inverse of this <tt>belongs_to</tt> association. Does not work in # combination with the <tt>:polymorphic</tt> options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:optional] + # When set to +true+, the association will not have its presence validated. # [:required] # When set to +true+, the association will also have its presence validated. # This will validate the association itself, not the id. You can use # +:inverse_of+ to avoid an extra query during validation. + # NOTE: <tt>required</tt> is set to <tt>true</tt> by default and is deprecated. If + # you don't want to have association presence validated, use <tt>optional: true</tt>. + # + # # # Option examples: # belongs_to :firm, foreign_key: "client_of" @@ -1536,7 +1542,7 @@ module ActiveRecord # belongs_to :post, counter_cache: true # belongs_to :comment, touch: true # belongs_to :company, touch: :employees_last_updated_at - # belongs_to :user, required: true + # belongs_to :user, optional: true def belongs_to(name, scope = nil, options = {}) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb index d0ad57f9c6..ec135d49b7 100644 --- a/activerecord/lib/active_record/associations/builder/belongs_to.rb +++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb @@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder end def self.valid_options(options) - super + [:foreign_type, :polymorphic, :touch, :counter_cache] + super + [:foreign_type, :polymorphic, :touch, :counter_cache, :optional] end def self.valid_dependent_options @@ -110,5 +110,23 @@ module ActiveRecord::Associations::Builder name = reflection.name model.after_destroy lambda { |o| o.association(name).handle_dependency } end + + def self.define_validations(model, reflection) + if reflection.options.key?(:required) + reflection.options[:optional] = !reflection.options.delete(:required) + end + + if reflection.options[:optional].nil? + required = model.belongs_to_required_by_default + else + required = !reflection.options[:optional] + end + + super + + if required + model.validates_presence_of reflection.name, message: :required + end + end end end diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 64e9e6b334..a272d3c781 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -17,5 +17,12 @@ module ActiveRecord::Associations::Builder def self.add_destroy_callbacks(model, reflection) super unless reflection.options[:through] end + + def self.define_validations(model, reflection) + super + if reflection.options[:required] + model.validates_presence_of reflection.name, message: :required + end + end end end diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index f6274c027e..42542f188e 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -27,12 +27,5 @@ module ActiveRecord::Associations::Builder end CODE end - - def self.define_validations(model, reflection) - super - if reflection.options[:required] - model.validates_presence_of reflection.name, message: :required - end - end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index a2777fcd0a..0acc815d51 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -142,6 +142,41 @@ module ActiveRecord end end + module ColumnMethods + # Appends a primary key definition to the table definition. + # Can be called multiple times, but this is probably not a good idea. + def primary_key(name, type = :primary_key, **options) + column(name, type, options.merge(primary_key: true)) + end + + # Appends a column or columns of a specified type. + # + # t.string(:goat) + # t.string(:goat, :sheep) + # + # See TableDefinition#column + [ + :bigint, + :binary, + :boolean, + :date, + :datetime, + :decimal, + :float, + :integer, + :string, + :text, + :time, + :timestamp, + ].each do |column_type| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{column_type}(*args, **options) + args.each { |name| column(name, :#{column_type}, options) } + end + CODE + end + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # @@ -163,6 +198,8 @@ module ActiveRecord # The table definitions # The Columns are stored as a ColumnDefinition in the +columns+ attribute. class TableDefinition + include ColumnMethods + # An array of ColumnDefinition objects, representing the column changes # that have been defined. attr_accessor :indexes @@ -181,12 +218,6 @@ module ActiveRecord def columns; @columns_hash.values; end - # Appends a primary key definition to the table definition. - # Can be called multiple times, but this is probably not a good idea. - def primary_key(name, type = :primary_key, options = {}) - column(name, type, options.merge(:primary_key => true)) - end - # Returns a ColumnDefinition for the column with name +name+. def [](name) @columns_hash[name.to_s] @@ -343,14 +374,6 @@ module ActiveRecord @columns_hash.delete name.to_s end - [:string, :text, :integer, :bigint, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| - define_method column_type do |*args| - options = args.extract_options! - column_names = args - column_names.each { |name| column(name, column_type, options) } - end - end - # Adds index options to the indexes hash, keyed by column name # This is primarily used to track indexes that need to be created after the table # @@ -462,6 +485,7 @@ module ActiveRecord # Available transformations are: # # change_table :table do |t| + # t.primary_key # t.column # t.index # t.rename_index @@ -474,6 +498,7 @@ module ActiveRecord # t.string # t.text # t.integer + # t.bigint # t.float # t.decimal # t.datetime @@ -490,6 +515,8 @@ module ActiveRecord # end # class Table + include ColumnMethods + attr_reader :name def initialize(table_name, base) @@ -640,21 +667,6 @@ module ActiveRecord end alias :remove_belongs_to :remove_references - # Adds a column or columns of a specified type. - # - # t.string(:goat) - # t.string(:goat, :sheep) - # - # See SchemaStatements#add_column - [:string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |column_type| - define_method column_type do |*args| - options = args.extract_options! - args.each do |column_name| - @base.add_column(name, column_name, column_type, options) - end - end - end - def foreign_key(*args) # :nodoc: @base.add_foreign_key(name, *args) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 8db4bcd7e3..8ec26bc109 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -6,13 +6,31 @@ module ActiveRecord class AbstractMysqlAdapter < AbstractAdapter include Savepoints - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition - def primary_key(name, type = :primary_key, options = {}) - options[:auto_increment] ||= type == :bigint + module ColumnMethods + def primary_key(name, type = :primary_key, **options) + options[:auto_increment] = true if type == :bigint super end end + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + def new_column_definition(name, type, options) # :nodoc: + column = super + case column.type + when :primary_key + column.type = :integer + column.auto_increment = true + end + column + end + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + class SchemaCreation < AbstractAdapter::SchemaCreation def visit_AddColumn(o) add_column_position!(super, column_options(o)) @@ -57,6 +75,10 @@ module ActiveRecord end end + def update_table_definition(table_name, base) # :nodoc: + Table.new(table_name, base) + end + def schema_creation SchemaCreation.new self end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index 52b307c432..c1835380f9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -8,20 +8,39 @@ module ActiveRecord def disable_referential_integrity # :nodoc: if supports_disable_referential_integrity? + original_exception = nil + begin - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) - rescue - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER USER" }.join(";")) + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) + end + rescue => e + original_exception = e end - end - yield - ensure - if supports_disable_referential_integrity? + + begin + yield + rescue ActiveRecord::InvalidForeignKey => e + warn <<-WARNING +WARNING: Rails was not able to disable referential integrity. + +This is most likely caused due to missing permissions. +Rails needs superuser privileges to disable referential integrity. + + cause: #{original_exception.try(:message)} + + WARNING + raise e + end + begin - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + transaction(require_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) + end rescue - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER USER" }.join(";")) end + else + yield end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index b9078d4c86..022dbdfa27 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -2,90 +2,129 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module ColumnMethods - def xml(*args) - options = args.extract_options! - column(args[0], :xml, options) + # Defines the primary key field. + # Use of the native PostgreSQL UUID type is supported, and can be used + # by defining your tables as such: + # + # create_table :stuffs, id: :uuid do |t| + # t.string :content + # t.timestamps + # end + # + # By default, this will use the +uuid_generate_v4()+ function from the + # +uuid-ossp+ extension, which MUST be enabled on your database. To enable + # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your + # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can + # set the +:default+ option to +nil+: + # + # create_table :stuffs, id: false do |t| + # t.primary_key :id, :uuid, default: nil + # t.uuid :foo_id + # t.timestamps + # end + # + # You may also pass a different UUID generation function from +uuid-ossp+ + # or another library. + # + # Note that setting the UUID primary key default value to +nil+ will + # require you to assure that you always provide a UUID value before saving + # a record (as primary keys cannot be +nil+). This might be done via the + # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. + def primary_key(name, type = :primary_key, **options) + options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid + super + end + + def bigserial(*args, **options) + args.each { |name| column(name, :bigserial, options) } + end + + def bit(*args, **options) + args.each { |name| column(name, :bit, options) } end - def tsvector(*args) - options = args.extract_options! - column(args[0], :tsvector, options) + def bit_varying(*args, **options) + args.each { |name| column(name, :bit_varying, options) } end - def int4range(name, options = {}) - column(name, :int4range, options) + def cidr(*args, **options) + args.each { |name| column(name, :cidr, options) } end - def int8range(name, options = {}) - column(name, :int8range, options) + def citext(*args, **options) + args.each { |name| column(name, :citext, options) } end - def tsrange(name, options = {}) - column(name, :tsrange, options) + def daterange(*args, **options) + args.each { |name| column(name, :daterange, options) } end - def tstzrange(name, options = {}) - column(name, :tstzrange, options) + def hstore(*args, **options) + args.each { |name| column(name, :hstore, options) } end - def numrange(name, options = {}) - column(name, :numrange, options) + def inet(*args, **options) + args.each { |name| column(name, :inet, options) } end - def daterange(name, options = {}) - column(name, :daterange, options) + def int4range(*args, **options) + args.each { |name| column(name, :int4range, options) } end - def hstore(name, options = {}) - column(name, :hstore, options) + def int8range(*args, **options) + args.each { |name| column(name, :int8range, options) } end - def ltree(name, options = {}) - column(name, :ltree, options) + def json(*args, **options) + args.each { |name| column(name, :json, options) } end - def inet(name, options = {}) - column(name, :inet, options) + def jsonb(*args, **options) + args.each { |name| column(name, :jsonb, options) } end - def cidr(name, options = {}) - column(name, :cidr, options) + def ltree(*args, **options) + args.each { |name| column(name, :ltree, options) } end - def macaddr(name, options = {}) - column(name, :macaddr, options) + def macaddr(*args, **options) + args.each { |name| column(name, :macaddr, options) } end - def uuid(name, options = {}) - column(name, :uuid, options) + def money(*args, **options) + args.each { |name| column(name, :money, options) } end - def json(name, options = {}) - column(name, :json, options) + def numrange(*args, **options) + args.each { |name| column(name, :numrange, options) } end - def jsonb(name, options = {}) - column(name, :jsonb, options) + def point(*args, **options) + args.each { |name| column(name, :point, options) } end - def citext(name, options = {}) - column(name, :citext, options) + def serial(*args, **options) + args.each { |name| column(name, :serial, options) } end - def point(name, options = {}) - column(name, :point, options) + def tsrange(*args, **options) + args.each { |name| column(name, :tsrange, options) } end - def bit(name, options = {}) - column(name, :bit, options) + def tstzrange(*args, **options) + args.each { |name| column(name, :tstzrange, options) } end - def bit_varying(name, options = {}) - column(name, :bit_varying, options) + def tsvector(*args, **options) + args.each { |name| column(name, :tsvector, options) } end - def money(name, options = {}) - column(name, :money, options) + def uuid(*args, **options) + args.each { |name| column(name, :uuid, options) } + end + + def xml(*args, **options) + args.each { |name| column(name, :xml, options) } end end @@ -96,39 +135,6 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - # Defines the primary key field. - # Use of the native PostgreSQL UUID type is supported, and can be used - # by defining your tables as such: - # - # create_table :stuffs, id: :uuid do |t| - # t.string :content - # t.timestamps - # end - # - # By default, this will use the +uuid_generate_v4()+ function from the - # +uuid-ossp+ extension, which MUST be enabled on your database. To enable - # the +uuid-ossp+ extension, you can use the +enable_extension+ method in your - # migrations. To use a UUID primary key without +uuid-ossp+ enabled, you can - # set the +:default+ option to +nil+: - # - # create_table :stuffs, id: false do |t| - # t.primary_key :id, :uuid, default: nil - # t.uuid :foo_id - # t.timestamps - # end - # - # You may also pass a different UUID generation function from +uuid-ossp+ - # or another library. - # - # Note that setting the UUID primary key default value to +nil+ will - # require you to assure that you always provide a UUID value before saving - # a record (as primary keys cannot be +nil+). This might be done via the - # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. - def primary_key(name, type = :primary_key, options = {}) - options[:default] = options.fetch(:default, 'uuid_generate_v4()') if type == :uuid - super - end - def new_column_definition(name, type, options) # :nodoc: column = super column.array = options[:array] diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 1244bd6195..de48681746 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -87,6 +87,8 @@ module ActiveRecord mattr_accessor :maintain_test_schema, instance_accessor: false + mattr_accessor :belongs_to_required_by_default, instance_accessor: false + class_attribute :default_connection_handler, instance_writer: false def self.connection_handler diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4a8e6bdd23..a83b90a95f 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -395,7 +395,7 @@ module ActiveRecord def load_schema_if_pending! if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? - # Roundrip to Rake to allow plugins to hook into database initialization. + # Roundtrip to Rake to allow plugins to hook into database initialization. FileUtils.cd Rails.root do current_config = Base.connection_config Base.clear_all_connections! diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb new file mode 100644 index 0000000000..98291f1bbf --- /dev/null +++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb @@ -0,0 +1,89 @@ +require 'cases/helper' +require 'support/connection_helper' + +class PostgreSQLReferentialIntegrityTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + + include ConnectionHelper + + module MissingSuperuserPrivileges + def execute(sql) + if sql.match(/DISABLE TRIGGER ALL/) || sql.match(/ENABLE TRIGGER ALL/) + super "BROKEN;" rescue nil # put transaction in broken state + raise ActiveRecord::StatementInvalid, 'PG::InsufficientPrivilege' + else + super + end + end + end + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + reset_connection + if ActiveRecord::Base.connection.is_a?(MissingSuperuserPrivileges) + raise "MissingSuperuserPrivileges patch was not removed" + end + end + + def test_should_reraise_invalid_foreign_key_exception_and_show_warning + @connection.extend MissingSuperuserPrivileges + + warning = capture(:stderr) do + e = assert_raises(ActiveRecord::InvalidForeignKey) do + @connection.disable_referential_integrity do + raise ActiveRecord::InvalidForeignKey, 'Should be re-raised' + end + end + assert_equal 'Should be re-raised', e.message + end + assert_match (/WARNING: Rails was not able to disable referential integrity/), warning + assert_match (/cause: PG::InsufficientPrivilege/), warning + end + + def test_does_not_print_warning_if_no_invalid_foreign_key_exception_was_raised + @connection.extend MissingSuperuserPrivileges + + warning = capture(:stderr) do + e = assert_raises(ActiveRecord::StatementInvalid) do + @connection.disable_referential_integrity do + raise ActiveRecord::StatementInvalid, 'Should be re-raised' + end + end + assert_equal 'Should be re-raised', e.message + end + assert warning.blank?, "expected no warnings but got:\n#{warning}" + end + + def test_does_not_break_transactions + @connection.extend MissingSuperuserPrivileges + + @connection.transaction do + @connection.disable_referential_integrity do + assert_transaction_is_not_broken + end + assert_transaction_is_not_broken + end + end + + def test_does_not_break_nested_transactions + @connection.extend MissingSuperuserPrivileges + + @connection.transaction do + @connection.transaction(requires_new: true) do + @connection.disable_referential_integrity do + assert_transaction_is_not_broken + end + end + assert_transaction_is_not_broken + end + end + + private + + def assert_transaction_is_not_broken + assert_equal "1", @connection.select_value("SELECT 1") + end +end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index a425b3ed88..47fd7345c8 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -58,6 +58,56 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase end end + def test_optional_relation + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = true + + model = Class.new(ActiveRecord::Base) do + self.table_name = "accounts" + def self.name; "Temp"; end + belongs_to :company, optional: true + end + + account = model.new + assert account.valid? + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end + + def test_not_optional_relation + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = true + + model = Class.new(ActiveRecord::Base) do + self.table_name = "accounts" + def self.name; "Temp"; end + belongs_to :company, optional: false + end + + account = model.new + refute account.valid? + assert_equal [{error: :blank}], account.errors.details[:company] + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end + + def test_required_belongs_to_config + original_value = ActiveRecord::Base.belongs_to_required_by_default + ActiveRecord::Base.belongs_to_required_by_default = true + + model = Class.new(ActiveRecord::Base) do + self.table_name = "accounts" + def self.name; "Temp"; end + belongs_to :company + end + + account = model.new + refute account.valid? + assert_equal [{error: :blank}], account.errors.details[:company] + ensure + ActiveRecord::Base.belongs_to_required_by_default = original_value + end + def test_default_scope_on_relations_is_not_cached counter = 0 diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 7010af5434..2ffe7a1b0d 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -13,7 +13,7 @@ module ActiveRecord end def with_change_table - yield ConnectionAdapters::Table.new(:delete_me, @connection) + yield ActiveRecord::Base.connection.update_table_definition(:delete_me, @connection) end def test_references_column_type_adds_id @@ -100,6 +100,13 @@ module ActiveRecord end end + def test_primary_key_creates_primary_key_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true] + t.primary_key :id, first: true + end + end + def test_integer_creates_integer_column with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :foo, :integer, {}] @@ -108,6 +115,14 @@ module ActiveRecord end end + def test_bigint_creates_bigint_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :foo, :bigint, {}] + @connection.expect :add_column, nil, [:delete_me, :bar, :bigint, {}] + t.bigint :foo, :bar + end + end + def test_string_creates_string_column with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :foo, :string, {}] @@ -116,6 +131,24 @@ module ActiveRecord end end + if current_adapter?(:PostgreSQLAdapter) + def test_json_creates_json_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :foo, :json, {}] + @connection.expect :add_column, nil, [:delete_me, :bar, :json, {}] + t.json :foo, :bar + end + end + + def test_xml_creates_xml_column + with_change_table do |t| + @connection.expect :add_column, nil, [:delete_me, :foo, :xml, {}] + @connection.expect :add_column, nil, [:delete_me, :bar, :xml, {}] + t.xml :foo, :bar + end + end + end + def test_column_creates_column with_change_table do |t| @connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}] diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index b45fbf0143..1ea1ef5e12 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -282,5 +282,15 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter) assert_match %r{create_table "widgets", id: :bigint}, schema end end + + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) + test "primary key column type with options" do + @connection.create_table(:widgets, id: :primary_key, limit: 8, force: true) + column = @connection.columns(:widgets).find { |c| c.name == 'id' } + assert column.auto_increment? + assert_equal :integer, column.type + assert_equal 8, column.limit + end + end end end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 77d2f7fda2..f84be0e7f4 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -88,16 +88,6 @@ _SQL end end - begin - execute <<_SQL - CREATE TABLE postgresql_xml_data_type ( - id SERIAL PRIMARY KEY, - data xml - ); -_SQL - rescue #This version of PostgreSQL either has no XML support or is was not compiled with XML support: skipping table - end - # This table is to verify if the :limit option is being ignored for text and binary columns create_table :limitless_fields, force: true do |t| t.binary :binary, limit: 100_000 diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 44735e4b75..361571db98 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -3,27 +3,28 @@ Before: - YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" - YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => true - YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => false - YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => 1 - YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => 1.1 + YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" + YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => true + YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => false + YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => 1 + YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => 1.1 - After: + After: - YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" - YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => "true" - YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => "false" - YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => "1" - YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => "1.1" + YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" + YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => "true" + YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => "false" + YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => "1" + YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => "1.1" *Godfrey Chan* -* Enable number_to_percentage to keep the number's precision by allowing :precision to be nil +* Enable `number_to_percentage` to keep the number's precision by allowing + `:precision` to be `nil`. *Jack Xu* -* config_accessor became a private method, as with Ruby's attr_accessor. +* `config_accessor` became a private method, as with Ruby's `attr_accessor`. *Akira Matsuda* diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index a5196e481e..bd8cdf62f2 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -38,7 +38,7 @@ object on how to write to and read from the database. ### Object Relational Mapping -Object-Relational Mapping, commonly referred to as its abbreviation ORM, is +Object Relational Mapping, commonly referred to as its abbreviation ORM, is a technique that connects the rich objects of an application to tables in a relational database management system. Using ORM, the properties and relationships of the objects in an application can be easily stored and diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index ad5103da69..e5a962b739 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1403,8 +1403,9 @@ WHERE people.name = 'John' LIMIT 1 ``` -NOTE: Remember that, if `find_by` returns more than one registry, it will take -just the first and ignore the others. Note the `LIMIT 1` statement above. +NOTE: Note that if a query matches multiple records, `find_by` will +fetch only the first one and ignore the others (see the `LIMIT 1` +statement above). Find or Build a New Object -------------------------- diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index cd715aba1f..0079049d44 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -833,6 +833,7 @@ The `belongs_to` association supports these options: * `:polymorphic` * `:touch` * `:validate` +* `:optional` ##### `:autosave` @@ -956,6 +957,10 @@ end If you set the `:validate` option to `true`, then associated objects will be validated whenever you save this object. By default, this is `false`: associated objects will not be validated when this object is saved. +##### `:optional` + +If you set the `:optional` option to `true`, then associated object will be validated for presence. By default, this is `false`: associated objects will be validated for presence. + #### Scopes for `belongs_to` There may be times when you wish to customize the query used by `belongs_to`. Such customizations can be achieved via a scope block. For example: diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 994cc31cff..4fc1301e06 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -300,6 +300,8 @@ All these configuration options are delegated to the `I18n` library. `config/environments/production.rb` which is generated by Rails. The default value is true if this configuration is not set. +* `config.active_record.belongs_to_required_by_default` is a boolean value and controls whether `belongs_to` association is required by default. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::MysqlAdapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns in a MySQL database to be booleans and is true by default. @@ -1053,17 +1055,19 @@ These configuration points are then available through the configuration object: Search Engines Indexing ----------------------- -Sometimes, you may want to prevent some pages of your application be visible on search sites like Google, -Bing, Yahoo or Duck Duck Go. The robots that index these sites will first analyse the -`https://your-site.com/robots.txt` file to know what pages it is allowed to index. +Sometimes, you may want to prevent some pages of your application to be visible +on search sites like Google, Bing, Yahoo or Duck Duck Go. The robots that index +these sites will first analyse the `http://your-site.com/robots.txt` file to +know which pages it is allowed to index. -Rails creates this file for you on `/public` folder. By default, it allows search engines to index all -pages of your application. If you want to block indexing on all pages of you application, use this: +Rails creates this file for you inside the `/public` folder. By default, it allows +search engines to index all pages of your application. If you want to block +indexing on all pages of you application, use this: ``` User-agent: * Disallow: / ``` -To block just specific pages, it's necessary to use a more complex syntax. Learn it on the -[official documentation](http://www.robotstxt.org/robotstxt.html). +To block just specific pages, it's necessary to use a more complex syntax. Learn +it on the [official documentation](http://www.robotstxt.org/robotstxt.html). diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 6113a61f4c..926a048762 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -246,9 +246,9 @@ noticeable with large amounts of logging, but it's a good practice to employ. Debugging with the `web-console` gem ------------------------------------- -The web console allows you to create an interactive ruby session in your browser. An interactive -console is launched automatically in case on an error but can also be launched for debugging purposes -by invoking `console` in a view or controller. +The web console allows you to start an interactive Ruby session in your browser. +An interactive console is launched automatically in case of an error but can also +be launched for debugging purposes by invoking `console` in a view or controller. For example in a view: @@ -268,30 +268,27 @@ class PostsController < ApplicationController end end ``` -###config.web_console.whitelisted_ips -By default the web console can only be accessed from localhost. `config.web_console.whitelisted_ips` -lets you control which IPs have access to the console. +### config.web_console.whitelisted_ips -For example, to allow access from both localhost and 192.168.0.100: +By default the web console can only be accessed from localhost. +`config.web_console.whitelisted_ips` lets you control which IPs have access to +the console. + +For example, to allow access from both localhost and 192.168.0.100, you can put +inside your configuration file: ```ruby -# config/application.rb -class Application < Rails::Application - config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.100 ) -end +config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.100 ) ``` -To allow access from an entire network: +Or to allow access from an entire network: ```ruby -# config/application.rb -class Application < Rails::Application - config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.0/16 ) -end +config.web_console.whitelisted_ips = %w( 127.0.0.1 192.168.0.0/16 ) ``` -Web console is a powerful tool so be careful who you open access to. +The web console is a powerful tool so be careful when you give access to an IP. Debugging with the `byebug` gem diff --git a/guides/source/engines.md b/guides/source/engines.md index 6eb558885f..84017d5e13 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -890,7 +890,9 @@ engine this would be done by changing `app/controllers/blorgh/application_controller.rb` to look like: ```ruby -class Blorgh::ApplicationController < ApplicationController +module Blorgh + class ApplicationController < ::ApplicationController + end end ``` diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 74d1ca3bd8..df9f8fe993 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,18 @@ +* Add `config/initializers/active_record_belongs_to_required_by_default.rb` + + Newly generated Rails apps have a new initializer called + `active_record_belongs_to_required_by_default.rb` which sets the value of + the configuration option `config.active_record.belongs_to_requred_by_default` + to `true` when ActiveRecord is not skipped. + + As a result, new Rails apps require `belongs_to` association on model + to be valid. + + This initializer is *not* added when running `rake rails:update`, so + old apps ported to Rails 5 will work without any change. + + *Josef Šimánek* + * `delete` operations in configurations are run last in order to eliminate 'No such middleware' errors when `insert_before` or `insert_after` are added after the `delete` operation for the middleware being deleted. diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index db8b184213..341291f08b 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -159,7 +159,7 @@ module Rails options = sorted_groups.flat_map(&:last) suggestions = options.sort_by {|suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) msg = "Could not find generator '#{namespace}'. " - msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.join(" or ") }\n" + msg << "Maybe you meant #{ suggestions.map {|s| "'#{s}'"}.to_sentence(last_word_connector: " or ") }\n" msg << "Run `rails generate --help` for more options." puts msg end @@ -260,11 +260,9 @@ module Rails t = str2 n = s.length m = t.length - max = n/2 return m if (0 == n) return n if (0 == m) - return n if (n - m).abs > max d = (0..m).to_a x = nil diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 977f5a1c03..899b33e529 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -89,6 +89,7 @@ module Rails def config_when_updating cookie_serializer_config_exist = File.exist?('config/initializers/cookies_serializer.rb') callback_terminator_config_exist = File.exist?('config/initializers/callback_terminator.rb') + active_record_belongs_to_required_by_default_config_exist = File.exist?('config/initializers/active_record_belongs_to_required_by_default.rb') config @@ -99,6 +100,10 @@ module Rails unless cookie_serializer_config_exist gsub_file 'config/initializers/cookies_serializer.rb', /json/, 'marshal' end + + unless active_record_belongs_to_required_by_default_config_exist + remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb' + end end def database_yml @@ -258,6 +263,12 @@ module Rails end end + def delete_active_record_initializers_skipping_active_record + if options[:skip_active_record] + remove_file 'config/initializers/active_record_belongs_to_required_by_default.rb' + end + end + def finish_template build(:leftovers) end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb new file mode 100644 index 0000000000..30c4f89792 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Require `belongs_to` associations by default. +Rails.application.config.active_record.belongs_to_required_by_default = true diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 8d71b813e6..2bff21dae5 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -194,7 +194,10 @@ module ApplicationTests assert_no_match(/Errors running/, output) end - def test_scaffold_with_references_columns_tests_pass_by_default + def test_scaffold_with_references_columns_tests_pass_when_belongs_to_is_optional + app_file "config/initializers/active_record_belongs_to_required_by_default.rb", + "Rails.application.config.active_record.belongs_to_required_by_default = false" + output = Dir.chdir(app_path) do `rails generate scaffold LineItems product:references cart:belongs_to; bundle exec rake db:migrate test` diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index ca26e0c8d7..4c5dd70a88 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -209,6 +209,38 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file("#{app_root}/config/initializers/cookies_serializer.rb", /Rails\.application\.config\.action_dispatch\.cookies_serializer = :marshal/) end + def test_rails_update_does_not_create_active_record_belongs_to_required_by_default + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.rm("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb") + + Rails.application.config.root = app_root + Rails.application.class.stubs(:name).returns("Myapp") + Rails.application.stubs(:is_a?).returns(Rails::Application) + + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_no_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb" + end + + def test_rails_update_does_not_remove_active_record_belongs_to_required_by_default_if_already_present + app_root = File.join(destination_root, 'myapp') + run_generator [app_root] + + FileUtils.touch("#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb") + + Rails.application.config.root = app_root + Rails.application.class.stubs(:name).returns("Myapp") + Rails.application.stubs(:is_a?).returns(Rails::Application) + + generator = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, destination_root: app_root, shell: @shell + generator.send(:app_const) + quietly { generator.send(:update_config_files) } + assert_file "#{app_root}/config/initializers/active_record_belongs_to_required_by_default.rb" + end + def test_application_names_are_not_singularized run_generator [File.join(destination_root, "hats")] assert_file "hats/config/environment.rb", /Rails\.application\.initialize!/ @@ -309,6 +341,7 @@ class AppGeneratorTest < Rails::Generators::TestCase def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" + assert_no_file "config/initializers/active_record_belongs_to_required_by_default.rb" assert_file "config/application.rb", /#\s+require\s+["']active_record\/railtie["']/ assert_file "test/test_helper.rb" do |helper_content| assert_no_match(/fixtures :all/, helper_content) |