diff options
57 files changed, 385 insertions, 201 deletions
@@ -65,8 +65,11 @@ group :cable do gem 'faye-websocket', require: false + # Lock to 1.1.1 until the fix for https://github.com/faye/faye/issues/394 is released + gem 'faye', '1.1.1', require: false + gem 'blade', '~> 0.5.5', require: false - gem 'blade-sauce_labs_plugin', github: 'javan/blade-sauce_labs_plugin', require: false + gem 'blade-sauce_labs_plugin', '~> 0.5.1', require: false end # Add your own local bundler stuff. diff --git a/Gemfile.lock b/Gemfile.lock index 7cddaa7e3b..ed325aef55 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,15 +22,6 @@ GIT delayed_job (>= 3.0, < 5) GIT - remote: git://github.com/javan/blade-sauce_labs_plugin.git - revision: dd4230c556aaa19b62cca757d6faeb2bb6bf95e7 - specs: - blade-sauce_labs_plugin (0.5.1) - childprocess - faraday - selenium-webdriver - -GIT remote: git://github.com/rails/coffee-rails.git revision: aa2e623cbda4f3c789a0a15d1f707239e68f5736 specs: @@ -139,6 +130,10 @@ GEM thor (~> 0.19.1) useragent (~> 0.16.7) blade-qunit_adapter (1.20.0) + blade-sauce_labs_plugin (0.5.1) + childprocess + faraday + selenium-webdriver builder (3.2.2) bunny (2.2.2) amq-protocol (>= 2.0.1) @@ -174,7 +169,7 @@ GEM execjs (2.6.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) - faye (1.1.2) + faye (1.1.1) cookiejar (>= 0.3.0) em-http-request (>= 0.3.0) eventmachine (>= 0.12.0) @@ -182,7 +177,7 @@ GEM multi_json (>= 1.0.0) rack (>= 1.0.0) websocket-driver (>= 0.5.1) - faye-websocket (0.10.3) + faye-websocket (0.10.4) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) ffi (1.9.10) @@ -218,7 +213,7 @@ GEM mocha (0.14.0) metaclass (~> 0.0.1) mono_logger (1.1.0) - multi_json (1.12.0) + multi_json (1.12.1) multipart-post (2.0.0) mustache (1.0.3) mysql2 (0.4.4) @@ -337,7 +332,7 @@ GEM nokogiri wdm (0.1.1) websocket (1.2.3) - websocket-driver (0.6.3) + websocket-driver (0.6.4) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) @@ -354,13 +349,14 @@ DEPENDENCIES bcrypt (~> 3.1.11) benchmark-ips blade (~> 0.5.5) - blade-sauce_labs_plugin! + blade-sauce_labs_plugin (~> 0.5.1) byebug coffee-rails! dalli (>= 2.2.1) delayed_job! delayed_job_active_record! em-hiredis + faye (= 1.1.1) faye-websocket hiredis jquery-rails diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md index 5ed5a8b029..4e6b811d3e 100644 --- a/RELEASING_RAILS.md +++ b/RELEASING_RAILS.md @@ -20,7 +20,7 @@ http://travis-ci.org/rails/rails ### Is Sam Ruby happy? If not, make him happy. Sam Ruby keeps a [test suite](https://github.com/rubys/awdwr) that makes -sure the code samples in his book +sure the code samples in his book ([Agile Web Development with Rails](https://pragprog.com/titles/rails5/agile-web-development-with-rails-5th-edition)) all work. These are valuable system tests for Rails. You can check the status of these tests here: @@ -47,7 +47,7 @@ Ruby implementors have high stakes in making sure Rails works. Be kind and give them a heads up that Rails will be released soonish. This is only required for major and minor releases, bugfix releases aren't a -big enough deal, and are supposed to be backwards compatible. +big enough deal, and are supposed to be backward compatible. Send an email just giving a heads up about the upcoming release to these lists: @@ -150,7 +150,7 @@ break existing applications. ### Post the announcement to the Rails blog. -If you used Markdown format for your email, you can just paste it in to the +If you used Markdown format for your email, you can just paste it into the blog. * http://weblog.rubyonrails.org diff --git a/actioncable/README.md b/actioncable/README.md index 71fec714f5..fa6a3ed0df 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -430,7 +430,7 @@ For every instance of your server you create and for every worker your server sp ### Notes -Beware that currently the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. +Beware that currently, the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. We'll get all this abstracted properly when the framework is integrated into Rails. diff --git a/actioncable/app/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb index 210a3ae17e..e0758dae72 100644 --- a/actioncable/app/assets/javascripts/action_cable.coffee.erb +++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb @@ -4,6 +4,8 @@ @ActionCable = INTERNAL: <%= ActionCable::INTERNAL.to_json %> + WebSocket: window.WebSocket + logger: window.console createConsumer: (url) -> url ?= @getConfig("url") ? @INTERNAL.default_mount_path @@ -33,4 +35,4 @@ log: (messages...) -> if @debugging messages.push(Date.now()) - console.log("[ActionCable]", messages...) + @logger.log("[ActionCable]", messages...) diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee index d6a6397804..29ad676290 100644 --- a/actioncable/app/assets/javascripts/action_cable/connection.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee @@ -27,7 +27,7 @@ class ActionCable.Connection else ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}") @uninstallEventHandlers() if @webSocket? - @webSocket = new WebSocket(@consumer.url, protocols) + @webSocket = new ActionCable.WebSocket(@consumer.url, protocols) @installEventHandlers() @monitor.start() true diff --git a/actioncable/blade.yml b/actioncable/blade.yml index e21151099a..e38e9b2f1d 100644 --- a/actioncable/blade.yml +++ b/actioncable/blade.yml @@ -17,18 +17,18 @@ plugins: browsers: Google Chrome: os: Mac, Windows - version: -2 + version: -1 Firefox: os: Mac, Windows - version: -2 + version: -1 Safari: platform: Mac - version: -3 + version: -1 Microsoft Edge: - version: -2 + version: -1 Internet Explorer: version: 11 iPhone: - version: [9.2, 8.4] + version: -1 Motorola Droid 4 Emulator: - version: [5.1, 4.4] + version: -1 diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index 0a0a95f453..561750d713 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -138,7 +138,7 @@ module ActionCable end # May be overridden to change the default stream handling behavior - # which decodes JSON and transmits to client. + # which decodes JSON and transmits to the client. # # TODO: Tests demonstrating this. # diff --git a/actioncable/test/javascript/src/test.coffee b/actioncable/test/javascript/src/test.coffee index 3ce88c7789..eb95fb2604 100644 --- a/actioncable/test/javascript/src/test.coffee +++ b/actioncable/test/javascript/src/test.coffee @@ -1,3 +1,3 @@ #= require action_cable -#= require_tree ./test_helpers +#= require ./test_helpers #= require_tree ./unit diff --git a/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee new file mode 100644 index 0000000000..6b145dede8 --- /dev/null +++ b/actioncable/test/javascript/src/test_helpers/consumer_test_helper.coffee @@ -0,0 +1,37 @@ +#= require mock-socket + +{TestHelpers} = ActionCable + +TestHelpers.consumerTest = (name, options = {}, callback) -> + unless callback? + callback = options + options = {} + + options.url ?= TestHelpers.testURL + + QUnit.test name, (assert) -> + doneAsync = assert.async() + + ActionCable.WebSocket = MockWebSocket + server = new MockServer options.url + consumer = ActionCable.createConsumer(options.url) + + server.on "connection", -> + clients = server.clients() + assert.equal clients.length, 1 + assert.equal clients[0].readyState, WebSocket.OPEN + + done = -> + consumer.disconnect() + server.close() + doneAsync() + + testData = {assert, consumer, server, done} + + if options.connect is false + callback(testData) + else + server.on "connection", -> + testData.client = server.clients()[0] + callback(testData) + consumer.connect() diff --git a/actioncable/test/javascript/src/test_helpers/index.coffee b/actioncable/test/javascript/src/test_helpers/index.coffee index e0d1e412cd..d36524d9cc 100644 --- a/actioncable/test/javascript/src/test_helpers/index.coffee +++ b/actioncable/test/javascript/src/test_helpers/index.coffee @@ -3,3 +3,6 @@ ActionCable.TestHelpers = testURL: "ws://cable.example.com/" + +originalWebSocket = ActionCable.WebSocket +QUnit.testDone -> ActionCable.WebSocket = originalWebSocket diff --git a/actioncable/test/javascript/src/test_helpers/mock_websocket.coffee b/actioncable/test/javascript/src/test_helpers/mock_websocket.coffee deleted file mode 100644 index b7f86f18f6..0000000000 --- a/actioncable/test/javascript/src/test_helpers/mock_websocket.coffee +++ /dev/null @@ -1,21 +0,0 @@ -#= require mock-socket - -NativeWebSocket = window.WebSocket - -server = null -consumer = null - -ActionCable.TestHelpers.createConsumer = (url, callback) -> - window.WebSocket = MockWebSocket - server = new MockServer url - consumer = ActionCable.createConsumer(url) - callback(consumer, server) - -QUnit.testDone -> - if consumer? - consumer.disconnect() - - if server? - server.clients().forEach (client) -> client.close() - server.close() - window.WebSocket = NativeWebSocket diff --git a/actioncable/test/javascript/src/unit/action_cable_test.coffee b/actioncable/test/javascript/src/unit/action_cable_test.coffee index f9eff64769..3944f3a7f6 100644 --- a/actioncable/test/javascript/src/unit/action_cable_test.coffee +++ b/actioncable/test/javascript/src/unit/action_cable_test.coffee @@ -2,6 +2,23 @@ {testURL} = ActionCable.TestHelpers module "ActionCable", -> + module "Adapters", -> + module "WebSocket", -> + test "default is window.WebSocket", (assert) -> + assert.equal ActionCable.WebSocket, window.WebSocket + + test "configurable", (assert) -> + ActionCable.WebSocket = "" + assert.equal ActionCable.WebSocket, "" + + module "logger", -> + test "default is window.console", (assert) -> + assert.equal ActionCable.logger, window.console + + test "configurable", (assert) -> + ActionCable.logger = "" + assert.equal ActionCable.logger, "" + module "#createConsumer", -> test "uses specified URL", (assert) -> consumer = ActionCable.createConsumer(testURL) diff --git a/actioncable/test/javascript/src/unit/consumer_test.coffee b/actioncable/test/javascript/src/unit/consumer_test.coffee index d8b1450ad8..cf8a592255 100644 --- a/actioncable/test/javascript/src/unit/consumer_test.coffee +++ b/actioncable/test/javascript/src/unit/consumer_test.coffee @@ -1,31 +1,11 @@ {module, test} = QUnit -{testURL, createConsumer} = ActionCable.TestHelpers +{consumerTest} = ActionCable.TestHelpers module "ActionCable.Consumer", -> - test "#connect", (assert) -> - done = assert.async() + consumerTest "#connect", connect: false, ({consumer, server, done}) -> + server.on("connection", done) + consumer.connect() - createConsumer testURL, (consumer, server) -> - server.on "connection", -> - clients = server.clients() - assert.equal clients.length, 1 - assert.equal clients[0].readyState, WebSocket.OPEN - done() - - consumer.connect() - - test "#disconnect", (assert) -> - done = assert.async() - - createConsumer testURL, (consumer, server) -> - server.on "connection", -> - clients = server.clients() - assert.equal clients.length, 1 - - clients[0].addEventListener "close", (event) -> - assert.equal event.type, "close" - done() - - consumer.disconnect() - - consumer.connect() + consumerTest "#disconnect", ({consumer, client, done}) -> + client.addEventListener("close", done) + consumer.disconnect() diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index ea5af9e4f2..6e0ae8a0a9 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -488,7 +488,7 @@ module ActionMailer end private :observer_class_for - # Returns the name of current mailer. This method is also being used as a path for a view lookup. + # Returns the name of the current mailer. This method is also being used as a path for a view lookup. # If this is an anonymous mailer, this method will return +anonymous+ instead. def mailer_name @mailer_name ||= anonymous? ? "anonymous" : name.underscore diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index ed2edcbe06..b1b3e87934 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -527,34 +527,37 @@ module ActionController @request.set_header k, @controller.config.relative_url_root end - @controller.recycle! - @controller.dispatch(action, @request, @response) - @request = @controller.request - @response = @controller.response - - @request.delete_header 'HTTP_COOKIE' + begin + @controller.recycle! + @controller.dispatch(action, @request, @response) + ensure + @request = @controller.request + @response = @controller.response + + @request.delete_header 'HTTP_COOKIE' + + if @request.have_cookie_jar? + unless @request.cookie_jar.committed? + @request.cookie_jar.write(@response) + self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + end + end + @response.prepare! - if @request.have_cookie_jar? - unless @request.cookie_jar.committed? - @request.cookie_jar.write(@response) - self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) + if flash_value = @request.flash.to_session_value + @request.session['flash'] = flash_value + else + @request.session.delete('flash') end - end - @response.prepare! - if flash_value = @request.flash.to_session_value - @request.session['flash'] = flash_value - else - @request.session.delete('flash') - end + if xhr + @request.delete_header 'HTTP_X_REQUESTED_WITH' + @request.delete_header 'HTTP_ACCEPT' + end + @request.query_string = '' - if xhr - @request.delete_header 'HTTP_X_REQUESTED_WITH' - @request.delete_header 'HTTP_ACCEPT' + @response.sent! end - @request.query_string = '' - - @response.sent! @response end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index fcbbfe8a18..1e1d6f5429 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -34,7 +34,6 @@ require 'action_dispatch' require 'active_support/dependencies' require 'active_model' require 'active_record' -require 'action_controller/caching' require 'pp' # require 'pp' early to prevent hidden_methods from not picking up the pretty-print methods until too late @@ -58,10 +57,6 @@ ActiveSupport::Deprecation.debug = true # Disable available locale checks to avoid warnings running the test suite. I18n.enforce_available_locales = false -# Register danish language for testing -I18n.backend.store_translations 'da', {} -I18n.backend.store_translations 'pt-BR', {} - FIXTURE_LOAD_PATH = File.join(File.dirname(__FILE__), 'fixtures') SharedTestRoutes = ActionDispatch::Routing::RouteSet.new @@ -256,24 +251,6 @@ end class ::ApplicationController < ActionController::Base end -class Workshop - extend ActiveModel::Naming - include ActiveModel::Conversion - attr_accessor :id - - def initialize(id) - @id = id - end - - def persisted? - id.present? - end - - def to_s - id.to_s - end -end - module ActionDispatch class DebugExceptions private @@ -377,37 +354,15 @@ module RoutingTestHelpers end end -class MetalRenderingController < ActionController::Metal - include AbstractController::Rendering - include ActionController::Rendering - include ActionController::Renderers -end - class ResourcesController < ActionController::Base def index() head :ok end alias_method :show, :index end -class ThreadsController < ResourcesController; end -class MessagesController < ResourcesController; end class CommentsController < ResourcesController; end -class ReviewsController < ResourcesController; end - class AccountsController < ResourcesController; end -class AdminController < ResourcesController; end -class ProductsController < ResourcesController; end class ImagesController < ResourcesController; end -module Backoffice - class ProductsController < ResourcesController; end - class ImagesController < ResourcesController; end - - module Admin - class ProductsController < ResourcesController; end - class ImagesController < ResourcesController; end - end -end - # Skips the current run on Rubinius using Minitest::Assertions#skip def rubinius_skip(message = '') skip message if RUBY_ENGINE == 'rbx' diff --git a/actionpack/test/controller/metal/renderers_test.rb b/actionpack/test/controller/metal/renderers_test.rb index 007866a559..247e872674 100644 --- a/actionpack/test/controller/metal/renderers_test.rb +++ b/actionpack/test/controller/metal/renderers_test.rb @@ -1,6 +1,12 @@ require 'abstract_unit' require 'active_support/core_ext/hash/conversions' +class MetalRenderingController < ActionController::Metal + include AbstractController::Rendering + include ActionController::Rendering + include ActionController::Renderers +end + class MetalRenderingJsonController < MetalRenderingController class Model def to_json(options = {}) diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index f83248402c..4af3c628d0 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -1,5 +1,23 @@ require 'abstract_unit' +class Workshop + extend ActiveModel::Naming + include ActiveModel::Conversion + attr_accessor :id + + def initialize(id) + @id = id + end + + def persisted? + id.present? + end + + def to_s + id.to_s + end +end + class RedirectController < ActionController::Base # empty method not used anywhere to ensure methods like # `status` and `location` aren't called on `redirect_to` calls diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 4490abf7b2..8e38af5025 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -3,8 +3,22 @@ require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/with_options' require 'active_support/core_ext/array/extract_options' -class ResourcesTest < ActionController::TestCase +class AdminController < ResourcesController; end +class MessagesController < ResourcesController; end +class ProductsController < ResourcesController; end +class ThreadsController < ResourcesController; end + +module Backoffice + class ProductsController < ResourcesController; end + class ImagesController < ResourcesController; end + + module Admin + class ProductsController < ResourcesController; end + class ImagesController < ResourcesController; end + end +end +class ResourcesTest < ActionController::TestCase def test_default_restful_routes with_restful_routing :messages do assert_simply_restful_for :messages diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 6160b3395a..ea59156f65 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -154,6 +154,10 @@ XML render html: '<body class="foo"></body>'.html_safe end + def boom + raise 'boom!' + end + private def generate_url(opts) @@ -981,6 +985,26 @@ XML assert_redirected_to 'created resource' end end + + def test_exception_in_action_reaches_test + assert_raise(RuntimeError) do + process :boom, method: "GET" + end + end + + def test_request_state_is_cleared_after_exception + assert_raise(RuntimeError) do + process :boom, + method: "GET", + params: { q: 'test1' } + end + + process :test_query_string, + method: "GET", + params: { q: 'test2' } + + assert_equal "q=test2", @response.body + end end class ResponseDefaultHeadersTest < ActionController::TestCase diff --git a/actionpack/test/dispatch/routing/concerns_test.rb b/actionpack/test/dispatch/routing/concerns_test.rb index 7ef513b0c8..6934271846 100644 --- a/actionpack/test/dispatch/routing/concerns_test.rb +++ b/actionpack/test/dispatch/routing/concerns_test.rb @@ -1,5 +1,7 @@ require 'abstract_unit' +class ReviewsController < ResourcesController; end + class RoutingConcernsTest < ActionDispatch::IntegrationTest class Reviewable def self.call(mapper, options = {}) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 696e6d31da..6f2c8c1c53 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -71,8 +71,8 @@ module ActiveModel # end def initialize(base) @base = base - @messages = Hash.new { |messages, attribute| messages[attribute] = [] } - @details = Hash.new { |details, attribute| details[attribute] = [] } + @messages = apply_default_array({}) + @details = apply_default_array({}) end def initialize_dup(other) # :nodoc: @@ -493,6 +493,16 @@ module ActiveModel I18n.translate(key, options) end + def marshal_dump + [@base, without_default_proc(@messages), without_default_proc(@details)] + end + + def marshal_load(array) + @base, @messages, @details = array + apply_default_array(@messages) + apply_default_array(@details) + end + private def normalize_message(attribute, message, options) case message @@ -506,6 +516,17 @@ module ActiveModel def normalize_detail(message, options) { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS)) end + + def without_default_proc(hash) + hash.dup.tap do |new_h| + new_h.default_proc = nil + end + end + + def apply_default_array(hash) + hash.default_proc = proc { |h, key| h[key] = [] } + hash + end end # Raised when a validation cannot be corrected by end users and are considered diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index fbf208836f..c90ee7021c 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -427,4 +427,13 @@ class ErrorsTest < ActiveModel::TestCase assert_equal [:name], person.errors.messages.keys assert_equal [:name], person.errors.details.keys end + + test "errors are marshalable" do + errors = ActiveModel::Errors.new(Person.new) + errors.add(:name, :invalid) + serialized = Marshal.load(Marshal.dump(errors)) + + assert_equal errors.messages, serialized.messages + assert_equal errors.details, serialized.details + end end diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 44c26e4f10..c589fd2c33 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -1,5 +1,4 @@ require 'active_model' -require 'active_support/core_ext/string/access' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true diff --git a/activemodel/test/models/project.rb b/activemodel/test/models/project.rb deleted file mode 100644 index 581b6dc0b3..0000000000 --- a/activemodel/test/models/project.rb +++ /dev/null @@ -1,3 +0,0 @@ -class Project - include ActiveModel::DeprecatedMassAssignmentSecurity -end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 929f241b1b..a1a4d2646f 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* Ensure hashes can be assigned to attributes created using `composed_of`. + Fixes #25210. + + *Sean Griffin* + * Fix logging edge case where if an attribute was of the binary type and was provided as a Hash. diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 3ff41ed81b..8bed5bca28 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -256,15 +256,16 @@ module ActiveRecord def writer_method(name, class_name, mapping, allow_nil, converter) define_method("#{name}=") do |part| klass = class_name.constantize - if part.is_a?(Hash) - raise ArgumentError unless part.size == part.keys.max - part = klass.new(*part.sort.map(&:last)) - end unless part.is_a?(klass) || converter.nil? || part.nil? part = converter.respond_to?(:call) ? converter.call(part) : klass.send(converter, part) end + if part.is_a?(Hash) + raise ArgumentError unless part.size == part.keys.max + part = klass.new(*part.sort.map(&:last)) + end + if part.nil? && allow_nil mapping.each { |key, _| self[key] = nil } @aggregation_cache[name] = nil diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index b94feeff12..491152bbcb 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -92,8 +92,9 @@ module ActiveRecord # associations # => [:appointments] # joins # => [] # - def initialize(base, associations, joins) + def initialize(base, associations, joins, eager_loading: true) @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster) + @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @join_root.children.each { |child| construct_tables! @join_root, child } @@ -238,11 +239,12 @@ module ActiveRecord reflection.check_eager_loadable! if reflection.polymorphic? + next unless @eager_loading raise EagerLoadPolymorphicError.new(reflection) end JoinAssociation.new reflection, build(right, reflection.klass) - end + end.compact end def construct(ar_parent, parent, row, rs, seen, model_cache, aliases) diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 24231dc9e1..701d24da88 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -108,6 +108,22 @@ module ActiveRecord [self.class, name, value_before_type_cast, type].hash end + def init_with(coder) + @name = coder["name"] + @value_before_type_cast = coder["value_before_type_cast"] + @type = coder["type"] + @original_attribute = coder["original_attribute"] + @value = coder["value"] if coder.map.key?("value") + end + + def encode_with(coder) + coder["name"] = name + coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast + coder["type"] = type if type + coder["original_attribute"] = original_attribute if original_attribute + coder["value"] = value if defined?(@value) + end + protected attr_reader :original_attribute @@ -201,6 +217,10 @@ module ActiveRecord def initialized? false end + + def with_type(type) + self.class.new(name, type) + end end private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue end diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index be581ac2a9..720d5f8b7c 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -1,7 +1,10 @@ require 'active_record/attribute_set/builder' +require 'active_record/attribute_set/yaml_encoder' module ActiveRecord class AttributeSet # :nodoc: + delegate :each_value, to: :attributes + def initialize(attributes) @attributes = attributes end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 3bd7c7997b..24a255efc1 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -22,7 +22,7 @@ module ActiveRecord end class LazyAttributeHash # :nodoc: - delegate :transform_values, :each_key, to: :materialize + delegate :transform_values, :each_key, :each_value, to: :materialize def initialize(types, values, additional_types) @types = types diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb new file mode 100644 index 0000000000..6208048231 --- /dev/null +++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb @@ -0,0 +1,39 @@ +module ActiveRecord + class AttributeSet + # Attempts to do more intelligent YAML dumping of an + # ActiveRecord::AttributeSet to reduce the size of the resulting string + class YAMLEncoder + def initialize(default_types) + @default_types = default_types + end + + def encode(attribute_set, coder) + coder['concise_attributes'] = attribute_set.each_value.map do |attr| + if attr.type.equal?(default_types[attr.name]) + attr.with_type(nil) + else + attr + end + end + end + + def decode(coder) + if coder['attributes'] + coder['attributes'] + else + attributes_hash = Hash[coder['concise_attributes'].map do |attr| + if attr.type.nil? + attr = attr.with_type(default_types[attr.name]) + end + [attr.name, attr] + end] + AttributeSet.new(attributes_hash) + end + end + + protected + + attr_reader :default_types + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 99a3e99bdc..5939ee9956 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -847,14 +847,19 @@ module ActiveRecord # # remove_reference(:products, :user, index: true, foreign_key: true) # - def remove_reference(table_name, ref_name, options = {}) - if options[:foreign_key] + def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options) + if foreign_key reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name - remove_foreign_key(table_name, reference_name) + if foreign_key.is_a?(Hash) + foreign_key_options = foreign_key + else + foreign_key_options = { to_table: reference_name } + end + remove_foreign_key(table_name, **foreign_key_options) end remove_column(table_name, "#{ref_name}_id") - remove_column(table_name, "#{ref_name}_type") if options[:polymorphic] + remove_column(table_name, "#{ref_name}_type") if polymorphic end alias :remove_belongs_to :remove_reference 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 44b4b547f3..718a6c5b91 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -41,14 +41,14 @@ module ActiveRecord NATIVE_DATABASE_TYPES = { primary_key: "int auto_increment PRIMARY KEY", string: { name: "varchar", limit: 255 }, - text: { name: "text" }, + text: { name: "text", limit: 65535 }, integer: { name: "int", limit: 4 }, float: { name: "float" }, decimal: { name: "decimal" }, datetime: { name: "datetime" }, time: { name: "time" }, date: { name: "date" }, - binary: { name: "blob" }, + binary: { name: "blob", limit: 65535 }, boolean: { name: "tinyint", limit: 1 }, json: { name: "json" }, } diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index f936e865e4..de337b24d6 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -338,7 +338,7 @@ module ActiveRecord # post.title # => 'hello world' def init_with(coder) coder = LegacyYamlAdapter.convert(self.class, coder) - @attributes = coder['attributes'] + @attributes = self.class.yaml_encoder.decode(coder) init_internals @@ -404,11 +404,9 @@ module ActiveRecord # Post.new.encode_with(coder) # coder # => {"attributes" => {"id" => nil, ... }} def encode_with(coder) - # FIXME: Remove this when we better serialize attributes - coder['raw_attributes'] = attributes_before_type_cast - coder['attributes'] = @attributes + self.class.yaml_encoder.encode(@attributes, coder) coder['new_record'] = new_record? - coder['active_record_yaml_version'] = 1 + coder['active_record_yaml_version'] = 2 end # Returns true if +comparison_object+ is the same exact object, or +comparison_object+ @@ -432,7 +430,7 @@ module ActiveRecord # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ] def hash if id - id.hash + [self.class, id].hash else super end diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb index 89dee58423..c7683f68c7 100644 --- a/activerecord/lib/active_record/legacy_yaml_adapter.rb +++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb @@ -4,7 +4,7 @@ module ActiveRecord return coder unless coder.is_a?(Psych::Coder) case coder["active_record_yaml_version"] - when 1 then coder + when 1, 2 then coder else if coder["attributes"].is_a?(AttributeSet) Rails420.convert(klass, coder) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index f691a8319d..7996c32bbc 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -267,6 +267,10 @@ module ActiveRecord @attribute_types ||= Hash.new(Type::Value.new) end + def yaml_encoder # :nodoc: + @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types) + end + # Returns the type of the attribute with the given name, after applying # all modifiers. This method is the only valid source of information for # anything related to the types of a model's attributes. This method will @@ -375,6 +379,7 @@ module ActiveRecord @columns = nil @columns_hash = nil @attribute_names = nil + @yaml_encoder = nil direct_descendants.each do |descendant| descendant.send(:reload_schema_from_cache) end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d042fe5f8b..7a1552856b 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -279,12 +279,7 @@ module ActiveRecord def empty? return @records.empty? if loaded? - if limit_value == 0 - true - else - c = count(:all) - c.respond_to?(:zero?) ? c.zero? : c.empty? - end + limit_value == 0 || !exists? end # Returns true if there are no records. diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index e7e331f88d..87eea8277a 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -318,7 +318,7 @@ module ActiveRecord return false if !conditions - relation = apply_join_dependency(self, construct_join_dependency) + relation = apply_join_dependency(self, construct_join_dependency(eager_loading: false)) return false if ActiveRecord::NullRelation === relation relation = relation.except(:select, :order).select(ONE_AS_ONE).limit(1) @@ -392,9 +392,9 @@ module ActiveRecord end end - def construct_join_dependency(joins = []) + def construct_join_dependency(joins = [], eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(@klass, including, joins) + ActiveRecord::Associations::JoinDependency.new(@klass, including, joins, eager_loading: eager_loading) end def construct_relation_for_association_calculations diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb index d7fd878265..413cb9fd84 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb @@ -2,13 +2,14 @@ module ActiveRecord class PredicateBuilder class AssociationQueryHandler # :nodoc: def self.value_for(table, column, value) - klass = if table.associated_table(column).polymorphic_association? && ::Array === value && value.first.is_a?(Base) + associated_table = table.associated_table(column) + klass = if associated_table.polymorphic_association? && ::Array === value && value.first.is_a?(Base) PolymorphicArrayValue else AssociationQueryValue end - klass.new(table.associated_table(column), value) + klass.new(associated_table, value) end def initialize(predicate_builder) diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb index 668c07dacb..c8028b6b36 100644 --- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb +++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb @@ -49,6 +49,6 @@ class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase test "schema dump includes collation" do output = dump_table_schema("charset_collations") assert_match %r{t.string\s+"string_ascii_bin",\s+collation: "ascii_bin"$}, output - assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+limit: 65535,\s+collation: "ucs2_unicode_ci"$}, output + assert_match %r{t.text\s+"text_ucs2_unicode_ci",\s+collation: "ucs2_unicode_ci"$}, output end end diff --git a/activerecord/test/cases/aggregations_test.rb b/activerecord/test/cases/aggregations_test.rb index 5536702f58..8a728902a8 100644 --- a/activerecord/test/cases/aggregations_test.rb +++ b/activerecord/test/cases/aggregations_test.rb @@ -138,6 +138,11 @@ class AggregationsTest < ActiveRecord::TestCase assert_equal 'Barnoit GUMBLEAU', customers(:barney).fullname.to_s assert_kind_of Fullname, customers(:barney).fullname end + + def test_assigning_hash_to_custom_converter + customers(:barney).fullname = { first: "Barney", last: "Stinson" } + assert_equal "Barney STINSON", customers(:barney).name + end end class OverridingAggregationsTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 7f2a2229ee..f7bb3e54d5 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1341,6 +1341,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_nothing_raised do authors(:david).essays.includes(:writer).any? + authors(:david).essays.includes(:writer).exists? end end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 3191393a41..80dcba1cf4 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1504,6 +1504,10 @@ class BasicsTest < ActiveRecord::TestCase assert_not_equal Post.new.hash, Post.new.hash end + test "records of different classes have different hashes" do + assert_not_equal Post.new(id: 1).hash, Developer.new(id: 1).hash + end + test "resetting column information doesn't remove attribute methods" do topic = topics(:first) diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index e030f6c588..aba854820b 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -151,6 +151,14 @@ module ActiveRecord end end + class RevertCustomForeignKeyTable < SilentMigration + def change + change_table(:horses) do |t| + t.references :owner, foreign_key: { to_table: :developers } + end + end + end + setup do @verbose_was, ActiveRecord::Migration.verbose = ActiveRecord::Migration.verbose, false end @@ -353,6 +361,13 @@ module ActiveRecord ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = '' end + def test_migrations_can_handle_foreign_keys_to_specific_tables + migration = RevertCustomForeignKeyTable.new + InvertibleMigration.migrate(:up) + migration.migrate(:up) + migration.migrate(:down) + end + # MySQL 5.7 and Oracle do not allow to create duplicate indexes on the same columns unless current_adapter?(:Mysql2Adapter, :OracleAdapter) def test_migrate_revert_add_index_with_name diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 9dc1f5e2c2..12f4196724 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -218,12 +218,17 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t\.boolean\s+"has_fun",.+default: false}, output end - if current_adapter?(:Mysql2Adapter) - def test_schema_dump_should_add_default_value_for_mysql_text_field - output = standard_dump - assert_match %r{t\.text\s+"body",\s+limit: 65535,\s+null: false$}, output - end + def test_schema_dump_does_not_include_limit_for_text_field + output = standard_dump + assert_match %r{t\.text\s+"params"$}, output + end + def test_schema_dump_does_not_include_limit_for_binary_field + output = standard_dump + assert_match %r{t\.binary\s+"data"$}, output + end + + if current_adapter?(:Mysql2Adapter) def test_schema_dump_includes_length_for_mysql_binary_fields output = standard_dump assert_match %r{t\.binary\s+"var_binary",\s+limit: 255$}, output @@ -233,11 +238,11 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dump_includes_length_for_mysql_blob_and_text_fields output = standard_dump assert_match %r{t\.blob\s+"tiny_blob",\s+limit: 255$}, output - assert_match %r{t\.binary\s+"normal_blob",\s+limit: 65535$}, output + assert_match %r{t\.binary\s+"normal_blob"$}, output assert_match %r{t\.binary\s+"medium_blob",\s+limit: 16777215$}, output assert_match %r{t\.binary\s+"long_blob",\s+limit: 4294967295$}, output assert_match %r{t\.text\s+"tiny_text",\s+limit: 255$}, output - assert_match %r{t\.text\s+"normal_text",\s+limit: 65535$}, output + assert_match %r{t\.text\s+"normal_text"$}, output assert_match %r{t\.text\s+"medium_text",\s+limit: 16777215$}, output assert_match %r{t\.text\s+"long_text",\s+limit: 4294967295$}, output end diff --git a/activerecord/test/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 56909a8630..d1c9a00786 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -109,6 +109,16 @@ class YamlSerializationTest < ActiveRecord::TestCase assert_equal("Have a nice day", topic.content) end + def test_yaml_encoding_keeps_mutations + author = Author.first + author.name = "Sean" + dumped = YAML.load(YAML.dump(author)) + + assert_equal "Sean", dumped.name + assert_equal author.name_was, dumped.name_was + assert_equal author.changes, dumped.changes + end + private def yaml_fixture(file_name) diff --git a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb index 607113b091..76734bcd7d 100644 --- a/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb +++ b/activerecord/test/migrations/to_copy/1_people_have_hobbies.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveHobbies < ActiveRecord::Migration::Current def self.up add_column "people", "hobbies", :text end diff --git a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb index d4cbddab50..7f883dbb45 100644 --- a/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb +++ b/activerecord/test/migrations/to_copy/2_people_have_descriptions.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveDescriptions < ActiveRecord::Migration::Current def self.up add_column "people", "description", :text end diff --git a/activerecord/test/migrations/to_copy2/2_create_comments.rb b/activerecord/test/migrations/to_copy2/2_create_comments.rb index 2e9f5ec6bc..d361847d4b 100644 --- a/activerecord/test/migrations/to_copy2/2_create_comments.rb +++ b/activerecord/test/migrations/to_copy2/2_create_comments.rb @@ -1,4 +1,4 @@ -class CreateArticles < ActiveRecord::Migration::Current +class CreateComments < ActiveRecord::Migration::Current def self.up end diff --git a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb index 8f81805fe1..1a863367dd 100644 --- a/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb +++ b/activerecord/test/migrations/to_copy_with_name_collision/1_people_have_hobbies.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveHobbies < ActiveRecord::Migration::Current def self.up add_column "people", "hobbies", :string end diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb index 607113b091..76734bcd7d 100644 --- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb +++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveHobbies < ActiveRecord::Migration::Current def self.up add_column "people", "hobbies", :text end diff --git a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb index d4cbddab50..7f883dbb45 100644 --- a/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb +++ b/activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb @@ -1,4 +1,4 @@ -class PeopleHaveLastNames < ActiveRecord::Migration::Current +class PeopleHaveDescriptions < ActiveRecord::Migration::Current def self.up add_column "people", "description", :text end diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index afe4b3d707..3338aaf7e1 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -64,7 +64,12 @@ class Fullname def self.parse(str) return nil unless str - new(*str.to_s.split) + + if str.is_a?(Hash) + new(str[:first], str[:last]) + else + new(*str.to_s.split) + end end def initialize(first, last = nil) diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 2dde01c844..721efea789 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -97,7 +97,7 @@ module ActiveSupport end def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) + OpenSSL::Cipher.new(@cipher) end def verifier diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 7aee28c74a..f0a3289563 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -308,7 +308,7 @@ module Rails def jbuilder_gemfile_entry comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder' - GemfileEntry.new 'jbuilder', '~> 2.0', comment, {}, options[:api] + GemfileEntry.new 'jbuilder', '~> 2.5', comment, {}, options[:api] end def coffee_gemfile_entry |