diff options
238 files changed, 2634 insertions, 1135 deletions
diff --git a/.travis.yml b/.travis.yml index 869507a9fd..ae38617b99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,6 @@ rvm: matrix: allow_failures: - rvm: ruby-head - - env: "GEM=ac" fast_finish: true notifications: email: false @@ -12,7 +12,7 @@ gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'jquery-rails' gem 'coffee-rails', '~> 4.1.0' -gem 'turbolinks' +gem 'turbolinks', github: 'turbolinks/turbolinks-rails' # require: false so bcrypt is loaded only when has_secure_password is used. # This is to avoid Active Model (and by extension the entire framework) @@ -65,6 +65,7 @@ group :cable do gem 'puma', require: false gem 'em-hiredis', require: false + gem 'hiredis', require: false gem 'redis', require: false gem 'faye-websocket', require: false @@ -102,7 +103,6 @@ platforms :ruby, :mswin, :mswin64, :mingw, :x64_mingw do end platforms :jruby do - gem 'json' if ENV['AR_JDBC'] gem 'activerecord-jdbcsqlite3-adapter', github: 'jruby/activerecord-jdbc-adapter', branch: 'master' group :db do diff --git a/Gemfile.lock b/Gemfile.lock index d2d616b39d..0d2ce7b97f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,64 +26,68 @@ GIT specs: sass (3.4.21) +GIT + remote: git://github.com/turbolinks/turbolinks-rails.git + revision: 1604dcd7bad911f1471d65e4f47cd19d844354f1 + specs: + turbolinks (5.0.0.beta1) + turbolinks-source + PATH remote: . specs: - actioncable (5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - coffee-rails (~> 4.1.0) + actioncable (5.0.0.beta2) + actionpack (= 5.0.0.beta2) nio4r (~> 1.2) websocket-driver (~> 0.6.1) - actionmailer (5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - actionview (= 5.0.0.beta1.1) - activejob (= 5.0.0.beta1.1) + actionmailer (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + actionview (= 5.0.0.beta2) + activejob (= 5.0.0.beta2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (5.0.0.beta1.1) - actionview (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + actionpack (5.0.0.beta2) + actionview (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) rack (~> 2.x) rack-test (~> 0.6.3) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + actionview (5.0.0.beta2) + activesupport (= 5.0.0.beta2) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + activejob (5.0.0.beta2) + activesupport (= 5.0.0.beta2) globalid (>= 0.3.6) - activemodel (5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) - activerecord (5.0.0.beta1.1) - activemodel (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + activemodel (5.0.0.beta2) + activesupport (= 5.0.0.beta2) + activerecord (5.0.0.beta2) + activemodel (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) arel (~> 7.0) - activesupport (5.0.0.beta1.1) + activesupport (5.0.0.beta2) concurrent-ruby (~> 1.0) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - method_source minitest (~> 5.1) tzinfo (~> 1.1) - rails (5.0.0.beta1.1) - actioncable (= 5.0.0.beta1.1) - actionmailer (= 5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - actionview (= 5.0.0.beta1.1) - activejob (= 5.0.0.beta1.1) - activemodel (= 5.0.0.beta1.1) - activerecord (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + rails (5.0.0.beta2) + actioncable (= 5.0.0.beta2) + actionmailer (= 5.0.0.beta2) + actionpack (= 5.0.0.beta2) + actionview (= 5.0.0.beta2) + activejob (= 5.0.0.beta2) + activemodel (= 5.0.0.beta2) + activerecord (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) bundler (>= 1.3.0, < 2.0) - railties (= 5.0.0.beta1.1) + railties (= 5.0.0.beta2) sprockets-rails (>= 2.0.0) - railties (5.0.0.beta1.1) - actionpack (= 5.0.0.beta1.1) - activesupport (= 5.0.0.beta1.1) + railties (5.0.0.beta2) + actionpack (= 5.0.0.beta2) + activesupport (= 5.0.0.beta2) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -165,7 +169,7 @@ GEM mysql2 (0.4.2) mysql2 (0.4.2-x64-mingw32) mysql2 (0.4.2-x86-mingw32) - nio4r (1.2.0) + nio4r (1.2.1) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) nokogiri (1.6.7.2-x64-mingw32) @@ -235,7 +239,7 @@ GEM sprockets (3.5.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.0.0) + sprockets-rails (3.0.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -248,8 +252,7 @@ GEM thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - turbolinks (2.5.3) - coffee-rails + turbolinks-source (5.0.0.beta1.1) tzinfo (1.2.2) thread_safe (~> 0.1) tzinfo-data (1.2015.7) @@ -286,8 +289,8 @@ DEPENDENCIES delayed_job_active_record em-hiredis faye-websocket + hiredis jquery-rails - json kindlerb (= 0.1.1) listen (~> 3.0.5) minitest (< 5.3.4) @@ -317,7 +320,7 @@ DEPENDENCIES sqlite3 (~> 1.3.6) stackprof sucker_punch - turbolinks + turbolinks! tzinfo-data uglifier (>= 1.3.0) w3c_validators diff --git a/RAILS_VERSION b/RAILS_VERSION index 8422265fdd..60b8d0bf66 100644 --- a/RAILS_VERSION +++ b/RAILS_VERSION @@ -1 +1 @@ -5.0.0.beta1.1 +5.0.0.beta2 @@ -64,7 +64,7 @@ and may also be used independently outside Rails. Run with `--help` or `-h` for options. 4. Using a browser, go to `http://localhost:3000` and you'll see: -"Welcome aboard: You're riding Ruby on Rails!" +"Yay! You’re on Rails!" 5. Follow the guidelines to start developing your application. You may find the following resources handy: diff --git a/RELEASING_RAILS.md b/RELEASING_RAILS.md index faf8fa7f0d..83586c22c0 100644 --- a/RELEASING_RAILS.md +++ b/RELEASING_RAILS.md @@ -19,13 +19,13 @@ http://travis-ci.org/rails/rails ### Is Sam Ruby happy? If not, make him happy. -Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile -Web Development with Rails) all work. These are valuable integration tests -for Rails. You can check the status of his tests here: +Sam Ruby keeps a [test suite](https://github.com/rubys/awdwr) that makes +sure the code samples in his book +([Agile Web Development with Rails](https://pragprog.com/titles/rails4/agile-web-development-with-rails-4th-edition)) +all work. These are valuable system tests +for Rails. You can check the status of these tests here: -``` -http://intertwingly.net/projects/dashboard.html -``` +[http://intertwingly.net/projects/dashboard.html](http://intertwingly.net/projects/dashboard.html) Do not release with Red AWDwR tests. @@ -212,4 +212,4 @@ There are two simple steps for fixing the CI: 1. Identify the problem 2. Fix it -Repeat these steps until the CI is green.
\ No newline at end of file +Repeat these steps until the CI is green. diff --git a/actioncable/.gitignore b/actioncable/.gitignore new file mode 100644 index 0000000000..0a04b29786 --- /dev/null +++ b/actioncable/.gitignore @@ -0,0 +1,2 @@ +/lib/assets/compiled +/tmp diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index a33b7b6b4b..bfc229d795 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,24 @@ +* Added ActionCable::SubscriptionAdapter::EventedRedis.em_redis_connector/redis_connector and + ActionCable::SubscriptionAdapter::Redis.redis_connector factory methods for redis connections, + so you can overwrite with your own initializers. This is used when you want to use different-than-standard Redis adapters, + like for Makara distributed Redis. + + *DHH* + +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* Support PostgreSQL pubsub adapter. + + *Jon Moss* + +* Remove EventMachine dependency. + + *Matthew Draper* + +* Remove Celluloid dependency. + + *Mike Perham* + * Create notion of an `ActionCable::SubscriptionAdapter`. Separate out Redis functionality into `ActionCable::SubscriptionAdapter::Redis`, and add a diff --git a/actioncable/README.md b/actioncable/README.md index ac57532b62..3ed8a23a29 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -17,7 +17,7 @@ The client of a WebSocket connection is called the consumer. Each consumer can in turn subscribe to multiple cable channels. Each channel encapsulates a logical unit of work, similar to what a controller does in a regular MVC setup. For example, -you could have a `ChatChannel` and a `AppearancesChannel`, and a consumer could be subscribed to either +you could have a `ChatChannel` and an `AppearancesChannel`, and a consumer could be subscribed to either or to both of these channels. At the very least, a consumer should be subscribed to one channel. When the consumer is subscribed to a channel, they act as a subscriber. The connection between @@ -188,7 +188,7 @@ can be reached as remote procedure calls via a subscription's `perform` method. The appearance example was all about exposing server functionality to client-side invocation over the WebSocket connection. But the great thing about WebSockets is that it's a two-way street. So now let's show an example where the server invokes -action on the client. +an action on the client. This is a web notification channel that allows you to trigger client-side web notifications when you broadcast to the right streams: @@ -324,7 +324,7 @@ Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml" Action Cable will only accept requests from specified origins, which are passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for match will be performed. ```ruby -ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] +Rails.application.config.action_cable.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/] ``` When running in the development environment, this defaults to "http://localhost:3000". @@ -332,7 +332,7 @@ When running in the development environment, this defaults to "http://localhost: To disable and allow requests from any origin: ```ruby -ActionCable.server.config.disable_request_forgery_protection = true +Rails.application.config.action_cable.disable_request_forgery_protection = true ``` ### Consumer Configuration @@ -374,7 +374,7 @@ App.cable = ActionCable.createConsumer() The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp: ```ruby -ActionCable.server.config.log_tags = [ +Rails.application.config.action_cable.log_tags = [ -> request { request.env['bc.account_id'] || "no-account" }, :action_cable, -> request { request.uuid } @@ -436,7 +436,7 @@ messages back and forth over the WebSocket cable connection. This dependency may be alleviated in the future, but for the moment that's what it is. So be sure to have Redis installed and running. -The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). +The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby). ## Deployment @@ -448,8 +448,17 @@ as long as you haven’t committed any thread-safety sins. But this also means that Action Cable needs to run in its own server process. So you'll have one set of server processes for your normal web work, and another -set of server processes for the Action Cable. The former can be single-threaded, -like Unicorn, but the latter must be multi-threaded, like Puma. +set of server processes for the Action Cable. + +The Action Cable server does _not_ need to be a multi-threaded application server. +This is because Action Cable uses the [Rack socket hijacking API](http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/) +to take over control of connections from the application server. Action Cable +then manages connections internally, in a multithreaded manner, regardless of +whether the application server is multi-threaded or not. So Action Cable works +with all the popular application servers -- Unicorn, Puma and Passenger. + +Action Cable does not work with WEBrick, because WEBrick does not support the +Rack socket hijacking API. ## License diff --git a/actioncable/Rakefile b/actioncable/Rakefile index b6c56e9195..1d77fc7067 100644 --- a/actioncable/Rakefile +++ b/actioncable/Rakefile @@ -1,9 +1,16 @@ require 'rake/testtask' +require 'pathname' +require 'sprockets' +require 'coffee-script' +require 'action_cable' dir = File.dirname(__FILE__) task :default => :test +task :package => "assets:compile" +task "package:clean" => "assets:clean" + Rake::TestTask.new do |t| t.libs << "test" t.test_files = Dir.glob("#{dir}/test/**/*_test.rb") @@ -11,3 +18,40 @@ Rake::TestTask.new do |t| t.verbose = true t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end + +namespace :assets do + root_path = Pathname.new(dir) + destination_path = root_path.join("lib/assets/compiled") + + desc "Compile dist/action_cable.js" + task :compile do + puts 'Compiling Action Cable assets...' + + precompile_list = %w(action_cable.js) + + environment = Sprockets::Environment.new + environment.gzip = false + Pathname.glob(root_path.join("app/assets/*/")) do |subdir| + environment.append_path subdir + end + + compile_path = root_path.join("tmp/sprockets") + compile_path.rmtree if compile_path.exist? + compile_path.mkpath + + manifest = Sprockets::Manifest.new(environment.index, compile_path) + manifest.compile(precompile_list) + + destination_path.rmtree if destination_path.exist? + manifest.assets.each do |path, fingerprint_path| + destination_path.join(path).dirname.mkpath + FileUtils.cp(compile_path.join(fingerprint_path), destination_path.join(path)) + end + + puts 'Done' + end + + task :clean do + destination_path.rmtree if destination_path.exist? + end +end diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 14f968f1ef..c65ff7871f 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -20,13 +20,6 @@ Gem::Specification.new do |s| s.add_dependency 'actionpack', version - s.add_dependency 'coffee-rails', '~> 4.1.0' s.add_dependency 'nio4r', '~> 1.2' s.add_dependency 'websocket-driver', '~> 0.6.1' - - s.add_development_dependency 'em-hiredis', '~> 0.3.0' - s.add_development_dependency 'mocha' - s.add_development_dependency 'pg' - s.add_development_dependency 'puma' - s.add_development_dependency 'redis', '~> 3.0' end diff --git a/actioncable/lib/assets/javascripts/action_cable.coffee.erb b/actioncable/app/assets/javascripts/action_cable.coffee.erb index 7daea4ebcd..18a48c0610 100644 --- a/actioncable/lib/assets/javascripts/action_cable.coffee.erb +++ b/actioncable/app/assets/javascripts/action_cable.coffee.erb @@ -1,5 +1,5 @@ #= require_self -#= require action_cable/consumer +#= require ./action_cable/consumer @ActionCable = INTERNAL: <%= ActionCable::INTERNAL.to_json %> diff --git a/actioncable/lib/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee index fbd7dbd35b..fbd7dbd35b 100644 --- a/actioncable/lib/assets/javascripts/action_cable/connection.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee diff --git a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee index 99b9a1c6d5..99b9a1c6d5 100644 --- a/actioncable/lib/assets/javascripts/action_cable/connection_monitor.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection_monitor.coffee diff --git a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee b/actioncable/app/assets/javascripts/action_cable/consumer.coffee index fcd8d0fb6c..717c0641a9 100644 --- a/actioncable/lib/assets/javascripts/action_cable/consumer.coffee +++ b/actioncable/app/assets/javascripts/action_cable/consumer.coffee @@ -1,7 +1,7 @@ -#= require action_cable/connection -#= require action_cable/connection_monitor -#= require action_cable/subscriptions -#= require action_cable/subscription +#= require ./connection +#= require ./connection_monitor +#= require ./subscriptions +#= require ./subscription # The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, # the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. diff --git a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee b/actioncable/app/assets/javascripts/action_cable/subscription.coffee index 339d676933..339d676933 100644 --- a/actioncable/lib/assets/javascripts/action_cable/subscription.coffee +++ b/actioncable/app/assets/javascripts/action_cable/subscription.coffee diff --git a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee index ae041ffa2b..ae041ffa2b 100644 --- a/actioncable/lib/assets/javascripts/action_cable/subscriptions.coffee +++ b/actioncable/app/assets/javascripts/action_cable/subscriptions.coffee diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index 88cdc1cab1..874ebe2e71 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -32,8 +32,11 @@ module ActionCable # # == Action processing # - # Unlike Action Controllers, channels do not follow a REST constraint form for its actions. It's a remote-procedure call model. You can - # declare any public method on the channel (optionally taking a data argument), and this method is automatically exposed as callable to the client. + # Unlike subclasses of ActionController::Base, channels do not follow a REST + # constraint form for their actions. Instead, ActionCable operates through a + # remote-procedure call model. You can declare any public method on the + # channel (optionally taking a <tt>data</tt> argument), and this method is + # automatically exposed as callable to the client. # # Example: # @@ -60,18 +63,22 @@ module ActionCable # end # end # - # In this example, subscribed/unsubscribed are not callable methods, as they were already declared in ActionCable::Channel::Base, but #appear/away - # are. #generate_connection_token is also not callable as its a private method. You'll see that appear accepts a data parameter, which it then - # uses as part of its model call. #away does not, it's simply a trigger action. + # In this example, subscribed/unsubscribed are not callable methods, as they + # were already declared in ActionCable::Channel::Base, but <tt>#appear</tt> + # and <tt>#away</tt> are. <tt>#generate_connection_token</tt> is also not + # callable as it's a private method. You'll see that appear accepts a data + # parameter, which it then uses as part of its model call. <tt>#away</tt> + # does not, since it's simply a trigger action. # - # Also note that in this example, current_user is available because it was marked as an identifying attribute on the connection. - # All such identifiers will automatically create a delegation method of the same name on the channel instance. + # Also note that in this example, <tt>current_user</tt> is available because + # it was marked as an identifying attribute on the connection. All such + # identifiers will automatically create a delegation method of the same name + # on the channel instance. # # == Rejecting subscription requests # - # A channel can reject a subscription request in the #subscribed callback by invoking #reject! - # - # Example: + # A channel can reject a subscription request in the #subscribed callback by + # invoking the #reject method: # # class ChatChannel < ApplicationCable::Channel # def subscribed @@ -80,8 +87,10 @@ module ActionCable # end # end # - # In this example, the subscription will be rejected if the current_user does not have access to the chat room. - # On the client-side, Channel#rejected callback will get invoked when the server rejects the subscription request. + # In this example, the subscription will be rejected if the + # <tt>current_user</tt> does not have access to the chat room. On the + # client-side, the <tt>Channel#rejected</tt> callback will get invoked when + # the server rejects the subscription request. class Base include Callbacks include PeriodicTimers diff --git a/actioncable/lib/action_cable/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb index a26373e387..3158f30814 100644 --- a/actioncable/lib/action_cable/channel/streams.rb +++ b/actioncable/lib/action_cable/channel/streams.rb @@ -41,22 +41,23 @@ module ActionCable # Example below shows how you can use this to provide performance introspection in the process: # # class ChatChannel < ApplicationCable::Channel - # def subscribed - # @room = Chat::Room[params[:room_number]] + # def subscribed + # @room = Chat::Room[params[:room_number]] # - # stream_for @room, -> (encoded_message) do - # message = ActiveSupport::JSON.decode(encoded_message) + # stream_for @room, -> (encoded_message) do + # message = ActiveSupport::JSON.decode(encoded_message) # - # if message['originated_at'].present? - # elapsed_time = (Time.now.to_f - message['originated_at']).round(2) + # if message['originated_at'].present? + # elapsed_time = (Time.now.to_f - message['originated_at']).round(2) # - # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing - # logger.info "Message took #{elapsed_time}s to arrive" - # end + # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing + # logger.info "Message took #{elapsed_time}s to arrive" + # end # - # transmit message - # end - # end + # transmit message + # end + # end + # end # # You can stop streaming from all broadcasts by calling #stop_all_streams. module Streams @@ -90,6 +91,7 @@ module ActionCable stream_from(broadcasting_for([ channel_name, model ]), callback) end + # Unsubscribes all streams associated with this channel from the pubsub queue. def stop_all_streams streams.each do |broadcasting, callback| pubsub.unsubscribe broadcasting, callback diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index b5f898436a..e23789978c 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -185,12 +185,14 @@ module ActionCable end def respond_to_successful_request + logger.info successful_request_message websocket.rack_response end def respond_to_invalid_request close if websocket.alive? + logger.error invalid_request_message logger.info finished_request_message [ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ] end @@ -205,7 +207,7 @@ module ActionCable 'Started %s "%s"%s for %s at %s' % [ request.request_method, request.filtered_path, - websocket.possible? ? ' [WebSocket]' : '', + websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]', request.ip, Time.now.to_s ] end @@ -213,10 +215,22 @@ module ActionCable def finished_request_message 'Finished "%s"%s for %s at %s' % [ request.filtered_path, - websocket.possible? ? ' [WebSocket]' : '', + websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]', request.ip, Time.now.to_s ] end + + def invalid_request_message + 'Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [ + env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"] + ] + end + + def successful_request_message + 'Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [ + env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"] + ] + end end end end diff --git a/actioncable/lib/action_cable/gem_version.rb b/actioncable/lib/action_cable/gem_version.rb index c652fb91ae..a71603e61a 100644 --- a/actioncable/lib/action_cable/gem_version.rb +++ b/actioncable/lib/action_cable/gem_version.rb @@ -8,7 +8,7 @@ module ActionCable MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actioncable/lib/action_cable/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb index aa2fc95d2f..7ec121308a 100644 --- a/actioncable/lib/action_cable/remote_connections.rb +++ b/actioncable/lib/action_cable/remote_connections.rb @@ -1,6 +1,7 @@ module ActionCable - # If you need to disconnect a given connection, you go through the RemoteConnections. You find the connections you're looking for by - # searching the identifier declared on the connection. Example: + # If you need to disconnect a given connection, you can go through the + # RemoteConnections. You can find the connections you're looking for by + # searching for the identifier declared on the connection. For example: # # module ApplicationCable # class Connection < ActionCable::Connection::Base @@ -11,8 +12,9 @@ module ActionCable # # ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect # - # That will disconnect all the connections established for User.find(1) across all servers running on all machines (because it uses - # the internal channel that all these servers are subscribed to). + # This will disconnect all the connections established for + # <tt>User.find(1)</tt> across all servers running on all machines, because + # it uses the internal channel that all these servers are subscribed to. class RemoteConnections attr_reader :server @@ -25,7 +27,7 @@ module ActionCable end private - # Represents a single remote connection found via ActionCable.server.remote_connections.where(*). + # Represents a single remote connection found via <tt>ActionCable.server.remote_connections.where(*)</tt>. # Exists for the solely for the purpose of calling #disconnect on that connection. class RemoteConnection class InvalidIdentifiersError < StandardError; end diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb index 4a26ed9269..7e8aef45f4 100644 --- a/actioncable/lib/action_cable/server/broadcasting.rb +++ b/actioncable/lib/action_cable/server/broadcasting.rb @@ -4,19 +4,19 @@ module ActionCable # broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example: # # class WebNotificationsChannel < ApplicationCable::Channel - # def subscribed - # stream_from "web_notifications_#{current_user.id}" - # end - # end + # def subscribed + # stream_from "web_notifications_#{current_user.id}" + # end + # end # - # # Somewhere in your app this is called, perhaps from a NewCommentJob - # ActionCable.server.broadcast \ - # "web_notifications_1", { title: 'New things!', body: 'All shit fit for print' } + # # Somewhere in your app this is called, perhaps from a NewCommentJob + # ActionCable.server.broadcast \ + # "web_notifications_1", { title: "New things!", body: "All that's fit for print" } # - # # Client-side coffescript, which assumes you've already requested the right to send web notifications - # App.cable.subscriptions.create "WebNotificationsChannel", - # received: (data) -> - # new Notification data['title'], body: data['body'] + # # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications + # App.cable.subscriptions.create "WebNotificationsChannel", + # received: (data) -> + # new Notification data['title'], body: data['body'] module Broadcasting # Broadcast a hash directly to a named <tt>broadcasting</tt>. It'll automatically be JSON encoded. def broadcast(broadcasting, message) diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb new file mode 100644 index 0000000000..af04a58c70 --- /dev/null +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -0,0 +1,75 @@ +require 'thread' + +gem 'em-hiredis', '~> 0.3.0' +gem 'redis', '~> 3.0' +require 'em-hiredis' +require 'redis' + +EventMachine.epoll if EventMachine.epoll? +EventMachine.kqueue if EventMachine.kqueue? + +module ActionCable + module SubscriptionAdapter + class EventedRedis < Base # :nodoc: + @@mutex = Mutex.new + + # Overwrite this factory method for EventMachine redis connections if you want to use a different Redis library than EM::Hiredis. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor(:em_redis_connector) { ->(config) { EM::Hiredis.connect(config[:url]) } } + + # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } } + + def initialize(*) + super + @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil + end + + def broadcast(channel, payload) + redis_connection_for_broadcasts.publish(channel, payload) + end + + def subscribe(channel, message_callback, success_callback = nil) + redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result| + result.callback { |reply| success_callback.call } if success_callback + end + end + + def unsubscribe(channel, message_callback) + redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback) + end + + def shutdown + redis_connection_for_subscriptions.pubsub.close_connection + @redis_connection_for_subscriptions = nil + end + + private + def redis_connection_for_subscriptions + ensure_reactor_running + @redis_connection_for_subscriptions || @server.mutex.synchronize do + @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis| + redis.on(:reconnect_failed) do + @logger.info "[ActionCable] Redis reconnect failed." + end + end + end + end + + def redis_connection_for_broadcasts + @redis_connection_for_broadcasts || @server.mutex.synchronize do + @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable) + end + end + + def ensure_reactor_running + return if EventMachine.reactor_running? + @@mutex.synchronize do + Thread.new { EventMachine.run } unless EventMachine.reactor_running? + Thread.pass until EventMachine.reactor_running? + end + end + end + end +end diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index 560b79df16..ba4934a264 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -1,66 +1,166 @@ require 'thread' -gem 'em-hiredis', '~> 0.3.0' gem 'redis', '~> 3.0' -require 'em-hiredis' require 'redis' -EventMachine.epoll if EventMachine.epoll? -EventMachine.kqueue if EventMachine.kqueue? - module ActionCable module SubscriptionAdapter class Redis < Base # :nodoc: - @@mutex = Mutex.new + # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis. + # This is needed, for example, when using Makara proxies for distributed Redis. + cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } } def initialize(*) super - @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil + @listener = nil + @redis_connection_for_broadcasts = nil end def broadcast(channel, payload) redis_connection_for_broadcasts.publish(channel, payload) end - def subscribe(channel, message_callback, success_callback = nil) - redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result| - result.callback { |reply| success_callback.call } if success_callback - end + def subscribe(channel, callback, success_callback = nil) + listener.add_subscriber(channel, callback, success_callback) end - def unsubscribe(channel, message_callback) - redis_connection_for_subscriptions.pubsub.unsubscribe_proc(channel, message_callback) + def unsubscribe(channel, callback) + listener.remove_subscriber(channel, callback) end def shutdown - redis_connection_for_subscriptions.pubsub.close_connection - @redis_connection_for_subscriptions = nil + @listener.shutdown if @listener + end + + def redis_connection_for_subscriptions + ::Redis.new(@server.config.cable) end private - def redis_connection_for_subscriptions - ensure_reactor_running - @redis_connection_for_subscriptions || @server.mutex.synchronize do - @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis| - redis.on(:reconnect_failed) do - @logger.info "[ActionCable] Redis reconnect failed." - end - end - end + def listener + @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) } end def redis_connection_for_broadcasts @redis_connection_for_broadcasts || @server.mutex.synchronize do - @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable) + @redis_connection_for_broadcasts ||= self.class.redis_connector.call(@server.config.cable) end end - def ensure_reactor_running - return if EventMachine.reactor_running? - @@mutex.synchronize do - Thread.new { EventMachine.run } unless EventMachine.reactor_running? - Thread.pass until EventMachine.reactor_running? + class Listener < SubscriberMap + def initialize(adapter) + super() + + @adapter = adapter + + @subscribe_callbacks = Hash.new { |h, k| h[k] = [] } + @subscription_lock = Mutex.new + + @raw_client = nil + + @when_connected = [] + + @thread = nil end + + def listen(conn) + conn.without_reconnect do + original_client = conn.client + + conn.subscribe('_action_cable_internal') do |on| + on.subscribe do |chan, count| + @subscription_lock.synchronize do + if count == 1 + @raw_client = original_client + + until @when_connected.empty? + @when_connected.shift.call + end + end + + if callbacks = @subscribe_callbacks[chan] + next_callback = callbacks.shift + Concurrent.global_io_executor << next_callback if next_callback + @subscribe_callbacks.delete(chan) if callbacks.empty? + end + end + end + + on.message do |chan, message| + broadcast(chan, message) + end + + on.unsubscribe do |chan, count| + if count == 0 + @subscription_lock.synchronize do + @raw_client = nil + end + end + end + end + end + end + + def shutdown + @subscription_lock.synchronize do + return if @thread.nil? + + when_connected do + send_command('unsubscribe') + @raw_client = nil + end + end + + Thread.pass while @thread.alive? + end + + def add_channel(channel, on_success) + @subscription_lock.synchronize do + ensure_listener_running + @subscribe_callbacks[channel] << on_success + when_connected { send_command('subscribe', channel) } + end + end + + def remove_channel(channel) + @subscription_lock.synchronize do + when_connected { send_command('unsubscribe', channel) } + end + end + + def invoke_callback(*) + Concurrent.global_io_executor.post { super } + end + + private + def ensure_listener_running + @thread ||= Thread.new do + Thread.current.abort_on_exception = true + + conn = @adapter.redis_connection_for_subscriptions + listen conn + end + end + + def when_connected(&block) + if @raw_client + block.call + else + @when_connected << block + end + end + + def send_command(*command) + @raw_client.write(command) + + very_raw_connection = + @raw_client.connection.instance_variable_defined?(:@connection) && + @raw_client.connection.instance_variable_get(:@connection) + + if very_raw_connection && very_raw_connection.respond_to?(:flush) + very_raw_connection.flush + end + end end end end diff --git a/actioncable/lib/rails/generators/channel/templates/channel.rb b/actioncable/lib/rails/generators/channel/templates/channel.rb index 6cf04ee61f..7bff3341c1 100644 --- a/actioncable/lib/rails/generators/channel/templates/channel.rb +++ b/actioncable/lib/rails/generators/channel/templates/channel.rb @@ -1,4 +1,4 @@ -# Be sure to restart your server when you modify this file. Action Cable runs in an EventMachine loop that does not support auto reloading. +# Be sure to restart your server when you modify this file. Action Cable runs in a loop that does not support auto reloading. <% module_namespacing do -%> class <%= class_name %>Channel < ApplicationCable::Channel def subscribed diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb index d7eecfa322..4ade9832e0 100644 --- a/actioncable/test/client_test.rb +++ b/actioncable/test/client_test.rb @@ -32,6 +32,7 @@ class ClientTest < ActionCable::TestCase server.config.channel_load_paths = [File.expand_path('client', __dir__)] Thread.new { EventMachine.run } unless EventMachine.reactor_running? + Thread.pass until EventMachine.reactor_running? # faye-websocket is warning-rich @previous_verbose, $VERBOSE = $VERBOSE, nil @@ -145,15 +146,6 @@ class ClientTest < ActionCable::TestCase @ws.close @closed.wait(WAIT_WHEN_EXPECTING_EVENT) end - - def close! - sock = BasicSocket.for_fd(@ws.instance_variable_get(:@stream).detach) - - # Force a TCP reset - sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [1, 0].pack('ii')) - - sock.close - end end def faye_client(port) @@ -195,7 +187,7 @@ class ClientTest < ActionCable::TestCase def test_many_clients with_puma_server do |port| - clients = 200.times.map { faye_client(port) } + clients = 100.times.map { faye_client(port) } clients.map {|c| Concurrent::Future.execute { c.send_message command: 'subscribe', identifier: JSON.dump(channel: 'EchoChannel') diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb new file mode 100644 index 0000000000..70333e51bd --- /dev/null +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -0,0 +1,10 @@ +require 'test_helper' +require_relative './common' + +class EventedRedisAdapterTest < ActionCable::TestCase + include CommonSubscriptionAdapterTest + + def cable_config + { adapter: 'evented_redis', url: 'redis://127.0.0.1:6379/12' } + end +end diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 8d52832c87..4f34dd86c9 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -5,6 +5,12 @@ class RedisAdapterTest < ActionCable::TestCase include CommonSubscriptionAdapterTest def cable_config - { adapter: 'redis', url: 'redis://127.0.0.1:6379/12' } + { adapter: 'redis', driver: 'ruby', url: 'redis://127.0.0.1:6379/12' } + end +end + +class RedisAdapterTest::Hiredis < RedisAdapterTest + def cable_config + super.merge(driver: 'hiredis') end end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index e74e0bc20a..22e9bd12a1 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * `config.force_ssl = true` will set diff --git a/actionmailer/Rakefile b/actionmailer/Rakefile index 7197ea5e27..54e6cff48d 100644 --- a/actionmailer/Rakefile +++ b/actionmailer/Rakefile @@ -3,6 +3,9 @@ require 'rake/testtask' desc "Default Task" task default: [ :test ] +task :package +task "package:clean" + # Run the unit tests Rake::TestTask.new { |t| t.libs << "test" diff --git a/actionmailer/lib/action_mailer/gem_version.rb b/actionmailer/lib/action_mailer/gem_version.rb index d28568b6a1..a1ee5fb238 100644 --- a/actionmailer/lib/action_mailer/gem_version.rb +++ b/actionmailer/lib/action_mailer/gem_version.rb @@ -8,7 +8,7 @@ module ActionMailer MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index f6ffe45490..bf964d06e9 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,57 @@ +* Add request encoding and response parsing to integration tests. + + What previously was: + + ```ruby + require 'test_helper' + + class ApiTest < ActionDispatch::IntegrationTest + test 'creates articles' do + assert_difference -> { Article.count } do + post articles_path(format: :json), + params: { article: { title: 'Ahoy!' } }.to_json, + headers: { 'Content-Type' => 'application/json' } + end + + assert_equal({ id: Article.last.id, title: 'Ahoy!' }, JSON.parse(response.body)) + end + end + ``` + + Can now be written as: + + ```ruby + require 'test_helper' + + class ApiTest < ActionDispatch::IntegrationTest + test 'creates articles' do + assert_difference -> { Article.count } do + post articles_path, { article: { title: 'Ahoy!' } }, as: :json + end + + assert_equal({ id: Article.last.id, title: 'Ahoy!' }, response.parsed_body) + end + end + ``` + + *Kasper Timm Hansen* + +* Add image/svg+xml as a default mime type. + + *DHH* + +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* Add `-g` and `-c` (short for _grep_ and _controller_ respectively) options + to `bin/rake routes`. These options return the url `name`, `verb` and + `path` field that match the pattern or match a specific controller. + + Deprecate `CONTROLLER` env variable in `bin/rake routes`. + + See #18902. + + *Anton Davydov* & *Vipul A M* + * Response etags to always be weak: Prefixes 'W/' to value returned by `ActionDispatch::Http::Cache::Response#etag=`, such that etags set in `fresh_when` and `stale?` are weak. @@ -171,11 +225,11 @@ * Accessing mime types via constants like `Mime::HTML` is deprecated. Please change code like this: - Mime::HTML + Mime::HTML To this: - Mime[:html] + Mime[:html] This change is so that Rails will not manage a list of constants, and fixes an issue where if a type isn't registered you could possibly get the wrong diff --git a/actionpack/Rakefile b/actionpack/Rakefile index 601263bfac..37a269cffd 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -5,6 +5,9 @@ test_files = Dir.glob('test/**/*_test.rb') desc "Default Task" task :default => :test +task :package +task "package:clean" + # Run the unit tests Rake::TestTask.new do |t| t.libs << 'test' diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb index 4c9f14e409..a0917b4fdb 100644 --- a/actionpack/lib/action_controller/log_subscriber.rb +++ b/actionpack/lib/action_controller/log_subscriber.rb @@ -25,7 +25,9 @@ module ActionController status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name) end message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms" - message << " (#{additions.join(" | ".freeze)})" unless additions.blank? + message << " (#{additions.join(" | ".freeze)})" unless additions.empty? + message << "\n\n" if defined?(Rails.env) && Rails.env.development? + message end end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 3dbf34eb2a..bf74b39ac4 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -19,9 +19,9 @@ module ActionController :controller => self.class.name, :action => self.action_name, :params => request.filtered_parameters, - :format => request.format.try(:ref), + :format => request.format.ref, :method => request.request_method, - :path => (request.fullpath rescue "unknown") + :path => request.fullpath } ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index e3c540bf5f..fc20e7a421 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -237,39 +237,55 @@ module ActionController # This processes the action in a child thread. It lets us return the # response code and headers back up the rack stack, and still process # the body in parallel with sending data to the client - Thread.new { - t2 = Thread.current - t2.abort_on_exception = true - - # Since we're processing the view in a different thread, copy the - # thread locals from the main thread to the child thread. :'( - locals.each { |k,v| t2[k] = v } - - begin - super(name) - rescue => e - if @_response.committed? - begin - @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html - @_response.stream.call_on_error - rescue => exception - log_error(exception) - ensure - log_error(e) - @_response.stream.close + new_controller_thread { + ActiveSupport::Dependencies.interlock.running do + t2 = Thread.current + + # Since we're processing the view in a different thread, copy the + # thread locals from the main thread to the child thread. :'( + locals.each { |k,v| t2[k] = v } + + begin + super(name) + rescue => e + if @_response.committed? + begin + @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html + @_response.stream.call_on_error + rescue => exception + log_error(exception) + ensure + log_error(e) + @_response.stream.close + end + else + error = e end - else - error = e + ensure + @_response.commit! end - ensure - @_response.commit! end } - @_response.await_commit + ActiveSupport::Dependencies.interlock.permit_concurrent_loads do + @_response.await_commit + end + raise error if error end + # Spawn a new thread to serve up the controller in. This is to get + # around the fact that Rack isn't based around IOs and we need to use + # a thread to stream data from the response bodies. Nobody should call + # this method except in Rails internals. Seriously! + def new_controller_thread # :nodoc: + Thread.new { + t2 = Thread.current + t2.abort_on_exception = true + yield + } + end + def log_error(exception) logger = ActionController::Base.logger return unless logger diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 91b3403ad5..6586985ff5 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -378,7 +378,8 @@ module ActionController #:nodoc: end def xor_byte_strings(s1, s2) - s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*') + s2_bytes = s2.bytes + s1.bytes.map.with_index { |c1, i| c1 ^ s2_bytes[i] }.pack('c*') end # The form's authenticity parameter. Override to provide your own. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index b43bb9dc17..0c4b661214 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -12,6 +12,17 @@ module ActionController include Testing::Functional end + module Live + # Disable controller / rendering threads in tests. User tests can access + # the database on the main thread, so they could open a txn, then the + # controller thread will open a new connection and try to access data + # that's only visible to the main thread's txn. This is the problem in #23483 + remove_method :new_controller_thread + def new_controller_thread # :nodoc: + yield + end + end + # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1. # Please use ActionDispatch::IntegrationTest going forward. class TestRequest < ActionDispatch::TestRequest #:nodoc: diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 0f7898a3f8..4bd727c14e 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -80,6 +80,14 @@ module ActionDispatch set_header DATE, utc_time.httpdate end + # This method allows you to set the ETag for cached content, which + # will be returned to the end user. + # + # By default, Action Dispatch sets all ETags to be weak. + # This ensures that if the content changes only semantically, + # the whole page doesn't have to be regenerated from scratch + # by the web server. With strong ETags, pages are compared + # byte by byte, and are regenerated only if they are not exactly equal. def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) super %(W/"#{Digest::MD5.hexdigest(key)}") diff --git a/actionpack/lib/action_dispatch/http/mime_types.rb b/actionpack/lib/action_dispatch/http/mime_types.rb index 87715205d9..8356d1a238 100644 --- a/actionpack/lib/action_dispatch/http/mime_types.rb +++ b/actionpack/lib/action_dispatch/http/mime_types.rb @@ -14,6 +14,7 @@ Mime::Type.register "image/jpeg", :jpeg, [], %w(jpg jpeg jpe pjpeg) Mime::Type.register "image/gif", :gif, [], %w(gif) Mime::Type.register "image/bmp", :bmp, [], %w(bmp) Mime::Type.register "image/tiff", :tiff, [], %w(tif tiff) +Mime::Type.register "image/svg+xml", :svg Mime::Type.register "video/mpeg", :mpeg, [], %w(mpg mpeg mpe) diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 14f86c7c07..fa4c54701a 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/module/attribute_accessors' require 'action_dispatch/http/filter_redirect' +require 'action_dispatch/http/cache' require 'monitor' module ActionDispatch # :nodoc: diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index d00b2c3eb5..6cde5b2900 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -239,7 +239,8 @@ module ActionDispatch # # rails routes # - # Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>. + # Target specific controllers by prefixing the command with <tt>--controller</tt> option + # - or its <tt>-c</tt> shorthand. # module Routing extend ActiveSupport::Autoload diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 69e6dd5215..b806ee015b 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -60,12 +60,10 @@ module ActionDispatch end def format(formatter, filter = nil) - routes_to_display = filter_routes(filter) - + routes_to_display = filter_routes(normalize_filter(filter)) routes = collect_routes(routes_to_display) - if routes.none? - formatter.no_routes(collect_routes(@routes), filter) + formatter.no_routes(collect_routes(@routes)) return formatter.result end @@ -82,10 +80,19 @@ module ActionDispatch private + def normalize_filter(filter) + if filter.is_a?(Hash) && filter[:controller] + { controller: /#{filter[:controller].downcase.sub(/_?controller\z/, '').sub('::', '/')}/ } + elsif filter + { controller: /#{filter}/, action: /#{filter}/ } + end + end + def filter_routes(filter) if filter - filter_name = filter.underscore.sub(/_controller$/, '') - @routes.select { |route| route.defaults[:controller] == filter_name } + @routes.select do |route| + filter.any? { |default, value| route.defaults[default] =~ value } + end else @routes end @@ -137,7 +144,7 @@ module ActionDispatch @buffer << draw_header(routes) end - def no_routes(routes, filter) + def no_routes(routes) @buffer << if routes.none? <<-MESSAGE.strip_heredoc @@ -145,8 +152,6 @@ module ActionDispatch Please add some routes in config/routes.rb. MESSAGE - elsif missing_controller?(filter) - "The controller #{filter} does not exist!" else "No routes were found for this controller" end @@ -154,10 +159,6 @@ module ActionDispatch end private - def missing_controller?(controller_name) - [ controller_name.camelize, "#{controller_name.camelize}Controller" ].none?(&:safe_constantize) - end - def draw_section(routes) header_lengths = ['Prefix', 'Verb', 'URI Pattern'].map(&:length) name_width, verb_width, path_width = widths(routes).zip(header_lengths).map(&:max) diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 78ef860548..e7af27463c 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -86,6 +86,7 @@ module ActionDispatch end # Load routes.rb if it hasn't been loaded. + options = options.clone generated_path, query_string_keys = @routes.generate_extras(options, defaults) found_extras = options.reject { |k, _| ! query_string_keys.include? k } diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 711ca10419..742bce1ca6 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -71,7 +71,7 @@ module ActionDispatch end # Performs an XMLHttpRequest request with the given parameters, mirroring - # a request from the Prototype library. + # an AJAX request made from JavaScript. # # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart @@ -321,7 +321,9 @@ module ActionDispatch end # Performs the actual request. - def process(method, path, params: nil, headers: nil, env: nil, xhr: false) + def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil) + request_encoder = RequestEncoder.encoder(as) + if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme @@ -330,14 +332,17 @@ module ActionDispatch url_host += ":#{location.port}" if default != location.port host! url_host end - path = location.query ? "#{location.path}?#{location.query}" : location.path + path = request_encoder.append_format_to location.path + path = location.query ? "#{path}?#{location.query}" : path + else + path = request_encoder.append_format_to path end hostname, port = host.split(':') request_env = { :method => method, - :params => params, + :params => request_encoder.encode_params(params), "SERVER_NAME" => hostname, "SERVER_PORT" => port || (https? ? "443" : "80"), @@ -347,7 +352,7 @@ module ActionDispatch "REQUEST_URI" => path, "HTTP_HOST" => host, "REMOTE_ADDR" => remote_addr, - "CONTENT_TYPE" => "application/x-www-form-urlencoded", + "CONTENT_TYPE" => request_encoder.content_type, "HTTP_ACCEPT" => accept } @@ -376,6 +381,7 @@ module ActionDispatch response = _mock_session.last_response @response = ActionDispatch::TestResponse.from_response(response) @response.request = @request + @response.response_parser = request_encoder @html_document = nil @url_options = nil @@ -387,6 +393,53 @@ module ActionDispatch def build_full_uri(path, env) "#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}" end + + class RequestEncoder # :nodoc: + @encoders = {} + + def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false) + @mime = Mime[mime_name] + + unless @mime + raise ArgumentError, "Can't register a request encoder for " \ + "unregistered MIME Type: #{mime_name}. See `Mime::Type.register`." + end + + @url_encoded_form = url_encoded_form + @path_format = ".#{@mime.symbol}" unless @url_encoded_form + @response_parser = response_parser || -> body { body } + @param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc + end + + def append_format_to(path) + path << @path_format unless @url_encoded_form + path + end + + def content_type + @mime.to_s + end + + def encode_params(params) + @param_encoder.call(params) + end + + def parse_body(body) + @response_parser.call(body) + end + + def self.encoder(name) + @encoders[name] || WWWFormEncoder + end + + def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil) + @encoders[mime_name] = new(mime_name, param_encoder, response_parser) + end + + register_encoder :json, response_parser: -> body { JSON.parse(body) } + + WWWFormEncoder = new(:url_encoded_form, -> params { params }, nil, true) + end end module Runner @@ -643,6 +696,38 @@ module ActionDispatch # end # end # + # You can also test your JSON API easily by setting what the request should + # be encoded as: + # + # require 'test_helper' + # + # class ApiTest < ActionDispatch::IntegrationTest + # test 'creates articles' do + # assert_difference -> { Article.count } do + # post articles_path, params: { article: { title: 'Ahoy!' } }, as: :json + # end + # + # assert_response :success + # assert_equal({ id: Arcticle.last.id, title: 'Ahoy!' }, response.parsed_body) + # end + # end + # + # The `as` option sets the format to JSON, sets the content type to + # 'application/json' and encodes the parameters as JSON. + # + # Calling `parsed_body` on the response parses the response body as what + # the last request was encoded as. If the request wasn't encoded `as` something, + # it's the same as calling `body`. + # + # For any custom MIME Types you've registered, you can even add your own encoders with: + # + # ActionDispatch::IntegrationTest.register_encoder :wibble, + # param_encoder: -> params { params.to_wibble }, + # response_parser: -> body { body } + # + # Where `param_encoder` defines how the params should be encoded and + # `response_parser` defines how the response body should be parsed. + # # Consult the Rails Testing Guide for more. class IntegrationTest < ActiveSupport::TestCase @@ -671,5 +756,9 @@ module ActionDispatch def document_root_element html_document.root end + + def self.register_encoder(*args) + Integration::Session::RequestEncoder.register_encoder(*args) + end end end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 4b79a90242..58d3e6eb0f 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -18,5 +18,11 @@ module ActionDispatch # Was there a server-side error? alias_method :error?, :server_error? + + attr_writer :response_parser # :nodoc: + + def parsed_body + @response_parser.parse_body(body) + end end end diff --git a/actionpack/lib/action_pack/gem_version.rb b/actionpack/lib/action_pack/gem_version.rb index 3a1410d492..778c5482d3 100644 --- a/actionpack/lib/action_pack/gem_version.rb +++ b/actionpack/lib/action_pack/gem_version.rb @@ -8,7 +8,7 @@ module ActionPack MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index d0a1d1285f..cb524bacb2 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -1126,3 +1126,59 @@ class IntegrationRequestsWithSessionSetup < ActionDispatch::IntegrationTest assert_equal({"user_name"=>"david"}, cookies.to_hash) end end + +class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest + class FooController < ActionController::Base + def foos_json + render json: params.permit(:foo) + end + + def foos_wibble + render plain: 'ok' + end + end + + def test_encoding_as_json + post_to_foos as: :json do + assert_response :success + assert_match 'foos_json.json', request.path + assert_equal 'application/json', request.content_type + assert_equal({ 'foo' => 'fighters' }, request.request_parameters) + assert_equal({ 'foo' => 'fighters' }, response.parsed_body) + end + end + + def test_encoding_as_without_mime_registration + assert_raise ArgumentError do + ActionDispatch::IntegrationTest.register_encoder :wibble + end + end + + def test_registering_custom_encoder + Mime::Type.register 'text/wibble', :wibble + + ActionDispatch::IntegrationTest.register_encoder(:wibble, + param_encoder: -> params { params }) + + post_to_foos as: :wibble do + assert_response :success + assert_match 'foos_wibble.wibble', request.path + assert_equal 'text/wibble', request.content_type + assert_equal Hash.new, request.request_parameters # Unregistered MIME Type can't be parsed. + assert_equal 'ok', response.parsed_body + end + ensure + Mime::Type.unregister :wibble + end + + private + def post_to_foos(as:) + with_routing do |routes| + routes.draw { post ':action' => FooController } + + post "/foos_#{as}", params: { foo: 'fighters' }, as: as + + yield + end + end +end diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 2ef9734269..0c3884cd38 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -152,7 +152,6 @@ module ActionController def thread_locals tc.assert_equal 'aaron', Thread.current[:setting] - tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread] response.headers['Content-Type'] = 'text/event-stream' %w{ hello world }.each do |word| @@ -261,6 +260,14 @@ module ActionController end end + def setup + super + + def @controller.new_controller_thread + Thread.new { yield } + end + end + def test_set_cookie get :set_cookie assert_equal({'hello' => 'world'}, @response.cookies) diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index 7382c267c7..f72a87b994 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -17,10 +17,10 @@ module ActionDispatch @set = ActionDispatch::Routing::RouteSet.new end - def draw(options = {}, &block) + def draw(options = nil, &block) @set.draw(&block) inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes) - inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n") + inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options).split("\n") end def test_displaying_routes_for_engines @@ -297,7 +297,7 @@ module ActionDispatch end def test_routes_can_be_filtered - output = draw(filter: 'posts') do + output = draw('posts') do resources :articles resources :posts end @@ -313,6 +313,26 @@ module ActionDispatch " DELETE /posts/:id(.:format) posts#destroy"], output end + def test_routes_can_be_filtered_with_namespaced_controllers + output = draw('admin/posts') do + resources :articles + namespace :admin do + resources :posts + end + end + + assert_equal [" Prefix Verb URI Pattern Controller#Action", + " admin_posts GET /admin/posts(.:format) admin/posts#index", + " POST /admin/posts(.:format) admin/posts#create", + " new_admin_post GET /admin/posts/new(.:format) admin/posts#new", + "edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit", + " admin_post GET /admin/posts/:id(.:format) admin/posts#show", + " PATCH /admin/posts/:id(.:format) admin/posts#update", + " PUT /admin/posts/:id(.:format) admin/posts#update", + " DELETE /admin/posts/:id(.:format) admin/posts#destroy"], output + end + + def test_regression_route_with_controller_regexp output = draw do get ':controller(/:action)', controller: /api\/[^\/]+/, format: false @@ -336,18 +356,18 @@ module ActionDispatch end def test_routes_with_undefined_filter - output = draw(:filter => 'Rails::MissingController') do + output = draw(controller: 'Rails::MissingController') do get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ end assert_equal [ - "The controller Rails::MissingController does not exist!", + "No routes were found for this controller", "For more information about routes, see the Rails guide: http://guides.rubyonrails.org/routing.html." ], output end def test_no_routes_matched_filter - output = draw(:filter => 'rails/dummy') do + output = draw('rails/dummy') do get 'photos/:id' => 'photos#show', :id => /[A-Z]\d{5}/ end @@ -358,7 +378,7 @@ module ActionDispatch end def test_no_routes_were_defined - output = draw(:filter => 'Rails::DummyController') { } + output = draw('Rails::DummyController') {} assert_equal [ "You don't have any routes defined!", diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index d85681e0d1..256b90784a 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,5 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + * Fix stripping the digest from the automatically generated img tag alt attribute when assets are handled by Sprockets >=3.0. diff --git a/actionview/Rakefile b/actionview/Rakefile index 93be50721d..d41030c650 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -3,6 +3,9 @@ require 'rake/testtask' desc "Default Task" task :default => :test +task :package +task "package:clean" + # Run the unit tests desc "Run all unit tests" diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index 5a4c3ea3fe..7731773040 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -7,18 +7,20 @@ module ActionView def self.find_dependencies(name, template, view_paths = nil) tracker = @trackers[template.handler] - return [] unless tracker.present? + return [] unless tracker - if tracker.respond_to?(:supports_view_paths?) && tracker.supports_view_paths? - tracker.call(name, template, view_paths) - else - tracker.call(name, template) - end + tracker.call(name, template, view_paths) end def self.register_tracker(extension, tracker) handler = Template.handler_for_extension(extension) - @trackers[handler] = tracker + if tracker.respond_to?(:supports_view_paths?) + @trackers[handler] = tracker + else + @trackers[handler] = lambda { |name, template, _| + tracker.call(name, template) + } + end end def self.remove_tracker(handler) @@ -151,11 +153,11 @@ module ActionView def resolve_directories(wildcard_dependencies) return [] unless @view_paths - wildcard_dependencies.each_with_object([]) do |query, templates| - @view_paths.find_all_with_query(query).each do |template| - templates << "#{File.dirname(query)}/#{File.basename(template).split('.').first}" + wildcard_dependencies.flat_map { |query, templates| + @view_paths.find_all_with_query(query).map do |template| + "#{File.dirname(query)}/#{File.basename(template).split('.').first}" end - end + }.sort end def explicit_dependencies diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb index 23d5319579..bb5c96cb39 100644 --- a/actionview/lib/action_view/gem_version.rb +++ b/actionview/lib/action_view/gem_version.rb @@ -8,7 +8,7 @@ module ActionView MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index b03b197cb5..ccee785d3e 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -59,6 +59,9 @@ module ActionView class Error < ActionViewError #:nodoc: SOURCE_CODE_RADIUS = 3 + # Override to prevent #cause resetting during re-raise. + attr_reader :cause + def initialize(template, original_exception = nil) if original_exception ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ @@ -67,6 +70,7 @@ module ActionView super($!.message) set_backtrace($!.backtrace) + @cause = $! @template, @sub_templates = template, nil end diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 8687af5eba..229ef03879 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,15 @@ +* Change the default adapter from inline to async. It's a better default as tests will then not mistakenly + come to rely on behavior happening synchronously. This is especially important with things like jobs kicked off + in Active Record lifecycle callbacks. + + *DHH* + + +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Fixed serializing `:at` option for `assert_enqueued_with` diff --git a/activejob/README.md b/activejob/README.md index 7268186c00..8becac7753 100644 --- a/activejob/README.md +++ b/activejob/README.md @@ -20,12 +20,7 @@ switch between them without having to rewrite your jobs. ## Usage -Set the queue adapter for Active Job: - -``` ruby -ActiveJob::Base.queue_adapter = :inline # default queue adapter -``` -Note: To learn how to use your preferred queueing backend see its adapter +To learn how to use your preferred queueing backend see its adapter documentation at [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). diff --git a/activejob/Rakefile b/activejob/Rakefile index d9648a7f16..2a853b4b6b 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -6,6 +6,9 @@ ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION) task default: :test task test: 'test:default' +task :package +task "package:clean" + namespace :test do desc 'Run all adapter tests' task :default do diff --git a/activejob/lib/active_job/gem_version.rb b/activejob/lib/active_job/gem_version.rb index 0bdeca76a8..bc88221027 100644 --- a/activejob/lib/active_job/gem_version.rb +++ b/activejob/lib/active_job/gem_version.rb @@ -8,7 +8,7 @@ module ActiveJob MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index 457015b741..72e4ebf935 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -4,25 +4,25 @@ require 'active_support/core_ext/string/inflections' module ActiveJob # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the - # correct adapter. The default queue adapter is the +:inline+ queue. + # correct adapter. The default queue adapter is the +:async+ queue. module QueueAdapter #:nodoc: extend ActiveSupport::Concern included do class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false - self.queue_adapter = :inline + self.queue_adapter = :async end # Includes the setter method for changing the active queue adapter. module ClassMethods # Returns the backend queue provider. The default queue adapter - # is the +:inline+ queue. See QueueAdapters for more information. + # is the +:async+ queue. See QueueAdapters for more information. def queue_adapter _queue_adapter end # Specify the backend queue provider. The default queue adapter - # is the +:inline+ queue. See QueueAdapters for more + # is the +:async+ queue. See QueueAdapters for more # information. def queue_adapter=(name_or_adapter_or_class) self._queue_adapter = interpret_adapter(name_or_adapter_or_class) diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index 6538ac1b30..b249ec09dd 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -12,7 +12,7 @@ module ActiveJob initializer "active_job.set_configs" do |app| options = app.config.active_job - options.queue_adapter ||= :inline + options.queue_adapter ||= :async ActiveSupport.on_load(:active_job) do options.each { |k,v| send("#{k}=", v) } diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 44ddfa5f69..ed0c05c1e5 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -58,7 +58,7 @@ module ActiveJob # The number of times a specific job is enqueued can be asserted. # # def test_logging_job - # assert_enqueued_jobs 2, only: LoggingJob do + # assert_enqueued_jobs 1, only: LoggingJob do # LoggingJob.perform_later # HelloJob.perform_later('jeremy') # end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index e4769d2f40..318e507ff1 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Validate multiple contexts on `valid?` and `invalid?` at once. diff --git a/activemodel/Rakefile b/activemodel/Rakefile index 5a67f0a151..9982d49bcb 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -4,6 +4,9 @@ dir = File.dirname(__FILE__) task :default => :test +task :package +task "package:clean" + Rake::TestTask.new do |t| t.libs << "test" t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb") diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index ef6141a51d..ea69e7549e 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -160,6 +160,15 @@ module ActiveModel # # person.errors[:name] # => ["cannot be nil"] # person.errors['name'] # => ["cannot be nil"] + # + # Note that, if you try to get errors of an attribute which has + # no errors associated with it, this method will instantiate + # an empty error list for it and +keys+ will return an array + # of error keys which includes this attribute. + # + # person.errors.keys # => [] + # person.errors[:name] # => [] + # person.errors.keys # => [:name] def [](attribute) messages[attribute.to_sym] end diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb index 4563f860c3..94514a0657 100644 --- a/activemodel/lib/active_model/gem_version.rb +++ b/activemodel/lib/active_model/gem_version.rb @@ -8,7 +8,7 @@ module ActiveModel MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f00c4d7b9c..ab17a2b438 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -2,27 +2,27 @@ Closes #13775. - *Takashi Kokubun* +* Add ActiveRecord `#second_to_last` and `#third_to_last` methods. + + *Brian Christian* -* Rework `ActiveRecord::Relation#last` - - 1. Always find last with ruby if relation is loaded - 2. Always use SQL instead if relation is not loaded. - 3. Deprecated relation loading when SQL order can not be automatically reversed +* Added `numeric` helper into migrations. - Topic.order("title").load.last(3) - # before: SELECT ... - # after: No SQL + Example: - Topic.order("title").last - # before: SELECT * FROM `topics` - # after: SELECT * FROM `topics` ORDER BY `topics`.`title` DESC LIMIT 1 + create_table(:numeric_types) do |t| + t.numeric :numeric_type, precision: 10, scale: 2 + end - Topic.order("coalesce(author, title)").last - # before: SELECT * FROM `topics` - # after: Deprecation Warning for irreversible order + *Mehmet Emin İNAÇ* - *Bogdan Gusiev* +* Bumped the minimum supported version of PostgreSQL to >= 9.1. + Both PG 9.0 and 8.4 are past their end of life date: + http://www.postgresql.org/support/versioning/ + + *Remo Mueller* + +## Rails 5.0.0.beta2 (February 01, 2016) ## * `ActiveRecord::Relation#reverse_order` throws `ActiveRecord::IrreversibleOrderError` when the order can not be reversed using current trivial algorithm. @@ -1022,13 +1022,6 @@ *Alex Coomans* -* Dump indexes in `create_table` instead of `add_index`. - - If the adapter supports indexes in `create_table`, generated SQL is - slightly more efficient. - - *Ryuta Kamizono* - * Correctly dump `:options` on `create_table` for MySQL. *Ryuta Kamizono* diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 0564dca94a..46df733cfe 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -20,6 +20,9 @@ end desc 'Run mysql2, sqlite, and postgresql tests by default' task :default => :test +task :package +task "package:clean" + desc 'Run mysql2, sqlite, and postgresql tests' task :test do tasks = defined?(JRUBY_VERSION) ? diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index f6d8e8a342..e13fe33b85 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1593,6 +1593,8 @@ module ActiveRecord # If true, the associated object will be touched (the updated_at/on attributes set to current time) # when this record is either saved or destroyed. If you specify a symbol, that attribute # will be updated with the current time in addition to the updated_at/on attribute. + # Please note that with touching no validation is performed and only the +after_touch+, + # +after_commit+ and +after_rollback+ callbacks are executed. # [:inverse_of] # Specifies the name of the #has_one or #has_many association on the associated # object that is the inverse of this #belongs_to association. Does not work in diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 473b80a658..2dca6b612e 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -72,7 +72,10 @@ module ActiveRecord pk_type = reflection.primary_key_type ids = Array(ids).reject(&:blank?) ids.map! { |i| pk_type.cast(i) } - replace(klass.find(ids).index_by(&:id).values_at(*ids)) + records = klass.where(reflection.association_primary_key => ids).index_by do |r| + r.send(reflection.association_primary_key) + end.values_at(*ids) + replace(records) end def reset @@ -133,6 +136,14 @@ module ActiveRecord first_nth_or_last(:forty_two, *args) end + def third_to_last(*args) + first_nth_or_last(:third_to_last, *args) + end + + def second_to_last(*args) + first_nth_or_last(:second_to_last, *args) + end + def last(*args) first_nth_or_last(:last, *args) end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index fe693cfbb6..2a9627a474 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -197,6 +197,16 @@ module ActiveRecord @association.forty_two(*args) end + # Same as #first except returns only the third-to-last record. + def third_to_last(*args) + @association.third_to_last(*args) + end + + # Same as #first except returns only the second-to-last record. + def second_to_last(*args) + @association.second_to_last(*args) + end + # Returns the last record, or the last +n+ records, from the collection. # If the collection is empty, the first form returns +nil+, and the second # form returns an empty array. diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 8e5b032a93..c5fbe0d1d1 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -81,7 +81,7 @@ module ActiveRecord column = klass.columns_hash[reflection.type.to_s] binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name)) - constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new) + constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new) end joins << table.create_join(table, table.create_on(constraint), join_type) diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index e11a5cfb8a..3032bc786e 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -47,7 +47,7 @@ module ActiveRecord # This is overridden by HABTM as the condition should be on the foreign_key column in # the join table def association_key - table[association_key_name] + klass.arel_attribute(association_key_name, table) end # The name of the key on the model which declares the association diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 423a93964e..e902eb7531 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -34,30 +34,6 @@ module ActiveRecord BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) - class AttributeMethodCache - def initialize - @module = Module.new - @method_cache = Concurrent::Map.new - end - - def [](name) - @method_cache.compute_if_absent(name) do - safe_name = name.unpack('h*'.freeze).first - temp_method = "__temp__#{safe_name}" - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - @module.module_eval method_body(temp_method, safe_name), __FILE__, __LINE__ - @module.instance_method temp_method - end - end - - private - - # Override this method in the subclasses for method body. - def method_body(method_name, const_name) - raise NotImplementedError, "Subclasses must implement a method_body(method_name, const_name) method." - end - end - class GeneratedAttributeMethods < Module; end # :nodoc: module ClassMethods diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 5197e21fa4..ab2ecaa7c5 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,8 +1,11 @@ module ActiveRecord module AttributeMethods module Read - ReaderMethodCache = Class.new(AttributeMethodCache) { - private + extend ActiveSupport::Concern + + module ClassMethods + protected + # We want to generate the methods via module_eval rather than # define_method, because define_method is slower on dispatch. # Evaluating many similar methods may use more memory as the instruction @@ -21,21 +24,6 @@ module ActiveRecord # to allocate an object on each call to the attribute method. # Making it frozen means that it doesn't get duped when used to # key the @attributes in read_attribute. - def method_body(method_name, const_name) - <<-EOMETHOD - def #{method_name} - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} - _read_attribute(name) { |n| missing_attribute(n, caller) } - end - EOMETHOD - end - }.new - - extend ActiveSupport::Concern - - module ClassMethods - protected - def define_method_attribute(name) safe_name = name.unpack('h*'.freeze).first temp_method = "__temp__#{safe_name}" diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 061628725d..ebaaa54b2b 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -20,7 +20,7 @@ module ActiveRecord nil end else - map(super) { |t| cast(t) } + map_avoiding_infinite_recursion(super) { |v| cast(v) } end end @@ -34,13 +34,23 @@ module ActiveRecord elsif value.is_a?(::Float) value else - map(value) { |v| convert_time_to_time_zone(v) } + map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) } end end def set_time_zone_without_conversion(value) ::Time.zone.local_to_utc(value).in_time_zone end + + def map_avoiding_infinite_recursion(value) + map(value) do |v| + if value.equal?(v) + nil + else + yield(v) + end + end + end end extend ActiveSupport::Concern diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index bbf2a51a0e..5599b590ca 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -1,19 +1,6 @@ module ActiveRecord module AttributeMethods module Write - WriterMethodCache = Class.new(AttributeMethodCache) { - private - - def method_body(method_name, const_name) - <<-EOMETHOD - def #{method_name}(value) - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{const_name} - write_attribute(name, value) - end - EOMETHOD - end - }.new - extend ActiveSupport::Concern included do diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fdffc3e6b9..7ed2fe48be 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -13,7 +13,6 @@ require 'active_support/core_ext/kernel/singleton_class' require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/class/subclasses' -require 'arel' require 'active_record/attribute_decorators' require 'active_record/errors' require 'active_record/log_subscriber' diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 7e0c9f7837..bb5119d64e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -27,10 +27,10 @@ module ActiveRecord end # Returns an ActiveRecord::Result instance. - def select_all(arel, name = nil, binds = []) + def select_all(arel, name = nil, binds = [], preparable: nil) arel, binds = binds_from_relation arel, binds sql = to_sql(arel, binds) - if arel.is_a?(String) + if arel.is_a?(String) && preparable.nil? preparable = false else preparable = visitor.preparable diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index 5e27cfe507..33dbab41cb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -61,11 +61,11 @@ module ActiveRecord @query_cache.clear end - def select_all(arel, name = nil, binds = []) + def select_all(arel, name = nil, binds = [], preparable: nil) if @query_cache_enabled && !locked?(arel) arel, binds = binds_from_relation arel, binds sql = to_sql(arel, binds) - cache_sql(sql, binds) { super(sql, name, binds) } + cache_sql(sql, binds) { super(sql, name, binds, preparable: visitor.preparable) } else super 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 690e0ba957..cb10ca9929 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -182,6 +182,7 @@ module ActiveRecord end CODE end + alias_method :numeric, :decimal end # Represents the schema of an SQL table in an abstract way. This class @@ -436,6 +437,7 @@ module ActiveRecord # t.bigint # t.float # t.decimal + # t.numeric # t.datetime # t.timestamp # t.time diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index a95109fdae..b1b6044e72 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -14,7 +14,7 @@ module ActiveRecord def column_spec_for_primary_key(column) return if column.type == :integer - spec = { id: column.type.inspect } + spec = { id: schema_type(column).inspect } spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) }) end @@ -24,7 +24,7 @@ module ActiveRecord def prepare_column_options(column) spec = {} spec[:name] = column.name.inspect - spec[:type] = schema_type(column) + spec[:type] = schema_type(column).to_s spec[:null] = 'false' unless column.null if limit = schema_limit(column) @@ -57,7 +57,7 @@ module ActiveRecord private def schema_type(column) - column.type.to_s + column.type end def schema_limit(column) 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 8db7f9172f..f0f855963a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -115,7 +115,7 @@ module ActiveRecord checks = [] checks << lambda { |c| c.name == column_name } checks << lambda { |c| c.type == type } if type - [:limit, :precision, :scale, :default, :null].each do |attr| + (migration_keys - [:name]).each do |attr| checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr) end @@ -459,7 +459,7 @@ module ActiveRecord # The +type+ parameter is normally one of the migrations native types, # which is one of the following: # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>, - # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, + # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>, # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>, # <tt>:binary</tt>, <tt>:boolean</tt>. # @@ -477,9 +477,9 @@ module ActiveRecord # Allows or disallows +NULL+ values in the column. This option could # have been named <tt>:null_allowed</tt>. # * <tt>:precision</tt> - - # Specifies the precision for a <tt>:decimal</tt> column. + # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns. # * <tt>:scale</tt> - - # Specifies the scale for a <tt>:decimal</tt> column. + # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns. # # Note: The precision is the total number of significant digits # and the scale is the number of digits that can be stored following @@ -496,8 +496,6 @@ module ActiveRecord # Default is (10,0). # * PostgreSQL: <tt>:precision</tt> [1..infinity], # <tt>:scale</tt> [0..infinity]. No default. - # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used. - # Internal storage as strings. No default. # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>, # but the maximum supported <tt>:precision</tt> is 16. No default. # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127]. @@ -700,7 +698,7 @@ module ActiveRecord # # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL # - # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables. + # Note: only supported by MySQL. def add_index(table_name, column_name, options = {}) index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options) execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}" @@ -981,6 +979,10 @@ module ActiveRecord ActiveRecord::InternalMetadata.create_table end + def internal_string_options_for_primary_key # :nodoc: + { primary_key: true } + end + def assume_migrated_upto_version(version, migrations_paths) migrations_paths = Array(migrations_paths) version = version.to_i diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 14d04a6388..6ecdab6eb0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -167,8 +167,13 @@ module ActiveRecord def commit_transaction transaction = @stack.last - transaction.before_commit_records - @stack.pop + + begin + transaction.before_commit_records + ensure + @stack.pop + end + transaction.commit transaction.commit_records 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 db09170d33..8751b6da4b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/mysql/column' +require 'active_record/connection_adapters/mysql/explain_pretty_printer' require 'active_record/connection_adapters/mysql/schema_creation' require 'active_record/connection_adapters/mysql/schema_definitions' require 'active_record/connection_adapters/mysql/schema_dumper' @@ -51,7 +52,6 @@ module ActiveRecord INDEX_TYPES = [:fulltext, :spatial] INDEX_USINGS = [:btree, :hash] - # FIXME: Make the first parameter more similar for the two adapters def initialize(connection, logger, connection_options, config) super(connection, logger, config) @quoted_column_names, @quoted_table_names = {}, {} @@ -64,16 +64,18 @@ module ActiveRecord else @prepared_statements = false end + + if version < '5.0.0' + raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0." + end end - MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN = 191 CHARSETS_OF_4BYTES_MAXLEN = ['utf8mb4', 'utf16', 'utf16le', 'utf32'] - def initialize_schema_migrations_table - if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) - ActiveRecord::SchemaMigration.create_table(MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN) - else - ActiveRecord::SchemaMigration.create_table - end + + def internal_string_options_for_primary_key # :nodoc: + super.tap { |options| + options[:collation] = collation.sub(/\A[^_]+/, 'utf8') if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) + } end def version @@ -99,12 +101,8 @@ module ActiveRecord true end - # MySQL 4 technically support transaction isolation, but it is affected by a bug - # where the transaction level gets persisted for the whole session: - # - # http://bugs.mysql.com/bug.php?id=39170 def supports_transaction_isolation? - version >= '5.0.0' + true end def supports_explain? @@ -120,17 +118,15 @@ module ActiveRecord end def supports_views? - version >= '5.0.0' + true end def supports_datetime_with_precision? version >= '5.6.4' end - # 5.0.0 definitely supports it, possibly supported by earlier versions but - # not sure def supports_advisory_locks? - version >= '5.0.0' + true end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: @@ -232,72 +228,7 @@ module ActiveRecord result = exec_query(sql, 'EXPLAIN', binds) elapsed = Time.now - start - ExplainPrettyPrinter.new.pp(result, elapsed) - end - - class ExplainPrettyPrinter # :nodoc: - # Pretty prints the result of an EXPLAIN in a way that resembles the output of the - # MySQL shell: - # - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | - # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # 2 rows in set (0.00 sec) - # - # This is an exercise in Ruby hyperrealism :). - def pp(result, elapsed) - widths = compute_column_widths(result) - separator = build_separator(widths) - - pp = [] - - pp << separator - pp << build_cells(result.columns, widths) - pp << separator - - result.rows.each do |row| - pp << build_cells(row, widths) - end - - pp << separator - pp << build_footer(result.rows.length, elapsed) - - pp.join("\n") + "\n" - end - - private - - def compute_column_widths(result) - [].tap do |widths| - result.columns.each_with_index do |column, i| - cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} - widths << cells_in_column.map(&:length).max - end - end - end - - def build_separator(widths) - padding = 1 - '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' - end - - def build_cells(items, widths) - cells = [] - items.each_with_index do |item, i| - item = 'NULL' if item.nil? - justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' - cells << item.to_s.send(justifier, widths[i]) - end - '| ' + cells.join(' | ') + ' |' - end - - def build_footer(nrows, elapsed) - rows_label = nrows == 1 ? 'row' : 'rows' - "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed - end + MySQL::ExplainPrettyPrinter.new.pp(result, elapsed) end def clear_cache! diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index f633892dee..4bc6447368 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -33,7 +33,7 @@ module ActiveRecord def initialize(url) raise "Database URL cannot be empty" if url.blank? @uri = uri_parser.parse(url) - @adapter = @uri.scheme.tr('-', '_') + @adapter = @uri.scheme && @uri.scheme.tr('-', '_') @adapter = "postgresql" if @adapter == "postgres" if @uri.opaque diff --git a/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb new file mode 100644 index 0000000000..1820853196 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb @@ -0,0 +1,70 @@ +module ActiveRecord + module ConnectionAdapters + module MySQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # MySQL shell: + # + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | + # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # 2 rows in set (0.00 sec) + # + # This is an exercise in Ruby hyperrealism :). + def pp(result, elapsed) + widths = compute_column_widths(result) + separator = build_separator(widths) + + pp = [] + + pp << separator + pp << build_cells(result.columns, widths) + pp << separator + + result.rows.each do |row| + pp << build_cells(row, widths) + end + + pp << separator + pp << build_footer(result.rows.length, elapsed) + + pp.join("\n") + "\n" + end + + private + + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} + widths << cells_in_column.map(&:length).max + end + end + end + + def build_separator(widths) + padding = 1 + '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' + end + + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = 'NULL' if item.nil? + justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' + cells << item.to_s.send(justifier, widths[i]) + end + '| ' + cells.join(' | ') + ' |' + end + + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? 'row' : 'rows' + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 9dee3172f4..ccf5b6cadc 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -12,7 +12,7 @@ module ActiveRecord spec[:unsigned] = 'true' if column.unsigned? return if spec.empty? else - spec[:id] = column.type.inspect + spec[:id] = schema_type(column).inspect spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) end spec @@ -32,7 +32,7 @@ module ActiveRecord def schema_type(column) if column.sql_type == 'tinyblob' - 'blob' + :blob else super end diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index c3c5b660fd..23b33a3555 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -42,7 +42,7 @@ module ActiveRecord end def supports_json? - version >= '5.7.8' + !mariadb? && version >= '5.7.8' end # HELPER METHODS =========================================== diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 8c7cfae7c1..6aa264d766 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -4,44 +4,7 @@ module ActiveRecord module DatabaseStatements def explain(arel, binds = []) sql = "EXPLAIN #{to_sql(arel, binds)}" - ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) - end - - class ExplainPrettyPrinter # :nodoc: - # Pretty prints the result of an EXPLAIN in a way that resembles the output of the - # PostgreSQL shell: - # - # QUERY PLAN - # ------------------------------------------------------------------------------ - # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) - # Join Filter: (posts.user_id = users.id) - # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) - # Index Cond: (id = 1) - # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) - # Filter: (posts.user_id = 1) - # (6 rows) - # - def pp(result) - header = result.columns.first - lines = result.rows.map(&:first) - - # We add 2 because there's one char of padding at both sides, note - # the extra hyphens in the example above. - width = [header, *lines].map(&:length).max + 2 - - pp = [] - - pp << header.center(width).rstrip - pp << '-' * width - - pp += lines.map {|line| " #{line}"} - - nrows = result.rows.length - rows_label = nrows == 1 ? 'row' : 'rows' - pp << "(#{nrows} #{rows_label})" - - pp.join("\n") + "\n" - end + PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds)) end def select_value(arel, name = nil, binds = []) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb new file mode 100644 index 0000000000..789b88912c --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb @@ -0,0 +1,42 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN in a way that resembles the output of the + # PostgreSQL shell: + # + # QUERY PLAN + # ------------------------------------------------------------------------------ + # Nested Loop Left Join (cost=0.00..37.24 rows=8 width=0) + # Join Filter: (posts.user_id = users.id) + # -> Index Scan using users_pkey on users (cost=0.00..8.27 rows=1 width=4) + # Index Cond: (id = 1) + # -> Seq Scan on posts (cost=0.00..28.88 rows=8 width=4) + # Filter: (posts.user_id = 1) + # (6 rows) + # + def pp(result) + header = result.columns.first + lines = result.rows.map(&:first) + + # We add 2 because there's one char of padding at both sides, note + # the extra hyphens in the example above. + width = [header, *lines].map(&:length).max + 2 + + pp = [] + + pp << header.center(width).rstrip + pp << '-' * width + + pp += lines.map {|line| " #{line}"} + + nrows = result.rows.length + rows_label = nrows == 1 ? 'row' : 'rows' + pp << "(#{nrows} #{rows_label})" + + pp.join("\n") + "\n" + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 78039b719c..dcc12ae2a4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -7,10 +7,6 @@ module ActiveRecord :money end - def precision - 19 - end - def scale 2 end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index cc7721ddd8..b82bdb8b0c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -11,7 +11,7 @@ module ActiveRecord spec[:id] = ':uuid' spec[:default] = schema_default(column) || 'nil' else - spec[:id] = column.type.inspect + spec[:id] = schema_type(column).inspect spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) }) end spec @@ -35,9 +35,9 @@ module ActiveRecord return super unless column.serial? if column.bigint? - 'bigserial' + :bigserial else - 'serial' + :serial end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 725173d995..beaeef3c78 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -5,6 +5,7 @@ require 'pg' require "active_record/connection_adapters/abstract_adapter" require "active_record/connection_adapters/postgresql/column" require "active_record/connection_adapters/postgresql/database_statements" +require "active_record/connection_adapters/postgresql/explain_pretty_printer" require "active_record/connection_adapters/postgresql/oid" require "active_record/connection_adapters/postgresql/quoting" require "active_record/connection_adapters/postgresql/referential_integrity" @@ -213,8 +214,8 @@ module ActiveRecord @statements = StatementPool.new @connection, self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }) - if postgresql_version < 80300 - raise "Your version of PostgreSQL (#{postgresql_version}) is too old, please upgrade!" + if postgresql_version < 90100 + raise "Your version of PostgreSQL (#{postgresql_version}) is too old. Active Record supports PostgreSQL >= 9.1." end add_pg_decoders @@ -296,9 +297,8 @@ module ActiveRecord true end - # Returns true if pg > 9.1 def supports_extensions? - postgresql_version >= 90100 + true end # Range datatypes weren't introduced until PostgreSQL 9.2 diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb new file mode 100644 index 0000000000..a946f5ebd0 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb @@ -0,0 +1,19 @@ +module ActiveRecord + module ConnectionAdapters + module SQLite3 + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles + # the output of the SQLite shell: + # + # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) + # 0|1|1|SCAN TABLE posts (~100000 rows) + # + def pp(result) + result.rows.map do |row| + row.join('|') + end.join("\n") + "\n" + end + end + end + end +end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index d1893f35f5..c65d33ccb3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,5 +1,6 @@ require 'active_record/connection_adapters/abstract_adapter' require 'active_record/connection_adapters/statement_pool' +require 'active_record/connection_adapters/sqlite3/explain_pretty_printer' require 'active_record/connection_adapters/sqlite3/schema_creation' gem 'sqlite3', '~> 1.3.6' @@ -7,7 +8,6 @@ require 'sqlite3' module ActiveRecord module ConnectionHandling # :nodoc: - # sqlite3 adapter reuses sqlite_connection. def sqlite3_connection(config) # Require database. unless config[:database] @@ -218,21 +218,7 @@ module ActiveRecord def explain(arel, binds = []) sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}" - ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) - end - - class ExplainPrettyPrinter - # Pretty prints the result of an EXPLAIN QUERY PLAN in a way that resembles - # the output of the SQLite shell: - # - # 0|0|0|SEARCH TABLE users USING INTEGER PRIMARY KEY (rowid=?) (~1 rows) - # 0|1|1|SCAN TABLE posts (~100000 rows) - # - def pp(result) # :nodoc: - result.rows.map do |row| - row.join('|') - end.join("\n") + "\n" - end + SQLite3::ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', [])) end def exec_query(sql, name = nil, binds = [], prepare: false) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 475a298467..24fd0aaecf 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -256,6 +256,11 @@ module ActiveRecord end end + def arel_attribute(name, table = arel_table) # :nodoc: + name = attribute_alias(name) if attribute_alias?(name) + table[name] + end + def predicate_builder # :nodoc: @predicate_builder ||= PredicateBuilder.new(table_metadata) end diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index b4f2f66e1c..aa1f5c4fb4 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -8,7 +8,7 @@ module ActiveRecord MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 3b6fb70d0d..899683ee4f 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -192,7 +192,7 @@ module ActiveRecord end def type_condition(table = arel_table) - sti_column = table[inheritance_column] + sti_column = arel_attribute(inheritance_column, table) sti_names = ([self] + descendants).map(&:sti_name) sti_column.in(sti_names) diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 10fee4dca2..81db96bffd 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -5,10 +5,6 @@ module ActiveRecord # This class is used to create a table that keeps track of values and keys such # as which environment migrations were run in. class InternalMetadata < ActiveRecord::Base # :nodoc: - # Keys in mysql are limited to 191 characters, due to this no adapter can - # use a longer key - KEY_LIMIT = 191 - class << self def primary_key "key" @@ -18,8 +14,8 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" end - def index_name - "#{table_name_prefix}unique_#{ActiveRecord::Base.internal_metadata_table_name}#{table_name_suffix}" + def original_table_name + "#{table_name_prefix}active_record_internal_metadatas#{table_name_suffix}" end def []=(key, value) @@ -34,14 +30,23 @@ module ActiveRecord ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end + def original_table_exists? + # This method will be removed in Rails 5.1 + # Since it is only necessary when `active_record_internal_metadatas` could exist + ActiveSupport::Deprecation.silence { connection.table_exists?(original_table_name) } + end + # Creates an internal metadata table with columns +key+ and +value+ def create_table + if original_table_exists? + connection.rename_table(original_table_name, table_name) + end unless table_exists? - connection.create_table(table_name, id: false) do |t| - t.column :key, :string, null: false, limit: KEY_LIMIT - t.column :value, :string - t.index :key, unique: true, name: index_name + key_options = connection.internal_string_options_for_primary_key + connection.create_table(table_name, id: false) do |t| + t.string :key, key_options + t.string :value t.timestamps end end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 722d7b5fce..ee52c3ae02 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -44,9 +44,9 @@ module ActiveRecord ## # :singleton-method: - # Accessor for the name of the internal metadata table. By default, the value is "active_record_internal_metadatas" + # Accessor for the name of the internal metadata table. By default, the value is "ar_internal_metadata" class_attribute :internal_metadata_table_name, instance_accessor: false - self.internal_metadata_table_name = "active_record_internal_metadatas" + self.internal_metadata_table_name = "ar_internal_metadata" ## # :singleton-method: diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 4d661735cc..d9a394fb71 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -106,7 +106,7 @@ module ActiveRecord # the existing record gets updated. # # By default, save always runs validations. If any of them fail the action - # is cancelled and #save returns +false+. However, if you supply + # is cancelled and #save returns +false+, and the record won't be saved. However, if you supply # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # @@ -133,7 +133,7 @@ module ActiveRecord # the existing record gets updated. # # By default, #save! always runs validations. If any of them fail - # ActiveRecord::RecordInvalid gets raised. However, if you supply + # ActiveRecord::RecordInvalid gets raised, and the record won't be saved. However, if you supply # validate: false, validations are bypassed altogether. See # ActiveRecord::Validations for more information. # @@ -270,7 +270,7 @@ module ActiveRecord alias update_attributes update # Updates its receiver just like #update but calls #save! instead - # of +save+, so an exception is raised if the record is invalid. + # of +save+, so an exception is raised if the record is invalid and saving will fail. def update!(attributes) # The following transaction covers any possible database side-effects of the # attributes assignment. For example, setting the IDs of a child collection. diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 1f429cfd94..de5b42e987 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -1,7 +1,7 @@ module ActiveRecord module Querying delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all - delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all @@ -35,8 +35,8 @@ module ActiveRecord # # Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date] # Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }] - def find_by_sql(sql, binds = []) - result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds) + def find_by_sql(sql, binds = [], preparable: nil) + result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable) column_types = result_set.column_types.dup columns_hash.each_key { |k| column_types.delete k } message_bus = ActiveSupport::Notifications.instrumenter diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 38916f7376..f4200e96b7 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -40,7 +40,7 @@ module ActiveRecord task :load_config do ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration - if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH) + if defined?(ENGINE_ROOT) && engine = Rails::Engine.find(ENGINE_ROOT) if engine.paths['db/migrate'].existent ActiveRecord::Tasks::DatabaseTasks.migrations_paths += engine.paths['db/migrate'].to_a end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 320ced5afa..956fe7c51e 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -124,8 +124,19 @@ module ActiveRecord end end - # Holds all the methods that are shared between MacroReflection, AssociationReflection - # and ThroughReflection + # Holds all the methods that are shared between MacroReflection and ThroughReflection. + # + # AbstractReflection + # MacroReflection + # AggregateReflection + # AssociationReflection + # HasManyReflection + # HasOneReflection + # BelongsToReflection + # HasAndBelongsToManyReflection + # ThroughReflection + # PolymorphicReflection + # RuntimeReflection class AbstractReflection # :nodoc: def table_name klass.table_name @@ -228,18 +239,14 @@ module ActiveRecord def alias_candidate(name) "#{plural_name}_#{name}" end + + def chain + collect_join_chain + end end # Base class for AggregateReflection and AssociationReflection. Objects of # AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods. - # - # MacroReflection - # AggregateReflection - # AssociationReflection - # HasManyReflection - # HasOneReflection - # BelongsToReflection - # ThroughReflection class MacroReflection < AbstractReflection # Returns the name of the macro. # @@ -418,7 +425,7 @@ module ActiveRecord # A chain of reflections from this one back to the owner. For more see the explanation in # ThroughReflection. - def chain + def collect_join_chain [self] end @@ -483,28 +490,7 @@ module ActiveRecord # Returns +true+ if +self+ is a +has_one+ reflection. def has_one?; false; end - def association_class - case macro - when :belongs_to - if polymorphic? - Associations::BelongsToPolymorphicAssociation - else - Associations::BelongsToAssociation - end - when :has_many - if options[:through] - Associations::HasManyThroughAssociation - else - Associations::HasManyAssociation - end - when :has_one - if options[:through] - Associations::HasOneThroughAssociation - else - Associations::HasOneAssociation - end - end - end + def association_class; raise NotImplementedError; end def polymorphic? options[:polymorphic] @@ -513,6 +499,18 @@ module ActiveRecord VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key] + def add_as_source(seed) + seed + end + + def add_as_polymorphic_through(reflection, seed) + seed + [PolymorphicReflection.new(self, reflection)] + end + + def add_as_through(seed) + seed + [self] + end + protected def actual_source_reflection # FIXME: this is a horrible name @@ -522,14 +520,7 @@ module ActiveRecord private def calculate_constructable(macro, options) - case macro - when :belongs_to - !polymorphic? - when :has_one - !options[:through] - else - true - end + true end # Attempts to find the inverse association name automatically. @@ -622,34 +613,52 @@ module ActiveRecord end class HasManyReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super(name, scope, options, active_record) - end - def macro; :has_many; end def collection?; true; end - end - class HasOneReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super(name, scope, options, active_record) + def association_class + if options[:through] + Associations::HasManyThroughAssociation + else + Associations::HasManyAssociation + end end + end + class HasOneReflection < AssociationReflection # :nodoc: def macro; :has_one; end def has_one?; true; end - end - class BelongsToReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super(name, scope, options, active_record) + def association_class + if options[:through] + Associations::HasOneThroughAssociation + else + Associations::HasOneAssociation + end end + private + + def calculate_constructable(macro, options) + !options[:through] + end + end + + class BelongsToReflection < AssociationReflection # :nodoc: def macro; :belongs_to; end def belongs_to?; true; end + def association_class + if polymorphic? + Associations::BelongsToPolymorphicAssociation + else + Associations::BelongsToAssociation + end + end + def join_keys(association_klass) key = polymorphic? ? association_primary_key(association_klass) : association_primary_key JoinKeys.new(key, foreign_key) @@ -658,6 +667,12 @@ module ActiveRecord def join_id_for(owner) # :nodoc: owner[foreign_key] end + + private + + def calculate_constructable(macro, options) + !polymorphic? + end end class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: @@ -743,19 +758,8 @@ module ActiveRecord # # => [<ActiveRecord::Reflection::ThroughReflection: @delegate_reflection=#<ActiveRecord::Reflection::HasManyReflection: @name=:tags...>, # <ActiveRecord::Reflection::HasManyReflection: @name=:taggings, @options={}, @active_record=Post>] # - def chain - @chain ||= begin - a = source_reflection.chain - b = through_reflection.chain.map(&:dup) - - if options[:source_type] - b[0] = PolymorphicReflection.new(b[0], self) - end - - chain = a + b - chain[0] = self # Use self so we don't lose the information from :source_type - chain - end + def collect_join_chain + collect_join_reflections [self] end # This is for clearing cache on the reflection. Useful for tests that need to compare @@ -914,6 +918,27 @@ module ActiveRecord scope_chain end + def add_as_source(seed) + collect_join_reflections seed + end + + def add_as_polymorphic_through(reflection, seed) + collect_join_reflections(seed + [PolymorphicReflection.new(self, reflection)]) + end + + def add_as_through(seed) + collect_join_reflections(seed + [self]) + end + + def collect_join_reflections(seed) + a = source_reflection.add_as_source seed + if options[:source_type] + through_reflection.add_as_polymorphic_through self, a + else + through_reflection.add_as_through a + end + end + protected def actual_source_reflection # FIXME: this is a horrible name diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 032b8d4c5d..7e842668c6 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -47,7 +47,7 @@ module ActiveRecord if !primary_key_value && connection.prefetch_primary_key?(klass.table_name) primary_key_value = connection.next_sequence_value(klass.sequence_name) - values[klass.arel_table[klass.primary_key]] = primary_key_value + values[arel_attribute(klass.primary_key)] = primary_key_value end end @@ -105,6 +105,10 @@ module ActiveRecord [substitutes, binds] end + def arel_attribute(name) # :nodoc: + klass.arel_attribute(name, table) + end + # Initializes new record from relation while maintaining the current # scope. # @@ -373,9 +377,9 @@ module ActiveRecord stmt.table(table) if joins_values.any? - @klass.connection.join_to_update(stmt, arel, table[primary_key]) + @klass.connection.join_to_update(stmt, arel, arel_attribute(primary_key)) else - stmt.key = table[primary_key] + stmt.key = arel_attribute(primary_key) stmt.take(arel.limit) stmt.order(*arel.orders) stmt.wheres = arel.constraints @@ -527,7 +531,7 @@ module ActiveRecord stmt.from(table) if joins_values.any? - @klass.connection.join_to_delete(stmt, arel, table[primary_key]) + @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) else stmt.wheres = arel.constraints end diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 54587ae18e..de005e2810 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -204,15 +204,15 @@ module ActiveRecord yield yielded_relation break if ids.length < of - batch_relation = relation.where(table[primary_key].gt(primary_key_offset)) + batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset)) end end private def apply_limits(relation, start, finish) - relation = relation.where(table[primary_key].gteq(start)) if start - relation = relation.where(table[primary_key].lteq(finish)) if finish + relation = relation.where(arel_attribute(primary_key).gteq(start)) if start + relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish relation end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index f45844a9ea..54c9af4898 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -155,15 +155,7 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - column_names.map! do |column_name| - if column_name.is_a?(Symbol) && attribute_alias?(column_name) - attribute_alias(column_name) - else - column_name.to_s - end - end - - if loaded? && (column_names - @klass.column_names).empty? + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? return @records.pluck(*column_names) end @@ -172,7 +164,7 @@ module ActiveRecord else relation = spawn relation.select_values = column_names.map { |cn| - columns_hash.key?(cn) ? arel_table[cn] : cn + @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn } result = klass.connection.select_all(relation.arel, nil, bound_attributes) result.cast_values(klass.attribute_types) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 5d4a045097..a4280c5f33 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -145,23 +145,15 @@ module ActiveRecord # # [#<Person id:4>, #<Person id:3>, #<Person id:2>] def last(limit = nil) - return find_last(limit) if loaded? - - result = order_values.empty? && primary_key ? order(arel_table[primary_key].desc) : reverse_order - result = result.limit!(limit || 1) - limit ? result.reverse : result.first - rescue ActiveRecord::IrreversibleOrderError - ActiveSupport::Deprecation.warn(<<-WARNING.squish) - Finding a last element by loading the relation when SQL ORDER - can not be reversed is deprecated. - Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case. - Please call `to_a.last` if you still want to load the relation. - WARNING - find_last(limit) - end - - def find_last(limit) - limit ? to_a.last(limit) : to_a.last + if limit + if order_values.empty? && primary_key + order(arel_attribute(primary_key).desc).limit(limit).reverse + else + to_a.last(limit) + end + else + find_last + end end # Same as #last but raises ActiveRecord::RecordNotFound if no record @@ -250,6 +242,38 @@ module ActiveRecord find_nth! 41 end + # Find the third-to-last record. + # If no order is defined it will order by primary key. + # + # Person.third_to_last # returns the third-to-last object fetched by SELECT * FROM people + # Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3 + # Person.where(["user_name = :u", { u: user_name }]).third_to_last + def third_to_last + find_nth(-3) + end + + # Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record + # is found. + def third_to_last! + find_nth!(-3) + end + + # Find the second-to-last record. + # If no order is defined it will order by primary key. + # + # Person.second_to_last # returns the second-to-last object fetched by SELECT * FROM people + # Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3 + # Person.where(["user_name = :u", { u: user_name }]).second_to_last + def second_to_last + find_nth(-2) + end + + # Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record + # is found. + def second_to_last! + find_nth!(-2) + end + # Returns true if a record exists in the table that matches the +id+ or # conditions given, or false otherwise. The argument can take six forms: # @@ -522,7 +546,7 @@ module ActiveRecord # TODO: once the offset argument is removed from find_nth, # find_nth_with_limit_and_offset can be merged into this method relation = if order_values.empty? && primary_key - order(arel_table[primary_key].asc) + order(arel_attribute(primary_key).asc) else self end @@ -531,6 +555,19 @@ module ActiveRecord relation.limit(limit).to_a end + def find_last + if loaded? + @records.last + else + @last ||= + if limit_value + to_a.last + else + reverse_order.limit(1).to_a.first + end + end + end + private def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc: diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb index 063150958a..8a910a82fe 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -3,7 +3,7 @@ module ActiveRecord class RelationHandler # :nodoc: def call(attribute, value) if value.select_values.empty? - value = value.select(value.klass.arel_table[value.klass.primary_key]) + value = value.select(value.arel_attribute(value.klass.primary_key)) end attribute.in(value.arel) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 8ef9f9f627..91bfa4d131 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1093,8 +1093,8 @@ module ActiveRecord def arel_columns(columns) columns.map do |field| - if (Symbol === field || String === field) && columns_hash.key?(field.to_s) && !from_clause.value - arel_table[field] + if (Symbol === field || String === field) && (klass.has_attribute?(field) || klass.attribute_alias?(field)) && !from_clause.value + arel_attribute(field) elsif Symbol === field connection.quote_table_name(field.to_s) else @@ -1105,7 +1105,7 @@ module ActiveRecord def reverse_sql_order(order_query) if order_query.empty? - return [table[primary_key].desc] if primary_key + return [arel_attribute(primary_key).desc] if primary_key raise IrreversibleOrderError, "Relation has no current order and table has no primary key to be used as default order" end @@ -1170,12 +1170,10 @@ module ActiveRecord order_args.map! do |arg| case arg when Symbol - arg = klass.attribute_alias(arg) if klass.attribute_alias?(arg) - table[arg].asc + arel_attribute(arg).asc when Hash arg.map { |field, dir| - field = klass.attribute_alias(field) if klass.attribute_alias?(field) - table[field].send(dir.downcase) + arel_attribute(field).send(dir.downcase) } else arg diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 65005bd44b..f115c7542b 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -178,11 +178,11 @@ HEADER tbl.puts end - indexes(table, tbl) - tbl.puts " end" tbl.puts + indexes(table, tbl) + tbl.rewind stream.print tbl.read rescue => e @@ -198,7 +198,8 @@ HEADER if (indexes = @connection.indexes(table)).any? add_index_statements = indexes.map do |index| statement_parts = [ - "t.index #{index.columns.inspect}", + "add_index #{remove_prefix_and_suffix(index.table).inspect}", + index.columns.inspect, "name: #{index.name.inspect}", ] statement_parts << 'unique: true' if index.unique @@ -212,10 +213,11 @@ HEADER statement_parts << "using: #{index.using.inspect}" if index.using statement_parts << "type: #{index.type.inspect}" if index.type - " #{statement_parts.join(', ')}" + " #{statement_parts.join(', ')}" end stream.puts add_index_statements.sort.join("\n") + stream.puts end end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 8f0ab2b55b..b6cb233e03 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -16,22 +16,16 @@ module ActiveRecord "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" end - def index_name - "#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}" - end - def table_exists? ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } end - def create_table(limit=nil) + def create_table unless table_exists? - version_options = {null: false} - version_options[:limit] = limit if limit + version_options = connection.internal_string_options_for_primary_key connection.create_table(table_name, id: false) do |t| - t.column :version, :string, version_options - t.index :version, unique: true, name: index_name + t.string :version, version_options end end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 103569c84d..5395bd6076 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -151,6 +151,7 @@ module ActiveRecord "a class method with the same name." end + valid_scope_name?(name) extension = Module.new(&block) if block if body.respond_to?(:to_proc) @@ -169,6 +170,15 @@ module ActiveRecord end end end + + protected + + def valid_scope_name?(name) + if respond_to?(name, true) + logger.warn "Creating scope :#{name}. " \ + "Overwriting existing method #{self.name}.#{name}." + end + end end end end diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index f6b0efb88a..6c896ccea6 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -106,7 +106,7 @@ module ActiveRecord sql = query_builder.sql_for bind_values, connection - klass.find_by_sql sql, bind_values + klass.find_by_sql(sql, bind_values, preparable: true) end alias :call :execute end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index f9bb1cf5e0..0faad48ce3 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -22,7 +22,11 @@ module ActiveRecord end def arel_attribute(column_name) - arel_table[column_name] + if klass + klass.arel_attribute(column_name, arel_table) + else + arel_table[column_name] + end end def type(column_name) diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index a376e2a17f..f0aa4521b5 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -19,7 +19,7 @@ module ActiveRecord relation = build_relation(finder_class, table, attribute, value) if record.persisted? && finder_class.primary_key.to_s != attribute.to_s if finder_class.primary_key - relation = relation.where.not(finder_class.primary_key => record.id) + relation = relation.where.not(finder_class.primary_key => record.id_was) else raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.") end diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb index 107f107dc4..481c70201b 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb @@ -19,7 +19,11 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi def change create_join_table :<%= join_tables.first %>, :<%= join_tables.second %> do |t| <%- attributes.each do |attribute| -%> + <%- if attribute.reference? -%> + t.references :<%= attribute.name %><%= attribute.inject_options %> + <%- else -%> <%= '# ' unless attribute.has_index? -%>t.index <%= attribute.index_name %><%= attribute.inject_index_options %> + <%- end -%> <%- end -%> end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 9a9a4fed42..7c89fda582 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -33,11 +33,6 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase end end - def test_key_limit_max_matches_max - assert_equal ActiveRecord::InternalMetadata::KEY_LIMIT ,ActiveRecord::ConnectionAdapters::Mysql2Adapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN - end - - private def with_encoding_utf8mb4 diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb index b2a805333c..b56c226763 100644 --- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb +++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb @@ -24,9 +24,9 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase return skip("no extension support") end - @old_schema_migration_tabel_name = ActiveRecord::SchemaMigration.table_name - @old_tabel_name_prefix = ActiveRecord::Base.table_name_prefix - @old_tabel_name_suffix = ActiveRecord::Base.table_name_suffix + @old_schema_migration_table_name = ActiveRecord::SchemaMigration.table_name + @old_table_name_prefix = ActiveRecord::Base.table_name_prefix + @old_table_name_suffix = ActiveRecord::Base.table_name_suffix ActiveRecord::Base.table_name_prefix = "p_" ActiveRecord::Base.table_name_suffix = "_s" @@ -36,11 +36,11 @@ class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase end def teardown - ActiveRecord::Base.table_name_prefix = @old_tabel_name_prefix - ActiveRecord::Base.table_name_suffix = @old_tabel_name_suffix + ActiveRecord::Base.table_name_prefix = @old_table_name_prefix + ActiveRecord::Base.table_name_suffix = @old_table_name_suffix ActiveRecord::SchemaMigration.delete_all rescue nil ActiveRecord::Migration.verbose = true - ActiveRecord::SchemaMigration.table_name = @old_schema_migration_tabel_name + ActiveRecord::SchemaMigration.table_name = @old_schema_migration_table_name super end diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index f1995b243a..31e87722d9 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -53,12 +53,6 @@ module ActiveRecord end end - def test_composite_primary_key - with_example_table 'id serial, number serial, PRIMARY KEY (id, number)' do - assert_nil @connection.primary_key('ex') - end - end - def test_primary_key_raises_error_if_table_not_found assert_raises(ActiveRecord::StatementInvalid) do @connection.primary_key('unobtainium') diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 4aeca4d709..f50fe88b9b 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -166,7 +166,7 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase end end - def test_raise_wraped_exception_on_bad_prepare + def test_raise_wrapped_exception_on_bad_prepare assert_raises(ActiveRecord::StatementInvalid) do @connection.exec_query "select * from developers where id = ?", 'sql', [bind_param(1)] end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 038d9e2d0f..02c3358ba6 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -400,12 +400,6 @@ module ActiveRecord end end - def test_composite_primary_key - with_example_table 'id integer, number integer, foo integer, PRIMARY KEY (id, number)' do - assert_nil @conn.primary_key('ex') - end - end - def test_supports_extensions assert_not @conn.supports_extensions?, 'does not support extensions' end diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 1f32c48b95..9c99689c1e 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -21,7 +21,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Migration.verbose = @original_verbose end - def test_has_has_primary_key + def test_has_primary_key old_primary_key_prefix_type = ActiveRecord::Base.primary_key_prefix_type ActiveRecord::Base.primary_key_prefix_type = :table_name_with_underscore assert_equal "version", ActiveRecord::SchemaMigration.primary_key diff --git a/activerecord/test/cases/associations/callbacks_test.rb b/activerecord/test/cases/associations/callbacks_test.rb index a531e0e02c..a4298a25a6 100644 --- a/activerecord/test/cases/associations/callbacks_test.rb +++ b/activerecord/test/cases/associations/callbacks_test.rb @@ -177,14 +177,14 @@ class AssociationCallbacksTest < ActiveRecord::TestCase end def test_dont_add_if_before_callback_raises_exception - assert !@david.unchangable_posts.include?(@authorless) + assert !@david.unchangeable_posts.include?(@authorless) begin - @david.unchangable_posts << @authorless + @david.unchangeable_posts << @authorless rescue Exception end assert @david.post_log.empty? - assert !@david.unchangable_posts.include?(@authorless) + assert !@david.unchangeable_posts.include?(@authorless) @david.reload - assert !@david.unchangable_posts.include?(@authorless) + assert !@david.unchangeable_posts.include?(@authorless) end end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index ccb062f991..5c4586da19 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -827,12 +827,12 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_no_queries { david.projects.columns } end - def test_attributes_are_being_set_when_initialized_from_habm_association_with_where_clause + def test_attributes_are_being_set_when_initialized_from_habtm_association_with_where_clause new_developer = projects(:action_controller).developers.where(:name => "Marcelo").build assert_equal new_developer.name, "Marcelo" end - def test_attributes_are_being_set_when_initialized_from_habm_association_with_multiple_where_clauses + def test_attributes_are_being_set_when_initialized_from_habtm_association_with_multiple_where_clauses new_developer = projects(:action_controller).developers.where(:name => "Marcelo").where(:salary => 90_000).build assert_equal new_developer.name, "Marcelo" assert_equal new_developer.salary, 90_000 diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index ad157582a4..e975f4fbdd 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -408,6 +408,16 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end assert_no_queries do + bulbs.third_to_last() + bulbs.third_to_last({}) + end + + assert_no_queries do + bulbs.second_to_last() + bulbs.second_to_last({}) + end + + assert_no_queries do bulbs.last() bulbs.last({}) end @@ -2271,7 +2281,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal [], authors(:david).posts_with_signature.map(&:title) end - test 'associations autosaves when object is already persited' do + test 'associations autosaves when object is already persisted' do bulb = Bulb.create! tyre = Tyre.create! diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index 226ecf5447..bb8c9fa19c 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -884,7 +884,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set company = companies(:rails_core) ids = [Developer.first.id, -9999] - assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids} + assert_raises(ActiveRecord::AssociationTypeMismatch) {company.developer_ids= ids} end def test_build_a_model_from_hm_through_association_with_where_clause diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 94dfbc3346..1db52af59b 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -714,6 +714,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase end end + def test_time_zone_aware_attributes_dont_recurse_infinitely_on_invalid_values + in_time_zone "Pacific Time (US & Canada)" do + record = @target.new(bonus_time: []) + assert_equal nil, record.bonus_time + end + end + def test_setting_time_zone_conversion_for_attributes_should_write_value_on_class_variable Topic.skip_time_zone_conversion_for_attributes = [:field_a] Minimalistic.skip_time_zone_conversion_for_attributes = [:field_b] @@ -791,7 +798,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_nil computer.system end - def test_global_methods_are_overwritte_when_subclassing + def test_global_methods_are_overwritten_when_subclassing klass = Class.new(ActiveRecord::Base) { self.abstract_class = true } subklass = Class.new(klass) do diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ecdf508e3e..eef2d29d02 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -804,7 +804,7 @@ class BasicsTest < ActiveRecord::TestCase assert_equal dev.salary.amount, dup.salary.amount assert !dup.persisted? - # test if the attributes have been dupd + # test if the attributes have been duped original_amount = dup.salary.amount dev.salary.amount = 1 assert_equal original_amount, dup.salary.amount diff --git a/activerecord/test/cases/connection_specification/resolver_test.rb b/activerecord/test/cases/connection_specification/resolver_test.rb index 3c2f5d4219..358b6ad537 100644 --- a/activerecord/test/cases/connection_specification/resolver_test.rb +++ b/activerecord/test/cases/connection_specification/resolver_test.rb @@ -57,6 +57,12 @@ module ActiveRecord "encoding" => "utf8" }, spec) end + def test_url_missing_scheme + spec = resolve 'foo' + assert_equal({ + "database" => "foo" }, spec) + end + def test_url_host_db spec = resolve 'abstract://foo/bar?encoding=utf8' assert_equal({ diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index 69b0487dd8..067513e24c 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -201,8 +201,7 @@ if current_adapter?(:Mysql2Adapter) assert_equal '0', klass.columns_hash['zero'].default assert !klass.columns_hash['zero'].null - # 0 in MySQL 4, nil in 5. - assert [0, nil].include?(klass.columns_hash['omit'].default) + assert_equal nil, klass.columns_hash['omit'].default assert !klass.columns_hash['omit'].null assert_raise(ActiveRecord::StatementInvalid) { klass.create! } diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index cbeb37dd22..75a74c052d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -506,7 +506,7 @@ class FinderTest < ActiveRecord::TestCase end end - def test_take_and_first_and_last_with_integer_should_use_sql + def test_take_and_first_and_last_with_integer_should_use_sql_limit assert_sql(/LIMIT|ROWNUM <=/) { Topic.take(3).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.first(2).entries } assert_sql(/LIMIT|ROWNUM <=/) { Topic.last(5).entries } @@ -516,30 +516,16 @@ class FinderTest < ActiveRecord::TestCase assert_equal Topic.order("title").to_a.last(2), Topic.order("title").last(2) end - def test_last_with_integer_and_order_should_use_sql - relation = Topic.order("title") - assert_queries(1) { relation.last(5) } - assert !relation.loaded? + def test_last_with_integer_and_order_should_not_use_sql_limit + query = assert_sql { Topic.order("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) end - def test_last_with_integer_and_reorder_should_use_sql - relation = Topic.reorder("title") - assert_queries(1) { relation.last(5) } - assert !relation.loaded? - end - - def test_last_on_loaded_relation_should_not_use_sql - relation = Topic.limit(10).load - assert_no_queries do - relation.last - relation.last(2) - end - end - - def test_last_with_irreversible_order - assert_deprecated do - Topic.order("coalesce(author_name, title)").last - end + def test_last_with_integer_and_reorder_should_not_use_sql_limit + query = assert_sql { Topic.reorder("title").last(5).entries } + assert_equal 1, query.length + assert_no_match(/LIMIT/, query.first) end def test_take_and_first_and_last_with_integer_should_return_an_array diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index 6a9cdd9d29..6d5b6243db 100644 --- a/activerecord/test/cases/migration/compatibility_test.rb +++ b/activerecord/test/cases/migration/compatibility_test.rb @@ -71,6 +71,36 @@ module ActiveRecord ensure connection.drop_table :more_testings rescue nil end + + def test_timestamps_have_null_constraints_if_not_present_in_migration_of_create_table + migration = Class.new(ActiveRecord::Migration) { + def migrate(x) + create_table :more_testings do |t| + t.timestamps + end + end + }.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + assert connection.columns(:more_testings).find { |c| c.name == 'created_at' }.null + assert connection.columns(:more_testings).find { |c| c.name == 'updated_at' }.null + ensure + connection.drop_table :more_testings rescue nil + end + + def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table + migration = Class.new(ActiveRecord::Migration) { + def migrate(x) + add_timestamps :testings + end + }.new + + ActiveRecord::Migrator.new(:up, [migration]).migrate + + assert connection.columns(:testings).find { |c| c.name == 'created_at' }.null + assert connection.columns(:testings).find { |c| c.name == 'updated_at' }.null + end end end end diff --git a/activerecord/test/cases/migration/table_and_index_test.rb b/activerecord/test/cases/migration/table_and_index_test.rb deleted file mode 100644 index 24cba84a09..0000000000 --- a/activerecord/test/cases/migration/table_and_index_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "cases/helper" - -module ActiveRecord - class Migration - class TableAndIndexTest < ActiveRecord::TestCase - def test_add_schema_info_respects_prefix_and_suffix - conn = ActiveRecord::Base.connection - - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) - # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters - ActiveRecord::Base.table_name_prefix = 'p_' - ActiveRecord::Base.table_name_suffix = '_s' - conn.drop_table(ActiveRecord::Migrator.schema_migrations_table_name, if_exists: true) - - conn.initialize_schema_migrations_table - - assert_equal "p_unique_schema_migrations_s", conn.indexes(ActiveRecord::Migrator.schema_migrations_table_name)[0][:name] - ensure - ActiveRecord::Base.table_name_prefix = "" - ActiveRecord::Base.table_name_suffix = "" - end - end - end -end diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index f51e366b1d..bae0467e72 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -192,8 +192,6 @@ class MigrationTest < ActiveRecord::TestCase # of 0, they take on the compile-time limit for precision and scale, # so the following should succeed unless you have used really wacky # compilation options - # - SQLite2 has the default behavior of preserving all data sent in, - # so this happens there too assert_kind_of BigDecimal, b.value_of_e assert_equal BigDecimal("2.7182818284590452353602875"), b.value_of_e elsif current_adapter?(:SQLite3Adapter) @@ -357,14 +355,14 @@ class MigrationTest < ActiveRecord::TestCase def test_internal_metadata_table_name original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name - assert_equal "active_record_internal_metadatas", ActiveRecord::InternalMetadata.table_name - ActiveRecord::Base.table_name_prefix = "prefix_" - ActiveRecord::Base.table_name_suffix = "_suffix" + assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name + ActiveRecord::Base.table_name_prefix = "p_" + ActiveRecord::Base.table_name_suffix = "_s" Reminder.reset_table_name - assert_equal "prefix_active_record_internal_metadatas_suffix", ActiveRecord::InternalMetadata.table_name + assert_equal "p_ar_internal_metadata_s", ActiveRecord::InternalMetadata.table_name ActiveRecord::Base.internal_metadata_table_name = "changed" Reminder.reset_table_name - assert_equal "prefix_changed_suffix", ActiveRecord::InternalMetadata.table_name + assert_equal "p_changed_s", ActiveRecord::InternalMetadata.table_name ActiveRecord::Base.table_name_prefix = "" ActiveRecord::Base.table_name_suffix = "" Reminder.reset_table_name @@ -426,6 +424,21 @@ class MigrationTest < ActiveRecord::TestCase ENV["RACK_ENV"] = original_rack_env end + def test_rename_internal_metadata_table + original_internal_metadata_table_name = ActiveRecord::Base.internal_metadata_table_name + + ActiveRecord::Base.internal_metadata_table_name = "active_record_internal_metadatas" + Reminder.reset_table_name + + ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name + Reminder.reset_table_name + + assert_equal "ar_internal_metadata", ActiveRecord::InternalMetadata.table_name + ensure + ActiveRecord::Base.internal_metadata_table_name = original_internal_metadata_table_name + Reminder.reset_table_name + end + def test_proper_table_name_on_migration reminder_class = new_isolated_reminder_class migration = ActiveRecord::Migration.new diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index af15e63d9c..56092aaa0c 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -183,7 +183,7 @@ class PersistenceTest < ActiveRecord::TestCase end end - def test_dupd_becomes_persists_changes_from_the_original + def test_duped_becomes_persists_changes_from_the_original original = topics(:first) copy = original.dup.becomes(Reply) copy.save! diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 155210cce9..b918b36b94 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -262,7 +262,7 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase def test_primary_key_issues_warning warning = capture(:stderr) do - @connection.primary_key("barcodes") + assert_nil @connection.primary_key("barcodes") end assert_match(/WARNING: Rails does not support composite primary key\./, warning) end diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index d0f60a84b5..ffb2da7a26 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -26,6 +26,10 @@ module ActiveRecord def sanitize_sql_for_order(sql) sql end + + def arel_attribute(name, table) + table[name] + end end def relation diff --git a/activerecord/test/cases/relation/record_fetch_warning_test.rb b/activerecord/test/cases/relation/record_fetch_warning_test.rb index 62f0a7cc49..53daf436e5 100644 --- a/activerecord/test/cases/relation/record_fetch_warning_test.rb +++ b/activerecord/test/cases/relation/record_fetch_warning_test.rb @@ -7,7 +7,7 @@ module ActiveRecord def test_warn_on_records_fetched_greater_than original_logger = ActiveRecord::Base.logger - orginal_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than + original_warn_on_records_fetched_greater_than = ActiveRecord::Base.warn_on_records_fetched_greater_than log = StringIO.new ActiveRecord::Base.logger = ActiveSupport::Logger.new(log) @@ -22,7 +22,7 @@ module ActiveRecord assert_match(/Query fetched/, log.string) ensure ActiveRecord::Base.logger = original_logger - ActiveRecord::Base.warn_on_records_fetched_greater_than = orginal_warn_on_records_fetched_greater_than + ActiveRecord::Base.warn_on_records_fetched_greater_than = original_warn_on_records_fetched_greater_than end end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 7b93d20e05..8def74e75b 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -38,7 +38,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output end def test_schema_dump_uses_force_cascade_on_create_table @@ -159,7 +159,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output end def test_schema_dump_with_regexp_ignored_table @@ -167,28 +167,28 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "accounts"}, output assert_match %r{create_table "authors"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output end def test_schema_dumps_index_columns_in_right_order - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_index/).first.strip + index_definition = standard_dump.split(/\n/).grep(/add_index.*companies/).first.strip if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) - assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition + assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index", using: :btree', index_definition else - assert_equal 't.index ["firm_id", "type", "rating"], name: "company_index"', index_definition + assert_equal 'add_index "companies", ["firm_id", "type", "rating"], name: "company_index"', index_definition end end def test_schema_dumps_partial_indices - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_partial_index/).first.strip + index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip if current_adapter?(:PostgreSQLAdapter) - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "(rating > 10)", using: :btree', index_definition elsif current_adapter?(:Mysql2Adapter) - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", using: :btree', index_definition elsif current_adapter?(:SQLite3Adapter) && ActiveRecord::Base.connection.supports_partial_index? - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index", where: "rating > 10"', index_definition else - assert_equal 't.index ["firm_id", "type"], name: "company_partial_index"', index_definition + assert_equal 'add_index "companies", ["firm_id", "type"], name: "company_partial_index"', index_definition end end @@ -235,8 +235,8 @@ class SchemaDumperTest < ActiveRecord::TestCase def test_schema_dumps_index_type output = standard_dump - assert_match %r{t\.index \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output - assert_match %r{t\.index \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output + assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output + assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output end end @@ -345,7 +345,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_no_match %r{create_table "foo_.+_bar"}, output assert_no_match %r{add_index "foo_.+_bar"}, output assert_no_match %r{create_table "schema_migrations"}, output - assert_no_match %r{create_table "active_record_internal_metadatas"}, output + assert_no_match %r{create_table "ar_internal_metadata"}, output if ActiveRecord::Base.connection.supports_foreign_keys? assert_no_match %r{add_foreign_key "foo_.+_bar"}, output diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb index 7a8eaeccb7..acba97bbb8 100644 --- a/activerecord/test/cases/scoping/named_scoping_test.rb +++ b/activerecord/test/cases/scoping/named_scoping_test.rb @@ -440,6 +440,25 @@ class NamedScopingTest < ActiveRecord::TestCase end end + def test_scopes_with_reserved_names + class << Topic + def public_method; end + public :public_method + + def protected_method; end + protected :protected_method + + def private_method; end + private :private_method + end + + [:public_method, :protected_method, :private_method].each do |reserved_method| + assert Topic.respond_to?(reserved_method, true) + ActiveRecord::Base.logger.expects(:warn) + silence_warnings { Topic.scope(reserved_method, -> { }) } + end + end + def test_scopes_on_relations # Topic.replied approved_topics = Topic.all.approved.order('id DESC') diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index e9cdf94c99..ab63f5825c 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -104,7 +104,7 @@ class StoreTest < ActiveRecord::TestCase assert_equal true, user.settings.instance_of?(ActiveSupport::HashWithIndifferentAccess) end - test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do + test "convert store attributes from any format other than Hash or HashWithIndifferentAccess losing the data" do @john.json_data = "somedata" @john.height = 'low' assert_equal true, @john.json_data.instance_of?(ActiveSupport::HashWithIndifferentAccess) diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 637f89196e..8a7f19293d 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -34,6 +34,7 @@ class TransactionCallbacksTest < ActiveRecord::TestCase has_many :replies, class_name: "ReplyWithCallbacks", foreign_key: "parent_id" + before_commit { |record| record.do_before_commit(nil) } after_commit { |record| record.do_after_commit(nil) } after_create_commit { |record| record.do_after_commit(:create) } after_update_commit { |record| record.do_after_commit(:update) } @@ -47,6 +48,12 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @history ||= [] end + def before_commit_block(on = nil, &block) + @before_commit ||= {} + @before_commit[on] ||= [] + @before_commit[on] << block + end + def after_commit_block(on = nil, &block) @after_commit ||= {} @after_commit[on] ||= [] @@ -59,6 +66,11 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @after_rollback[on] << block end + def do_before_commit(on) + blocks = @before_commit[on] if defined?(@before_commit) + blocks.each{|b| b.call(self)} if blocks + end + def do_after_commit(on) blocks = @after_commit[on] if defined?(@after_commit) blocks.each{|b| b.call(self)} if blocks @@ -74,6 +86,20 @@ class TransactionCallbacksTest < ActiveRecord::TestCase @first = TopicWithCallbacks.find(1) end + # FIXME: Test behavior, not implementation. + def test_before_commit_exception_should_pop_transaction_stack + @first.before_commit_block { raise 'better pop this txn from the stack!' } + + original_txn = @first.class.connection.current_transaction + + begin + @first.save! + fail + rescue + assert_equal original_txn, @first.class.connection.current_transaction + end + end + def test_call_after_commit_after_transaction_commits @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 7502a55391..e601c53dbf 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -469,4 +469,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert_match(/\AUnknown primary key for table dashboards in model/, e.message) assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message) end + + def test_validate_uniqueness_ignores_itself_when_primary_key_changed + Topic.validates_uniqueness_of(:title) + + t = Topic.new("title" => "This is a unique title") + assert t.save, "Should save t as unique" + + t.id += 1 + assert t.valid?, "Should be valid" + assert t.save, "Should still save t as unique" + end end diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 0d90cbb110..f25e31b13d 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -75,7 +75,7 @@ class Author < ActiveRecord::Base has_many :posts_with_multiple_callbacks, :class_name => "Post", :before_add => [:log_before_adding, Proc.new {|o, r| o.post_log << "before_adding_proc#{r.id || '<new>'}"}], :after_add => [:log_after_adding, Proc.new {|o, r| o.post_log << "after_adding_proc#{r.id || '<new>'}"}] - has_many :unchangable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding + has_many :unchangeable_posts, :class_name => "Post", :before_add => :raise_exception, :after_add => :log_after_adding has_many :categorizations has_many :categories, :through => :categorizations diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb index 16ff6de24d..101e657982 100644 --- a/activerecord/test/schema/mysql2_specific_schema.rb +++ b/activerecord/test/schema/mysql2_specific_schema.rb @@ -21,16 +21,15 @@ ActiveRecord::Schema.define do t.index :var_binary end - create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t| + create_table :key_tests, force: true, options: 'ENGINE=MyISAM' do |t| t.string :awesome t.string :pizza t.string :snacks + t.index :awesome, type: :fulltext, name: 'index_key_tests_on_awesome' + t.index :pizza, using: :btree, name: 'index_key_tests_on_pizza' + t.index :snacks, name: 'index_key_tests_on_snack' end - add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome' - add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza' - add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack' - create_table :collation_tests, id: false, force: true do |t| t.string :string_cs_column, limit: 1, collation: 'utf8_bin' t.string :string_ci_column, limit: 1, collation: 'utf8_general_ci' diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index fcc4eee79c..b9e0706d60 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -202,12 +202,11 @@ ActiveRecord::Schema.define do t.integer :rating, default: 1 t.integer :account_id t.string :description, default: "" + t.index [:firm_id, :type, :rating], name: "company_index" + t.index [:firm_id, :type], name: "company_partial_index", where: "rating > 10" + t.index :name, name: 'company_name_index', using: :btree end - add_index :companies, [:firm_id, :type, :rating], name: "company_index" - add_index :companies, [:firm_id, :type], name: "company_partial_index", where: "rating > 10" - add_index :companies, :name, name: 'company_name_index', using: :btree - create_table :content, force: true do |t| t.string :title end @@ -304,8 +303,8 @@ ActiveRecord::Schema.define do create_table :edges, force: true, id: false do |t| t.column :source_id, :integer, null: false t.column :sink_id, :integer, null: false + t.index [:source_id, :sink_id], unique: true, name: 'unique_edge_index' end - add_index :edges, [:source_id, :sink_id], unique: true, name: 'unique_edge_index' create_table :engines, force: true do |t| t.integer :car_id @@ -782,8 +781,8 @@ ActiveRecord::Schema.define do t.string :nick, null: false t.string :name t.column :books_count, :integer, null: false, default: 0 + t.index :nick, unique: true end - add_index :subscribers, :nick, unique: true create_table :subscriptions, force: true do |t| t.string :subscriber_id diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 7701e0a7a2..3d4cc8fae6 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,12 @@ +* Add `Array#second_to_last` and `Array#third_to_last` methods. + + *Brian Christian* + +* Fix regression in `Hash#dig` for HashWithIndifferentAccess. + *Jon Moss* + +## Rails 5.0.0.beta2 (February 01, 2016) ## + * Change number_to_currency behavior for checking negativity. Used `to_f.negative` instead of using `to_f.phase` for checking negativity @@ -42,44 +51,44 @@ Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it doesn't need itself along: - module Current - thread_mattr_accessor :account - thread_mattr_accessor :user + module Current + thread_mattr_accessor :account + thread_mattr_accessor :user - def self.reset() self.account = self.user = nil end - end + def self.reset() self.account = self.user = nil end + end - class ApplicationController < ActionController::Base - before_action :set_current - after_action { Current.reset } + class ApplicationController < ActionController::Base + before_action :set_current + after_action { Current.reset } - private - def set_current - Current.account = Account.find(params[:account_id]) - Current.user = Current.account.users.find(params[:user_id]) + private + def set_current + Current.account = Account.find(params[:account_id]) + Current.user = Current.account.users.find(params[:user_id]) + end end - end - class MessagesController < ApplicationController - def create - @message = Message.create!(message_params) - end - end + class MessagesController < ApplicationController + def create + @message = Message.create!(message_params) + end + end - class Message < ApplicationRecord - has_many :events - after_create :track_created + class Message < ApplicationRecord + has_many :events + after_create :track_created - private - def track_created - events.create! origin: self, action: :create + private + def track_created + events.create! origin: self, action: :create + end end - end - class Event < ApplicationRecord - belongs_to :creator, class_name: 'User' - before_validation { self.creator ||= Current.user } - end + class Event < ApplicationRecord + belongs_to :creator, class_name: 'User' + before_validation { self.creator ||= Current.user } + end *DHH* diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 81c242d4b1..33ee62aa1b 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -1,6 +1,10 @@ require 'rake/testtask' task :default => :test + +task :package +task "package:clean" + Rake::TestTask.new do |t| t.libs << 'test' t.pattern = 'test/**/*_test.rb' diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 32e28c0212..68a80701ed 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -21,9 +21,7 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] s.add_dependency 'i18n', '~> 0.7' - s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' s.add_dependency 'concurrent-ruby', '~> 1.0' - s.add_dependency 'method_source' end diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb index ca48164c54..54244317e4 100644 --- a/activesupport/lib/active_support/concurrency/share_lock.rb +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -6,12 +6,6 @@ module ActiveSupport # A share/exclusive lock, otherwise known as a read/write lock. # # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock - #-- - # Note that a pending Exclusive lock attempt does not block incoming - # Share requests (i.e., we are "read-preferring"). That seems - # consistent with the behavior of "loose" upgrades, but may be the - # wrong choice otherwise: it nominally reduces the possibility of - # deadlock by risking starvation instead. class ShareLock include MonitorMixin @@ -48,17 +42,11 @@ module ActiveSupport def start_exclusive(purpose: nil, compatible: [], no_wait: false) synchronize do unless @exclusive_thread == Thread.current - if busy?(purpose) + if busy_for_exclusive?(purpose) return false if no_wait - loose_shares = @sharing.delete(Thread.current) - @waiting[Thread.current] = compatible if loose_shares - - begin - @cv.wait_while { busy?(purpose) } - ensure - @waiting.delete Thread.current - @sharing[Thread.current] = loose_shares if loose_shares + yield_shares(purpose: purpose, compatible: compatible, block_share: true) do + @cv.wait_while { busy_for_exclusive?(purpose) } end end @exclusive_thread = Thread.current @@ -71,13 +59,19 @@ module ActiveSupport # Relinquish the exclusive lock. Must only be called by the thread # that called start_exclusive (and currently holds the lock). - def stop_exclusive + def stop_exclusive(compatible: []) synchronize do raise "invalid unlock" if @exclusive_thread != Thread.current @exclusive_depth -= 1 if @exclusive_depth == 0 @exclusive_thread = nil + + if eligible_waiters?(compatible) + yield_shares(compatible: compatible, block_share: true) do + @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) } + end + end @cv.broadcast end end @@ -85,8 +79,16 @@ module ActiveSupport def start_sharing synchronize do - if @exclusive_thread && @exclusive_thread != Thread.current + if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current + # We already hold a lock; nothing to wait for + elsif @waiting[Thread.current] + # We're nested inside a +yield_shares+ call: we'll resume as + # soon as there isn't an exclusive lock in our way @cv.wait_while { @exclusive_thread } + else + # This is an initial / outermost share call: any outstanding + # requests for an exclusive lock get to go first + @cv.wait_while { busy_for_sharing?(false) } end @sharing[Thread.current] += 1 end @@ -109,12 +111,12 @@ module ActiveSupport # the block. # # See +start_exclusive+ for other options. - def exclusive(purpose: nil, compatible: [], no_wait: false) + def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false) if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait) begin yield ensure - stop_exclusive + stop_exclusive(compatible: after_compatible) end end end @@ -129,14 +131,56 @@ module ActiveSupport end end + # Temporarily give up all held Share locks while executing the + # supplied block, allowing any +compatible+ exclusive lock request + # to proceed. + def yield_shares(purpose: nil, compatible: [], block_share: false) + loose_shares = previous_wait = nil + synchronize do + if loose_shares = @sharing.delete(Thread.current) + if previous_wait = @waiting[Thread.current] + purpose = nil unless purpose == previous_wait[0] + compatible &= previous_wait[1] + end + compatible |= [false] unless block_share + @waiting[Thread.current] = [purpose, compatible] + + @cv.broadcast + end + end + + begin + yield + ensure + synchronize do + @cv.wait_while { @exclusive_thread && @exclusive_thread != Thread.current } + + if previous_wait + @waiting[Thread.current] = previous_wait + else + @waiting.delete Thread.current + end + @sharing[Thread.current] = loose_shares if loose_shares + end + end + end + private # Must be called within synchronize - def busy?(purpose) - (@exclusive_thread && @exclusive_thread != Thread.current) || - @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } || + def busy_for_exclusive?(purpose) + busy_for_sharing?(purpose) || @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) end + + def busy_for_sharing?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) } + end + + def eligible_waiters?(compatible) + @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } } + end end end end diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 3177d8498e..37d833887a 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -73,4 +73,18 @@ class Array def forty_two self[41] end + + # Equal to <tt>self[-3]</tt>. + # + # %w( a b c d e ).third_to_last # => "c" + def third_to_last + self[-3] + end + + # Equal to <tt>self[-2]</tt>. + # + # %w( a b c d e ).second_to_last # => "d" + def second_to_last + self[-2] + end end diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb index bf72caa058..18bcc01fa4 100644 --- a/activesupport/lib/active_support/core_ext/kernel/concern.rb +++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/module/concerning' module Kernel + module_function + # A shortcut to define a toplevel concern, not within a module. # # See Module::Concerning for more. diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 8afc258df8..d0197af95f 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,4 +1,6 @@ module Kernel + module_function + # Sets $VERBOSE to nil for the duration of the block and back to its original # value afterwards. # diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 6251f34daf..43b9fd4bf7 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -171,7 +171,7 @@ module ActiveSupport #:nodoc: original_concat(value) end - def initialize(*) + def initialize(str = '') @html_safe = true super end diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb index fbeb904684..47bcecbb35 100644 --- a/activesupport/lib/active_support/dependencies/interlock.rb +++ b/activesupport/lib/active_support/dependencies/interlock.rb @@ -8,13 +8,13 @@ module ActiveSupport #:nodoc: end def loading - @lock.exclusive(purpose: :load, compatible: [:load]) do + @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do yield end end def unloading - @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do yield end end @@ -24,7 +24,7 @@ module ActiveSupport #:nodoc: # concurrent activity, return immediately (without executing the # block) instead of waiting. def attempt_unloading - @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload], no_wait: true) do yield end end @@ -42,6 +42,12 @@ module ActiveSupport #:nodoc: yield end end + + def permit_concurrent_loads + @lock.yield_shares(compatible: [:load]) do + yield + end + end end end end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 0de891f1a2..dc24e2d0e1 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -11,7 +11,7 @@ module ActiveSupport DEFAULT_BEHAVIORS = { raise: ->(message, callstack) { e = DeprecationException.new(message) - e.set_backtrace(callstack) + e.set_backtrace(callstack.map(&:to_s)) raise e }, diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 6f0ad445fc..0cb2d4d22e 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -80,7 +80,7 @@ module ActiveSupport # example.old_request.to_s # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of # @request.to_s - # (Bactrace information…) + # (Backtrace information…) # "special_request" # # example.request.to_s @@ -118,7 +118,7 @@ module ActiveSupport # # PLANETS.map { |planet| planet.capitalize } # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. - # (Bactrace information…) + # (Backtrace information…) # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] class DeprecatedConstantProxy < DeprecationProxy def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 3104d0e00f..fc08273b6d 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -8,7 +8,7 @@ module ActiveSupport MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index b878f31e75..03770a197c 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -69,9 +69,13 @@ module ActiveSupport end def default(*args) - key = args.first - args[0] = key.to_s if key.is_a?(Symbol) - super(*args) + arg_key = args.first + + if include?(key = convert_key(arg_key)) + self[key] + else + super + end end def self.new_from_hash_copying_default(hash) diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb index 3f1e0c4cb4..1d834667f0 100644 --- a/activesupport/test/core_ext/array/access_test.rb +++ b/activesupport/test/core_ext/array/access_test.rb @@ -26,6 +26,8 @@ class AccessTest < ActiveSupport::TestCase assert_equal array[3], array.fourth assert_equal array[4], array.fifth assert_equal array[41], array.forty_two + assert_equal array[-3], array.third_to_last + assert_equal array[-2], array.second_to_last end def test_without diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 1b66f784e4..be8583e704 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -702,6 +702,12 @@ class HashExtTest < ActiveSupport::TestCase assert_equal h.class, h.dup.class end + def test_nested_dig_indifferent_access + skip if RUBY_VERSION < "2.3.0" + data = {"this" => {"views" => 1234}}.with_indifferent_access + assert_equal 1234, data.dig(:this, :views) + end + def test_assert_valid_keys assert_nothing_raised do { :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ]) diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 58a0a3964d..45c88b79cb 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -105,13 +105,13 @@ class DeprecationTest < ActiveSupport::TestCase ActiveSupport::Deprecation.behavior = :raise message = 'Revise this deprecated stuff now!' - callstack = %w(foo bar baz) + callstack = caller_locations e = assert_raise ActiveSupport::DeprecationException do ActiveSupport::Deprecation.behavior.first.call(message, callstack) end assert_equal message, e.message - assert_equal callstack, e.backtrace + assert_equal callstack.map(&:to_s), e.backtrace.map(&:to_s) end def test_default_stderr_behavior diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb index 465a657308..acefa185a8 100644 --- a/activesupport/test/share_lock_test.rb +++ b/activesupport/test/share_lock_test.rb @@ -114,14 +114,17 @@ class ShareLockTest < ActiveSupport::TestCase [true, false].each do |use_upgrading| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| begin + together = Concurrent::CyclicBarrier.new(2) conflicting_exclusive_threads = [ Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do + together.wait @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {} end end, Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do + together.wait @lock.exclusive(purpose: :blue, compatible: [:green]) {} end end @@ -183,11 +186,14 @@ class ShareLockTest < ActiveSupport::TestCase load_params = [:load, [:load]] unload_params = [:unload, [:unload, :load]] + all_sharing = Concurrent::CyclicBarrier.new(4) + [load_params, load_params, unload_params, unload_params].permutation do |thread_params| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| threads = thread_params.map do |purpose, compatible| Thread.new do @lock.sharing do + all_sharing.wait @lock.exclusive(purpose: purpose, compatible: compatible) do scratch_pad_mutex.synchronize { scratch_pad << purpose } end @@ -209,6 +215,245 @@ class ShareLockTest < ActiveSupport::TestCase end end + def test_new_share_attempts_block_on_waiting_exclusive + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + release_exclusive = Concurrent::CountDownLatch.new + + waiting_exclusive = Thread.new do + @lock.sharing do + @lock.exclusive do + release_exclusive.wait + end + end + end + assert_threads_stuck waiting_exclusive + + late_share_attempt = Thread.new do + @lock.sharing {} + end + assert_threads_stuck late_share_attempt + + sharing_thread_release_latch.count_down + assert_threads_stuck late_share_attempt + + release_exclusive.count_down + assert_threads_not_stuck late_share_attempt + end + end + + def test_share_remains_reentrant_ignoring_a_waiting_exclusive + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + ready = Concurrent::CyclicBarrier.new(2) + attempt_reentrancy = Concurrent::CountDownLatch.new + + sharer = Thread.new do + @lock.sharing do + ready.wait + attempt_reentrancy.wait + @lock.sharing {} + end + end + + exclusive = Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive {} + end + end + + assert_threads_stuck exclusive + + attempt_reentrancy.count_down + + assert_threads_not_stuck sharer + assert_threads_stuck exclusive + end + end + + def test_compatible_exclusives_cooperate_to_both_proceed + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + + threads = 2.times.map do + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x, compatible: [:x], after_compatible: [:x]) {} + done.wait + end + end + end + + assert_threads_not_stuck threads + end + + def test_manual_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:x]) do + done.wait + end + end + end, + ] + + assert_threads_not_stuck threads + end + + def test_manual_incompatible_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + done.wait + end + end + end, + ] + + assert_threads_stuck threads + ensure + threads.each(&:kill) if threads + end + + def test_manual_recursive_yield + ready = Concurrent::CyclicBarrier.new(2) + done = Concurrent::CyclicBarrier.new(2) + do_nesting = Concurrent::CountDownLatch.new + + threads = [ + Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + done.wait + end + end, + + Thread.new do + @lock.sharing do + @lock.yield_shares(compatible: [:x]) do + @lock.sharing do + ready.wait + do_nesting.wait + @lock.yield_shares(compatible: [:x, :y]) do + done.wait + end + end + end + end + end + ] + + assert_threads_stuck threads + do_nesting.count_down + + assert_threads_not_stuck threads + end + + def test_manual_recursive_yield_cannot_expand_outer_compatible + ready = Concurrent::CyclicBarrier.new(2) + do_compatible_nesting = Concurrent::CountDownLatch.new + in_compatible_nesting = Concurrent::CountDownLatch.new + + incompatible_thread = Thread.new do + @lock.sharing do + ready.wait + @lock.exclusive(purpose: :x) {} + end + end + + yield_shares_thread = Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + do_compatible_nesting.wait + @lock.sharing do + @lock.yield_shares(compatible: [:x, :y]) do + in_compatible_nesting.wait + end + end + end + end + end + + assert_threads_stuck incompatible_thread + do_compatible_nesting.count_down + assert_threads_stuck incompatible_thread + in_compatible_nesting.count_down + assert_threads_not_stuck [yield_shares_thread, incompatible_thread] + end + + def test_manual_recursive_yield_restores_previous_compatible + ready = Concurrent::CyclicBarrier.new(2) + do_nesting = Concurrent::CountDownLatch.new + after_nesting = Concurrent::CountDownLatch.new + + incompatible_thread = Thread.new do + ready.wait + @lock.exclusive(purpose: :z) {} + end + + recursive_yield_shares_thread = Thread.new do + @lock.sharing do + ready.wait + @lock.yield_shares(compatible: [:y]) do + do_nesting.wait + @lock.sharing do + @lock.yield_shares(compatible: [:x, :y]) {} + end + after_nesting.wait + end + end + end + + assert_threads_stuck incompatible_thread + do_nesting.count_down + assert_threads_stuck incompatible_thread + + compatible_thread = Thread.new do + @lock.exclusive(purpose: :y) {} + end + assert_threads_not_stuck compatible_thread + + post_nesting_incompatible_thread = Thread.new do + @lock.exclusive(purpose: :x) {} + end + assert_threads_stuck post_nesting_incompatible_thread + + after_nesting.count_down + assert_threads_not_stuck recursive_yield_shares_thread + # post_nesting_incompatible_thread can now proceed + assert_threads_not_stuck post_nesting_incompatible_thread + # assert_threads_not_stuck can now proceed + assert_threads_not_stuck incompatible_thread + end + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads scratch_pad = [] scratch_pad_mutex = Mutex.new diff --git a/ci/travis.rb b/ci/travis.rb index e9a3626b9a..063c6acb07 100755 --- a/ci/travis.rb +++ b/ci/travis.rb @@ -157,20 +157,6 @@ ENV['GEM'].split(',').each do |gem| end end -# puts -# puts "Build environment:" -# puts " #{`cat /etc/issue`}" -# puts " #{`uname -a`}" -# puts " #{`ruby -v`}" -# puts " #{`mysql --version`}" -# puts " #{`pg_config --version`}" -# puts " SQLite3: #{`sqlite3 -version`}" -# `gem env`.each_line {|line| print " #{line}"} -# puts " Bundled gems:" -# `bundle show`.each_line {|line| print " #{line}"} -# puts " Local gems:" -# `gem list`.each_line {|line| print " #{line}"} - failures = results.select { |key, value| !value } if failures.empty? diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index aae405d5ac..d58016053b 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 5.0.0.beta2 (February 01, 2016) ## + +* No changes. + + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Add code of conduct to contributing guide diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index 0a8048cc48..fcc90fa503 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -19,9 +19,6 @@ require 'active_support' require 'active_support/core_ext/object/blank' require 'minitest/autorun' -# Ensure backward compatibility with Minitest 4 -Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test) - class BugTest < Minitest::Test def test_stuff assert "zomg".present? diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 2650384df3..4e8252f85b 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -253,6 +253,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. `ActionDispatch::IntegrationTest` instead. ([commit](https://github.com/rails/rails/commit/4414c5d1795e815b102571425974a8b1d46d932d)) +* Rails will only generate "weak", instead of strong ETags. + ([Pull Request](https://github.com/rails/rails/pull/17573)) + Action View ------------- @@ -378,6 +381,9 @@ Please refer to the [Changelog][active-record] for detailed changes. * Removed support for the `protected_attributes` gem. ([commit](https://github.com/rails/rails/commit/f4fbc0301021f13ae05c8e941c8efc4ae351fdf9)) +* Removed support for PostgreSQL versions below 9.1. + ([Pull Request](https://github.com/rails/rails/pull/23434)) + ### Deprecations * Deprecated passing a class as a value in a query. Users should pass strings diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 76c13f0ea9..d8ea1ee079 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -109,10 +109,12 @@ That's it! Job Execution ------------- -For enqueuing and executing jobs you need to set up a queuing backend, that is to -say you need to decide for a 3rd-party queuing library that Rails should use. -Rails itself does not provide a sophisticated queuing system and just executes the -job immediately if no adapter is set. +For enqueuing and executing jobs in production you need to set up a queuing backend, +that is to say you need to decide for a 3rd-party queuing library that Rails should use. +Rails itself only provides an in-process queuing system, which only keeps the jobs in RAM. +If the process crashes or the machine is reset, then all outstanding jobs are lost with the +default async back-end. This may be fine for smaller apps or non-critical jobs, but most +production apps will need to pick a persistent backend. ### Backends diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 83f4b951ee..bd7dbd0f11 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -883,7 +883,7 @@ Changing Existing Migrations ---------------------------- Occasionally you will make a mistake when writing a migration. If you have -already run the migration then you cannot just edit the migration and run the +already run the migration, then you cannot just edit the migration and run the migration again: Rails thinks it has already run the migration and so will do nothing when you run `rails db:migrate`. You must rollback the migration (for example with `bin/rails db:rollback`), edit your migration and then run @@ -933,7 +933,7 @@ There are two ways to dump the schema. This is set in `config/application.rb` by the `config.active_record.schema_format` setting, which may be either `:sql` or `:ruby`. -If `:ruby` is selected then the schema is stored in `db/schema.rb`. If you look +If `:ruby` is selected, then the schema is stored in `db/schema.rb`. If you look at this file you'll find that it looks an awful lot like one very big migration: diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 68c6a77882..5eb19f5214 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -14,7 +14,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -In order to use the PostgreSQL adapter you need to have at least version 8.2 +In order to use the PostgreSQL adapter you need to have at least version 9.1 installed. Older versions are not supported. To get started with PostgreSQL have a look at the diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 63658e7c8b..1235c04c50 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1296,6 +1296,28 @@ Using a class method is the preferred way to accept arguments for scopes. These category.articles.created_before(time) ``` +### Using conditionals + +Your scope can utilize conditionals: + +```ruby +class Article < ApplicationRecord + scope :created_before, ->(time) { where("created_at < ?", time) if time.present? } +end +``` + +Like the other examples, this will behave similarly to a class method. + +```ruby +class Article < ApplicationRecord + def self.created_before(time) + where("created_at < ?", time) if time.present? + end +end +``` + +However, there is one important caveat: A scope will always return an `ActiveRecord::Relation` object, even if the conditional evaluates to `false`, whereas a class method, will return `nil`. This can cause `NoMethodError` when chaining class methods with conditionals, if any of the conditionals return `false`. + ### Applying a default scope If we wish for a scope to be applied across all queries to the model we can use the diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 0aca6db9b6..10122629b2 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -2240,7 +2240,7 @@ Similarly, `from` returns the tail from the element at the passed index to the e [].from(0) # => [] ``` -The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element (`first` is built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available. +The methods `second`, `third`, `fourth`, and `fifth` return the corresponding element, as do `second_to_last` and `third_to_last` (`first` and `last` are built-in). Thanks to social wisdom and positive constructiveness all around, `forty_two` is also available. ```ruby %w(a b c d).third # => "c" diff --git a/guides/source/api_app.md b/guides/source/api_app.md index e3481ce046..563214896a 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -135,6 +135,8 @@ If you're building a Rails application that will be an API server first and foremost, you can start with a more limited subset of Rails and add in features as needed. +### Creating a new application + You can generate a new api Rails app: ```bash @@ -153,6 +155,8 @@ This will do three main things for you: - Configure the generators to skip generating views, helpers and assets when you generate a new resource. +### Changing an existing application + If you want to take an existing application and make it an API one, read the following steps. @@ -262,9 +266,6 @@ subsequent inbound requests for the same URL. Think of it as page caching using HTTP semantics. -NOTE: This middleware is always outside of the `Rack::Lock` mutex, even in -single-threaded applications. - ### Using Rack::Sendfile When you use the `send_file` method inside a Rails controller, it sets the diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 5bdaf600ad..4efffc6605 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -21,8 +21,11 @@ What is the Asset Pipeline? The asset pipeline provides a framework to concatenate and minify or compress JavaScript and CSS assets. It also adds the ability to write these assets in other languages and pre-processors such as CoffeeScript, Sass and ERB. +It allows assets in your application to be automatically combined with assets +from other gems. For example, jquery-rails includes a copy of jquery.js +and enables AJAX features in Rails. -The asset pipeline is technically no longer a core feature of Rails 4, it has +The asset pipeline is technically no longer a core feature from Rails 4 onwards -- it has been extracted out of the framework into the [sprockets-rails](https://github.com/rails/sprockets-rails) gem. @@ -35,7 +38,7 @@ passing the `--skip-sprockets` option. rails new appname --skip-sprockets ``` -Rails 4 automatically adds the `sass-rails`, `coffee-rails` and `uglifier` +Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier` gems to your Gemfile, which are used by Sprockets for asset compression: ```ruby @@ -44,8 +47,8 @@ gem 'uglifier' gem 'coffee-rails' ``` -Using the `--skip-sprockets` option will prevent Rails 4 from adding -`sass-rails` and `uglifier` to your Gemfile, so if you later want to enable +Using the `--skip-sprockets` option will prevent Rails from adding +them to your Gemfile, so if you later want to enable the asset pipeline you will have to add those gems to your Gemfile. Also, creating an application with the `--skip-sprockets` option will generate a slightly different `config/application.rb` file, with a require statement @@ -327,7 +330,7 @@ familiar `javascript_include_tag` and `stylesheet_link_tag`: <%= javascript_include_tag "application" %> ``` -If using the turbolinks gem, which is included by default in Rails 4, then +If using the turbolinks gem, which is included by default in Rails, then include the 'data-turbolinks-track' option which causes turbolinks to check if an asset has been updated and if so loads it into the page: @@ -443,7 +446,7 @@ makes fewer requests. Compression also reduces file size, enabling the browser to download them faster. -For example, a new Rails 4 application includes a default +For example, a new Rails application includes a default `app/assets/javascripts/application.js` file containing the following lines: ```js @@ -484,7 +487,7 @@ which contains these lines: */ ``` -Rails 4 creates both `app/assets/javascripts/application.js` and +Rails creates both `app/assets/javascripts/application.js` and `app/assets/stylesheets/application.css` regardless of whether the --skip-sprockets option is used when creating a new rails application. This is so you can easily add asset pipelining later if you like. @@ -1108,7 +1111,7 @@ supported runtime in order to use `uglifier`. If you are using Mac OS X or Windows you have a JavaScript runtime installed in your operating system. NOTE: The `config.assets.compress` initialization option is no longer used in -Rails 4 to enable either CSS or JavaScript compression. Setting it will have no +Rails to enable either CSS or JavaScript compression. Setting it will have no effect on the application. Instead, setting `config.assets.css_compressor` and `config.assets.js_compressor` will control compression of CSS and JavaScript assets. @@ -1177,19 +1180,14 @@ TIP: For further details have a look at the docs of your production web server: Assets Cache Store ------------------ -The default Rails cache store will be used by Sprockets to cache assets in -development and production. This can be changed by setting -`config.assets.cache_store`: +By default, Sprockets caches assets in `tmp/cache/assets` in development +and production environments. This can be changed as follows: ```ruby -config.assets.cache_store = :memory_store -``` - -The options accepted by the assets cache store are the same as the application's -cache store. - -```ruby -config.assets.cache_store = :memory_store, { size: 32.megabytes } +config.assets.configure do |env| + env.cache = ActiveSupport::Cache.lookup_store(:memory_store, + { size: 32.megabytes }) +end ``` To disable the assets cache store: @@ -1295,7 +1293,7 @@ config.assets.digest = true # config.assets.precompile += %w( search.js ) ``` -Rails 4 no longer sets default config values for Sprockets in `test.rb`, so +Rails 4 and above no longer set default config values for Sprockets in `test.rb`, so `test.rb` now requires Sprockets configuration. The old defaults in the test environment are: `config.assets.compile = true`, `config.assets.compress = false`, `config.assets.debug = false` and `config.assets.digest = false`. diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index accce5a904..09ab64837a 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -713,7 +713,7 @@ By default, Active Record doesn't know about the connection between these associ ```ruby a = Author.first -b = c.books.first +b = a.books.first a.first_name == b.author.first_name # => true a.first_name = 'Manny' a.first_name == b.author.first_name # => false @@ -726,7 +726,7 @@ class Author < ApplicationRecord has_many :books, inverse_of: :author end -class book < ApplicationRecord +class Book < ApplicationRecord belongs_to :author, inverse_of: :books end ``` @@ -735,7 +735,7 @@ With these changes, Active Record will only load one copy of the author object, ```ruby a = author.first -b = c.books.first +b = a.books.first a.first_name == b.author.first_name # => true a.first_name = 'Manny' a.first_name == b.author.first_name # => true diff --git a/guides/source/command_line.md b/guides/source/command_line.md index f33e729de0..e865a02cbd 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -325,7 +325,7 @@ With the `helper` method it is possible to access Rails and your application's h ### `rails dbconsole` -`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL, SQLite and SQLite3. +`rails dbconsole` figures out which database you're using and drops you into whichever command line interface you would use with it (and figures out the command line parameters to give to it, too!). It supports MySQL, PostgreSQL and SQLite3. INFO: You can also use the alias "db" to invoke the dbconsole: `rails db`. @@ -432,7 +432,7 @@ Ruby version 2.2.2 (x86_64-linux) RubyGems version 2.4.6 Rack version 1.6 JavaScript Runtime Node.js (V8) -Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag +Middleware Rack::Sendfile, ActionDispatch::Static, ActionDispatch::LoadInterlock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 @@ -618,7 +618,7 @@ We had to create the **gitapp** directory and initialize an empty git repository ```bash $ cat config/database.yml -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Install the pg driver: # gem install pg diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 8a21d4062a..a5fb396f15 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -159,8 +159,6 @@ pipeline is enabled. It is set to true by default. * `config.assets.debug` disables the concatenation and compression of assets. Set to `true` by default in `development.rb`. -* `config.assets.cache_store` defines the cache store that Sprockets will use. The default is the Rails file store. - * `config.assets.compile` is a boolean that can be used to turn on live Sprockets compilation in production. * `config.assets.logger` accepts a logger conforming to the interface of Log4r or the default Ruby `Logger` class. Defaults to the same configured at `config.logger`. Setting `config.assets.logger` to false will turn off served assets logging. @@ -200,7 +198,7 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::SSL` forces every request to be served using HTTPS. Enabled if `config.force_ssl` is set to `true`. Options passed to this can be configured by setting `config.ssl_options`. * `ActionDispatch::Static` is used to serve static assets. Disabled if `config.public_file_server.enabled` is `false`. Set `config.public_file_server.index_name` if you need to serve a static directory index file that is not named `index`. For example, to serve `main.html` instead of `index.html` for directory requests, set `config.public_file_server.index_name` to `"main"`. -* `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. Only enabled when `config.cache_classes` is `false`. +* `ActionDispatch::LoadInterlock` allows thread safe code reloading. Disabled if `config.allow_concurrency` is `false`, which causes `Rack::Lock` to be loaded. `Rack::Lock` wraps the app in mutex so it can only be called by a single thread at a time. * `ActiveSupport::Cache::Strategy::LocalCache` serves as a basic memory backed cache. This cache is not thread safe and is intended only for serving as a temporary memory cache for a single thread. * `Rack::Runtime` sets an `X-Runtime` header, containing the time (in seconds) taken to execute the request. * `Rails::Rack::Logger` notifies the logs that the request has begun. After request is complete, flushes all the logs. @@ -1000,7 +998,7 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `initialize_cache` If `Rails.cache` isn't set yet, initializes the cache by referencing the value in `config.cache_store` and stores the outcome as `Rails.cache`. If this object responds to the `middleware` method, its middleware is inserted before `Rack::Runtime` in the middleware stack. -* `set_clear_dependencies_hook` Provides a hook for `active_record.set_dispatch_hooks` to use, which will run before this initializer. This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. +* `set_clear_dependencies_hook` This initializer - which runs only if `cache_classes` is set to `false` - uses `ActionDispatch::Callbacks.after` to remove the constants which have been referenced during the request from the object space so that they will be reloaded during the following request. * `initialize_dependency_mechanism` If `config.cache_classes` is true, configures `ActiveSupport::Dependencies.mechanism` to `require` dependencies rather than `load` them. @@ -1014,13 +1012,17 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_support.initialize_beginning_of_week` Sets the default beginning of week for the application based on `config.beginning_of_week` setting, which defaults to `:monday`. +* `active_support.set_configs` Sets up Active Support by using the settings in `config.active_support` by `send`'ing the method names as setters to `ActiveSupport` and passing the values through. + * `action_dispatch.configure` Configures the `ActionDispatch::Http::URL.tld_length` to be set to the value of `config.action_dispatch.tld_length`. * `action_view.set_configs` Sets up Action View by using the settings in `config.action_view` by `send`'ing the method names as setters to `ActionView::Base` and passing the values through. -* `action_controller.logger` Sets `ActionController::Base.logger` - if it's not already set - to `Rails.logger`. +* `action_controller.assets_config` Initializes the `config.actions_controller.assets_dir` to the app's public directory if not explicitly configured -* `action_controller.initialize_framework_caches` Sets `ActionController::Base.cache_store` - if it's not already set - to `Rails.cache`. +* `action_controller.set_helpers_path` Sets Action Controller's helpers_path to the application's helpers_path + +* `action_controller.parameters_config` Configures strong parameters options for `ActionController::Parameters` * `action_controller.set_configs` Sets up Action Controller by using the settings in `config.action_controller` by `send`'ing the method names as setters to `ActionController::Base` and passing the values through. @@ -1030,13 +1032,21 @@ Below is a comprehensive list of all the initializers found in Rails in the orde * `active_record.logger` Sets `ActiveRecord::Base.logger` - if it's not already set - to `Rails.logger`. +* `active_record.migration_error` Configures middleware to check for pending migrations + +* `active_record.check_schema_cache_dump` Loads the schema cache dump if configured and available + +* `active_record.warn_on_records_fetched_greater_than` Enables warnings when queries return large numbers of records + * `active_record.set_configs` Sets up Active Record by using the settings in `config.active_record` by `send`'ing the method names as setters to `ActiveRecord::Base` and passing the values through. * `active_record.initialize_database` Loads the database configuration (by default) from `config/database.yml` and establishes a connection for the current environment. * `active_record.log_runtime` Includes `ActiveRecord::Railties::ControllerRuntime` which is responsible for reporting the time taken by Active Record calls for the request back to the logger. -* `active_record.set_dispatch_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. +* `active_record.set_reloader_hooks` Resets all reloadable connections to the database if `config.cache_classes` is set to `false`. + +* `active_record.add_watchable_files` Adds `schema.rb` and `structure.sql` files to watchable files * `active_job.logger` Sets `ActiveJob::Base.logger` - if it's not already set - to `Rails.logger`. diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index cbc304c87f..0f98d12217 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -105,7 +105,7 @@ $ git checkout -b testing_branch Then you can use their remote branch to update your codebase. For example, let's say the GitHub user JohnSmith has forked and pushed to a topic branch "orange" located at https://github.com/JohnSmith/rails. ```bash -$ git remote add JohnSmith git://github.com/JohnSmith/rails.git +$ git remote add JohnSmith https://github.com/JohnSmith/rails.git $ git pull JohnSmith orange ``` @@ -159,7 +159,7 @@ If you want to translate the Rails guides in your own language, follows these st * Copy the contents of *guides/source* into your own language directory and translate them. * Do NOT translate the HTML files, as they are automatically generated. -To generate the guides in HTML format cd into the *guides* direcotry then run (eg. for it-IT): +To generate the guides in HTML format cd into the *guides* directory then run (eg. for it-IT): ```bash $ bundle install @@ -204,7 +204,7 @@ In case you can't use the Rails development box, see [this other guide](developm To be able to contribute code, you need to clone the Rails repository: ```bash -$ git clone git://github.com/rails/rails.git +$ git clone https://github.com/rails/rails.git ``` and create a dedicated branch: @@ -506,7 +506,7 @@ Navigate to the Rails [GitHub repository](https://github.com/rails/rails) and pr Add the new remote to your local repository on your local machine: ```bash -$ git remote add mine git@github.com:<your user name>/rails.git +$ git remote add mine https://github.com:<your user name>/rails.git ``` Push to your remote: @@ -520,7 +520,7 @@ You might have cloned your forked repository into your machine and might want to In the directory you cloned your fork: ```bash -$ git remote add rails git://github.com/rails/rails.git +$ git remote add rails https://github.com/rails/rails.git ``` Download new commits and branches from the official repository: diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index fdd6d4d33d..2cf613f47f 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -137,7 +137,6 @@ description: This guide explains how to profile your Rails applications to improve performance. - name: Using Rails for API-only Applications - work_in_progress: true url: api_app.html description: This guide explains how to effectively use Rails to develop a JSON API application. diff --git a/guides/source/engines.md b/guides/source/engines.md index 697938434c..415def8367 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -1034,6 +1034,8 @@ typical `GET` to a controller in a controller's functional test like this: ```ruby module Blorgh class FooControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + def test_index get foos_url ... @@ -1050,6 +1052,8 @@ in your setup code: ```ruby module Blorgh class FooControllerTest < ActionDispatch::IntegrationTest + include Engine.routes.url_helpers + setup do @routes = Engine.routes end diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 3392dad897..8eb3b6190f 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -653,7 +653,7 @@ run this command in your terminal: $ bin/rails generate model Article title:string text:text ``` -With that command we told Rails that we want a `Article` model, together +With that command we told Rails that we want an `Article` model, together with a _title_ attribute of type string, and a _text_ attribute of type text. Those attributes are automatically added to the `articles` table in the database and mapped to the `Article` model. @@ -767,7 +767,7 @@ Why do you have to bother? The ability to grab and automatically assign all controller parameters to your model in one shot makes the programmer's job easier, but this convenience also allows malicious use. What if a request to the server was crafted to look like a new article form submit but also included -extra fields with values that violated your applications integrity? They would +extra fields with values that violated your application's integrity? They would be 'mass assigned' into your model and then into the database along with the good stuff - potentially breaking your application or worse. @@ -1540,7 +1540,7 @@ This is very similar to the `Article` model that you saw earlier. The difference is the line `belongs_to :article`, which sets up an Active Record _association_. You'll learn a little about associations in the next section of this guide. -The (`:references`) keyword used in the bash command is a special data type for models. +The (`:references`) keyword used in the bash command is a special data type for models. It creates a new column on your database table with the provided model name appended with an `_id` that can hold integer values. You can get a better understanding after analyzing the `db/schema.rb` file below. diff --git a/guides/source/initialization.md b/guides/source/initialization.md index 6232ef4c57..156f9c92b4 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -157,7 +157,7 @@ snippet. If we had used `s` rather than `server`, Rails would have used the `aliases` defined here to find the matching command. -### `rails/commands/command_tasks.rb` +### `rails/commands/commands_tasks.rb` When one types a valid Rails command, `run_command!` a method of the same name is called. If Rails doesn't recognize the command, it tries to run a Rake task diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index d55e1007ee..6946eb81eb 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -700,7 +700,7 @@ This would detect that there are no books with the specified ID, populate the `@ ### Using `head` To Build Header-Only Responses -The `head` method can be used to send responses with only headers to the browser. It provides a more obvious alternative to calling `render :nothing`. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: +The `head` method can be used to send responses with only headers to the browser. The `head` method accepts a number or symbol (see [reference table](#the-status-option)) representing a HTTP status code. The options argument is interpreted as a hash of header names and values. For example, you can return only an error header: ```ruby head :bad_request diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 934693252e..3b61d65df5 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -104,7 +104,7 @@ For a freshly generated Rails application, this might produce something like: ```ruby use Rack::Sendfile use ActionDispatch::Static -use Rack::Lock +use ActionDispatch::LoadInterlock use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838> use Rack::Runtime use Rack::MethodOverride @@ -171,10 +171,10 @@ Add the following lines to your application configuration: ```ruby # config/application.rb -config.middleware.delete Rack::Lock +config.middleware.delete Rack::Runtime ``` -And now if you inspect the middleware stack, you'll find that `Rack::Lock` is +And now if you inspect the middleware stack, you'll find that `Rack::Runtime` is not a part of it. ```bash @@ -219,6 +219,10 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Sets `env["rack.multithread"]` flag to `false` and wraps the application within a Mutex. +**`ActionDispatch::LoadInterlock`** + +* Used for thread safe code reloading during development. + **`ActiveSupport::Cache::Strategy::LocalCache::Middleware`** * Used for memory caching. This cache is not thread safe. diff --git a/guides/source/routing.md b/guides/source/routing.md index 5a745b10cd..777d1d24b6 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -1136,10 +1136,21 @@ For example, here's a small section of the `rails routes` output for a RESTful r edit_user GET /users/:id/edit(.:format) users#edit ``` -You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable: +You can search through your routes with the --grep option (-g for short). This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path. -```bash -$ CONTROLLER=users bin/rails routes +``` +$ bin/rails routes --grep new_comment +$ bin/rails routes -g POST +$ bin/rails routes -g admin +``` + +If you only want to see the routes that map to a specific controller, there's the --controller option (-c for short). + +``` +$ bin/rails routes --controller users +$ bin/rails routes --controller admin/users +$ bin/rails routes -c Comments +$ bin/rails routes -c Articles::CommentsController ``` TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap. diff --git a/guides/source/security.md b/guides/source/security.md index 1d0e87d831..98324141cc 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -23,7 +23,7 @@ Web application frameworks are made to help developers build web applications. S In general there is no such thing as plug-n-play security. Security depends on the people using the framework, and sometimes on the development method. And it depends on all layers of a web application environment: The back-end storage, the web server and the web application itself (and possibly other layers or applications). -The Gartner Group however estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person. +The Gartner Group, however, estimates that 75% of attacks are at the web application layer, and found out "that out of 300 audited sites, 97% are vulnerable to attack". This is because web applications are relatively easy to attack, as they are simple to understand and manipulate, even by the lay person. The threats against web applications include user account hijacking, bypass of access control, reading or modifying sensitive data, or presenting fraudulent content. Or an attacker might be able to install a Trojan horse program or unsolicited e-mail sending software, aim at financial enrichment or cause brand name damage by modifying company resources. In order to prevent attacks, minimize their impact and remove points of attack, first of all, you have to fully understand the attack methods in order to find the correct countermeasures. That is what this guide aims at. @@ -62,7 +62,7 @@ Many web applications have an authentication system: a user provides a user name Hence, the cookie serves as temporary authentication for the web application. Anyone who seizes a cookie from someone else, may use the web application as this user - with possibly severe consequences. Here are some ways to hijack a session, and their countermeasures: -* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: +* Sniff the cookie in an insecure network. A wireless LAN can be an example of such a network. In an unencrypted wireless LAN, it is especially easy to listen to the traffic of all connected clients. For the web application builder this means to _provide a secure connection over SSL_. In Rails 3.1 and later, this could be accomplished by always forcing SSL connection in your application config file: ```ruby config.force_ssl = true @@ -102,7 +102,7 @@ Thus the session becomes a more secure place to store data. The encryption is done using a server-side secret key `secrets.secret_key_base` stored in `config/secrets.yml`. -That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rake secret` instead_. +That means the security of this storage depends on this secret (and on the digest algorithm, which defaults to SHA1, for compatibility). So _don't use a trivial secret, i.e. a word from a dictionary, or one which is shorter than 30 characters, use `rails secret` instead_. `secrets.secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get `secrets.secret_key_base` initialized to a random key present in `config/secrets.yml`, e.g.: @@ -787,7 +787,7 @@ The following is an excerpt from the [Js.Yamanner@m](http://www.symantec.com/sec var IDList = ''; var CRumb = ''; function makeRequest(url, Func, Method,Param) { ... ``` -The worms exploits a hole in Yahoo's HTML/JavaScript filter, which usually filters all target and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. +The worms exploit a hole in Yahoo's HTML/JavaScript filter, which usually filters all targets and onload attributes from tags (because there can be JavaScript). The filter is applied only once, however, so the onload attribute with the worm code stays in place. This is a good example why blacklist filters are never complete and why it is hard to allow HTML/JavaScript in a web application. Another proof-of-concept webmail worm is Nduja, a cross-domain worm for four Italian webmail services. Find more details on [Rosario Valotta's paper](http://www.xssed.com/news/37/Nduja_Connection_A_cross_webmail_worm_XWW/). Both webmail worms have the goal to harvest email addresses, something a criminal hacker could make money with. diff --git a/guides/source/testing.md b/guides/source/testing.md index b5e49a41f4..1c64b2c0ac 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -265,7 +265,7 @@ By now you've caught a glimpse of some of the assertions that are available. Ass Here's an extract of the assertions you can use with [`Minitest`](https://github.com/seattlerb/minitest), the default testing library used by Rails. The `[msg]` parameter is an optional string message you can -specify to make your test failure messages clearer. +specify to make your test failure messages clearer. | Assertion | Purpose | | ---------------------------------------------------------------- | ------- | @@ -316,12 +316,12 @@ Rails adds some custom assertions of its own to the `minitest` framework: | Assertion | Purpose | | --------------------------------------------------------------------------------- | ------- | -| `assert_difference(expressions, difference = 1, message = nil) {...}` | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| -| `assert_no_difference(expressions, message = nil, &block)` | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| -| `assert_recognizes(expected_options, path, extras={}, message=nil)` | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| -| `assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)` | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| -| `assert_response(type, message = nil)` | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| -| `assert_redirected_to(options = {}, message=nil)` | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| +| [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| +| [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| +| [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| +| [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| +| [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| +| [`assert_redirected_to(options = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_redirected_to) | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| You'll see the usage of some of these assertions in the next chapter. @@ -329,11 +329,11 @@ You'll see the usage of some of these assertions in the next chapter. All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from: -* `ActiveSupport::TestCase` -* `ActionMailer::TestCase` -* `ActionView::TestCase` -* `ActionDispatch::IntegrationTest` -* `ActiveJob::TestCase` +* [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html) +* [`ActionMailer::TestCase`](http://api.rubyonrails.org/classes/ActionMailer/TestCase.html) +* [`ActionView::TestCase`](http://api.rubyonrails.org/classes/ActionView/TestCase.html) +* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html) +* [`ActiveJob::TestCase`](http://api.rubyonrails.org/classes/ActiveJob/TestCase.html) Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. @@ -415,6 +415,8 @@ You can find comprehensive documentation in the [Fixtures API documentation](htt _Fixtures_ is a fancy word for sample data. Fixtures allow you to populate your testing database with predefined data before your tests run. Fixtures are database independent and written in YAML. There is one file per model. +NOTE: Fixtures are not designed to create every object that your tests need, and are best managed when only used for default data that can be applied to the common case. + You'll find fixtures under your `test/fixtures` directory. When you run `rails generate model` to create a new model, Rails automatically creates fixture stubs in this directory. #### YAML @@ -518,7 +520,7 @@ create test/models/article_test.rb create test/fixtures/articles.yml ``` -Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from `ActiveSupport::TestCase`. +Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html). Integration Testing @@ -589,7 +591,7 @@ class BlogFlowTest < ActionDispatch::IntegrationTest end ``` -We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content. +We will take a look at `assert_select` to query the resulting HTML of a request in the "Testing Views" section below. It is used for testing the response of our request by asserting the presence of key HTML elements and their content. When we visit our root path, we should see `welcome/index.html.erb` rendered for the view. So this assertion should pass. @@ -638,9 +640,9 @@ We were able to successfully test a very small workflow for visiting our blog an Functional Tests for Your Controllers ------------------------------------- -In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you're testing how your actions handle the requests and the expected result, or response in some cases an HTML view. +In Rails, testing the various actions of a controller is a form of writing functional tests. Remember your controllers handle the incoming web requests to your application and eventually respond with a rendered view. When writing functional tests, you are testing how your actions handle the requests and the expected result or response, in some cases an HTML view. -### What to Include in your Functional Tests +### What to include in your Functional Tests You should test for things such as: @@ -650,8 +652,7 @@ You should test for things such as: * was the correct object stored in the response template? * was the appropriate message displayed to the user in the view? -The easiest way to see functional tests in action is to generate a controller -scaffold: +The easiest way to see functional tests in action is to generate a controller using the scaffold generator: ```bash $ bin/rails generate scaffold_controller article title:string body:text @@ -664,7 +665,7 @@ create test/controllers/articles_controller_test.rb ``` This will generate the controller code and tests for an `Article` resource. -You can take look at the file `articles_controller_test.rb` in the `test/controllers` directory. +You can take a look at the file `articles_controller_test.rb` in the `test/controllers` directory. If you already have a controller and just want to generate the test scaffold code for each of the seven default actions, you can use the following command: @@ -677,7 +678,7 @@ create test/controllers/articles_controller_test.rb ... ``` -Let me take you through one such test, `test_should_get_index` from the file `articles_controller_test.rb`. +Let's take a look at one such test, `test_should_get_index` from the file `articles_controller_test.rb`. ```ruby # articles_controller_test.rb @@ -693,7 +694,7 @@ end In the `test_should_get_index` test, Rails simulates a request on the action called `index`, making sure the request was successful and also ensuring that the right response body has been generated. -The `get` method kicks off the web request and populates the results into the response. It accepts 4 arguments: +The `get` method kicks off the web request and populates the results into the `@response`. It accepts 4 arguments: * The action of the controller you are requesting. This can be in the form of a string or a route (i.e. `articles_url`). @@ -705,7 +706,7 @@ The `get` method kicks off the web request and populates the results into the re * `flash`: option with a hash of flash values. -All the keyword arguments are optional. +All of these keyword arguments are optional. Example: Calling the `:show` action, passing an `id` of 12 as the `params` and setting a `user_id` of 5 in the session: @@ -753,7 +754,7 @@ NOTE: Functional tests do not verify whether the specified request type is accep ### Testing XHR (AJAX) requests To test AJAX requests, you can specify the `xhr: true` option to `get`, `post`, -`patch`, `put`, and `delete` methods: +`patch`, `put`, and `delete` methods. For example: ```ruby test "ajax request" do @@ -808,7 +809,7 @@ post article_url # simulate the request with custom env variable ### Testing `flash` notices -If you remember from earlier one of the Three Hashes of the Apocalypse was `flash`. +If you remember from earlier, one of the Three Hashes of the Apocalypse was `flash`. We want to add a `flash` message to our blog application whenever someone successfully creates a new Article. @@ -893,7 +894,7 @@ test "should show article" do end ``` -Remember from our discussion earlier on fixtures the `articles()` method will give us access to our Articles fixtures. +Remember from our discussion earlier on fixtures, the `articles()` method will give us access to our Articles fixtures. How about deleting an existing Article? @@ -913,14 +914,19 @@ We can also add a test for updating an existing Article. ```ruby test "should update article" do article = articles(:one) + patch '/article', params: { id: article.id, article: { title: "updated" } } + assert_redirected_to article_path(article) + # Reload association to fetch updated data and assert that title is updated. + article.reload + assert_equal "updated", article.title end ``` Notice we're starting to see some duplication in these three tests, they both access the same Article fixture data. We can D.R.Y. this up by using the `setup` and `teardown` methods provided by `ActiveSupport::Callbacks`. -Our test should now look something like this, disregard the other tests we're leaving them out for brevity. +Our test should now look something as what follows. Disregard the other tests for now, we're leaving them out for brevity. ```ruby require 'test_helper' @@ -952,8 +958,12 @@ class ArticlesControllerTest < ActionDispatch::IntegrationTest end test "should update article" do - patch article_url(@article), params: { article: { title: "updated" } } + patch '/article', params: { id: @article.id, article: { title: "updated" } } + assert_redirected_to article_path(@article) + # Reload association to fetch updated data and assert that title is updated. + @article.reload + assert_equal "updated", @article.title end end ``` @@ -966,7 +976,7 @@ To avoid code duplication, you can add your own test helpers. Sign in helper can be a good example: ```ruby -test/test_helper.rb +#test/test_helper.rb module SignInHelper def sign_in(user) @@ -999,6 +1009,8 @@ Testing Routes Like everything else in your Rails application, you can test your routes. +NOTE: If your application has complex routes, Rails provides a number of useful helpers to test them. + For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html). Testing Views @@ -1045,7 +1057,7 @@ assert_select "ol" do end ``` -This assertion is quite powerful. For more advanced usage, refer to its [documentation](http://www.rubydoc.info/github/rails/rails-dom-testing). +This assertion is quite powerful. For more advanced usage, refer to its [documentation](https://github.com/rails/rails-dom-testing/blob/master/lib/rails/dom/testing/assertions/selector_assertions.rb). #### Additional View-Based Assertions @@ -1068,27 +1080,31 @@ end Testing Helpers --------------- +A helper is just a simple module where you can define methods which are +available into your views. + In order to test helpers, all you need to do is check that the output of the helper method matches what you'd expect. Tests related to the helpers are located under the `test/helpers` directory. -A helper test looks like so: +Given we have the following helper: ```ruby -require 'test_helper' - -class UserHelperTest < ActionView::TestCase +module UserHelper + def link_to_user(user) + link_to "#{user.first_name} #{user.last_name}", user + end end ``` -A helper is just a simple module where you can define methods which are -available into your views. To test the output of the helper's methods, you just -have to use a mixin like this: +We can test the output of this method like this: ```ruby class UserHelperTest < ActionView::TestCase - test "should return the user name" do - # ... + test "should return the user's full name" do + user = users(:david) + + assert_dom_equal %{<a href="/user/#{user.id}">David Heinemeier Hansson</a>}, link_to_user(user) end end ``` @@ -1123,7 +1139,7 @@ In order to test that your mailer is working as expected, you can use unit tests For the purposes of unit testing a mailer, fixtures are used to provide an example of how the output _should_ look. Because these are example emails, and not Active Record data like the other fixtures, they are kept in their own subdirectory apart from the other fixtures. The name of the directory within `test/fixtures` directly corresponds to the name of the mailer. So, for a mailer named `UserMailer`, the fixtures should reside in `test/fixtures/user_mailer` directory. -When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator you'll have to make those files yourself. +When you generated your mailer, the generator creates stub fixtures for each of the mailers actions. If you didn't use the generator, you'll have to create those files yourself. #### The Basic Test Case @@ -1204,7 +1220,7 @@ Testing Jobs ------------ Since your custom jobs can be queued at different levels inside your application, -you'll need to test both jobs themselves (their behavior when they get enqueued) +you'll need to test both, the jobs themselves (their behavior when they get enqueued) and that other entities correctly enqueue them. ### A Basic Test Case @@ -1252,10 +1268,12 @@ class ProductTest < ActiveJob::TestCase end ``` -Testing Time-Dependent Code ---------------------------- +Additional Testing Resources +---------------------------- + +### Testing Time-Dependent Code -Rails provides inbuilt helper methods that enable you to assert that your time-sensitve code works as expected. +Rails provides built-in helper methods that enable you to assert that your time-sensitive code works as expected. Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 202e5b5cb9..e631445492 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -402,7 +402,7 @@ secrets, you need to: 3. Remove the `secret_token.rb` initializer. -4. Use `rake secret` to generate new keys for the `development` and `test` sections. +4. Use `rails secret` to generate new keys for the `development` and `test` sections. 5. Restart your server. diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index 48fc6bc9c0..26ff5da7a3 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -148,10 +148,10 @@ and Rails has got your back in those cases. Because of Unobtrusive JavaScript, the Rails "Ajax helpers" are actually in two parts: the JavaScript half and the Ruby half. +Unless you have disabled the Asset Pipeline, [rails.js](https://github.com/rails/jquery-ujs/blob/master/src/rails.js) provides the JavaScript half, and the regular Ruby view helpers add appropriate -tags to your DOM. The CoffeeScript in rails.js then listens for these -attributes, and attaches appropriate handlers. +tags to your DOM. ### form_for @@ -350,7 +350,7 @@ $("<%= escape_javascript(render @user) %>").appendTo("#users"); Turbolinks ---------- -Rails 4 ships with the [Turbolinks gem](https://github.com/rails/turbolinks). +Rails 4 ships with the [Turbolinks gem](https://github.com/turbolinks/turbolinks). This gem uses Ajax to speed up page rendering in most applications. ### How Turbolinks Works @@ -395,7 +395,7 @@ $(document).on "page:change", -> For more details, including other events you can bind to, check out [the Turbolinks -README](https://github.com/rails/turbolinks/blob/master/README.md). +README](https://github.com/turbolinks/turbolinks/blob/master/README.md). Other Resources --------------- diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index f94bb49135..2506baac16 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,19 @@ +* The Gemfiles of new applications include spring-watcher-listen on Linux and + Mac OS X (unless --skip-spring). + + *Xavier Noria* + +* New applications are generated with the evented file system monitor enabled + on Linux and Mac OS X. + + *Xavier Noria* + +* Add dummy files for apple-touch-icon.png and apple-touch-icon.png. GH#23427 + + *Alexey Zabelin* + +## Rails 5.0.0.beta2 (February 01, 2016) ## + * Add `after_bundle` callbacks in Rails plugin templates. Useful for allowing templates to perform actions that are dependent upon `bundle install`. @@ -21,6 +37,7 @@ *Will Fisher* + ## Rails 5.0.0.beta1 (December 18, 2015) ## * Newly generated plugins get a `README.md` in Markdown. diff --git a/railties/RDOC_MAIN.rdoc b/railties/RDOC_MAIN.rdoc index ce024563c4..26a25ee9dc 100644 --- a/railties/RDOC_MAIN.rdoc +++ b/railties/RDOC_MAIN.rdoc @@ -51,7 +51,7 @@ can read more about Action Pack in its {README}[link:files/actionpack/README_rdo 4. Go to http://localhost:3000 and you'll see: - "Welcome aboard: You're riding Ruby on Rails!" + "Yay! You’re on Rails!" 5. Follow the guidelines to start developing your application. You may find the following resources handy: diff --git a/railties/Rakefile b/railties/Rakefile index cf130a5f14..3421d9b464 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -2,6 +2,9 @@ require 'rake/testtask' task :default => :test +task :package +task "package:clean" + desc "Run all unit tests" task :test => 'test:isolated' diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index 404e3c3e23..411cdbad19 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -86,7 +86,7 @@ module Rails # added in the hook are taken into account. initializer :set_clear_dependencies_hook, group: :all do callback = lambda do - ActiveSupport::Dependencies.interlock.attempt_unloading do + ActiveSupport::Dependencies.interlock.unloading do ActiveSupport::DescendantsTracker.clear ActiveSupport::Dependencies.clear end diff --git a/railties/lib/rails/code_statistics.rb b/railties/lib/rails/code_statistics.rb index 0997414482..fc8717c752 100644 --- a/railties/lib/rails/code_statistics.rb +++ b/railties/lib/rails/code_statistics.rb @@ -9,6 +9,8 @@ class CodeStatistics #:nodoc: 'Job tests', 'Integration tests'] + HEADERS = {lines: ' Lines', code_lines: ' LOC', classes: 'Classes', methods: 'Methods'} + def initialize(*pairs) @pairs = pairs @statistics = calculate_statistics @@ -67,27 +69,37 @@ class CodeStatistics #:nodoc: test_loc end + def width_for(label) + [@statistics.values.sum {|s| s.send(label) }.to_s.size, HEADERS[label].length].max + end + def print_header print_splitter - puts "| Name | Lines | LOC | Classes | Methods | M/C | LOC/M |" + print '| Name ' + HEADERS.each do |k, v| + print " | #{v.rjust(width_for(k))}" + end + puts ' | M/C | LOC/M |' print_splitter end def print_splitter - puts "+----------------------+--------+--------+---------+---------+-----+-------+" + print '+----------------------' + HEADERS.each_key do |k| + print "+#{'-' * (width_for(k) + 2)}" + end + puts '+-----+-------+' end def print_line(name, statistics) m_over_c = (statistics.methods / statistics.classes) rescue m_over_c = 0 loc_over_m = (statistics.code_lines / statistics.methods) - 2 rescue loc_over_m = 0 - puts "| #{name.ljust(20)} " \ - "| #{statistics.lines.to_s.rjust(6)} " \ - "| #{statistics.code_lines.to_s.rjust(6)} " \ - "| #{statistics.classes.to_s.rjust(7)} " \ - "| #{statistics.methods.to_s.rjust(7)} " \ - "| #{m_over_c.to_s.rjust(3)} " \ - "| #{loc_over_m.to_s.rjust(5)} |" + print "| #{name.ljust(20)} " + HEADERS.each_key do |k| + print "| #{statistics.send(k).to_s.rjust(width_for(k))} " + end + puts "| #{m_over_c.to_s.rjust(3)} | #{loc_over_m.to_s.rjust(5)} |" end def print_code_test_stats diff --git a/railties/lib/rails/commands.rb b/railties/lib/rails/commands.rb index 7627fcf5a0..fa47c52b96 100644 --- a/railties/lib/rails/commands.rb +++ b/railties/lib/rails/commands.rb @@ -14,6 +14,5 @@ command = ARGV.shift command = aliases[command] || command require 'rails/command' -require 'rails/commands/dev_cache' Rails::Command.run(command, ARGV) diff --git a/railties/lib/rails/commands/dev_cache.rb b/railties/lib/rails/commands/dev_cache.rb deleted file mode 100644 index ec96e8f630..0000000000 --- a/railties/lib/rails/commands/dev_cache.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'rails/command' - -module Rails - module Commands - # This is a wrapper around the Rails dev:cache command - class DevCache < Command - set_banner :dev_cache, 'Toggle development mode caching on/off' - def dev_cache - if File.exist? 'tmp/caching-dev.txt' - File.delete 'tmp/caching-dev.txt' - puts 'Development mode is no longer being cached.' - else - FileUtils.touch 'tmp/caching-dev.txt' - puts 'Development mode is now being cached.' - end - - FileUtils.touch 'tmp/restart.txt' - end - end - end -end diff --git a/railties/lib/rails/commands/runner.rb b/railties/lib/rails/commands/runner.rb index babb197ba1..5844e9037c 100644 --- a/railties/lib/rails/commands/runner.rb +++ b/railties/lib/rails/commands/runner.rb @@ -59,8 +59,8 @@ elsif File.exist?(code_or_file) Kernel.load code_or_file else begin - eval(code_or_file, binding, __FILE__, __LINE__) - rescue SyntaxError,NameError => err + eval(code_or_file, binding, __FILE__, __LINE__) + rescue SyntaxError, NameError $stderr.puts "Please specify a valid ruby command or the path of a script to run." $stderr.puts "Run '#{$0} -h' for help." exit 1 diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb index a6d87b78e4..7bbd9ef744 100644 --- a/railties/lib/rails/engine/commands.rb +++ b/railties/lib/rails/engine/commands.rb @@ -1,3 +1,5 @@ +require 'rails/engine/commands_tasks' + ARGV << '--help' if ARGV.empty? aliases = { @@ -9,35 +11,4 @@ aliases = { command = ARGV.shift command = aliases[command] || command -require ENGINE_PATH -engine = ::Rails::Engine.find(ENGINE_ROOT) - -case command -when 'generate', 'destroy', 'test' - require 'rails/generators' - Rails::Generators.namespace = engine.railtie_namespace - engine.load_generators - require "rails/commands/#{command}" - -when '--version', '-v' - ARGV.unshift '--version' - require 'rails/commands/application' - -else - puts "Error: Command not recognized" unless %w(-h --help).include?(command) - puts <<-EOT -Usage: rails COMMAND [ARGS] - -The common Rails commands available for engines are: - generate Generate new code (short-cut alias: "g") - destroy Undo code generated with "generate" (short-cut alias: "d") - test Run tests (short-cut alias: "t") - -All commands can be run with -h for more information. - -If you want to run any commands that need to be run in context -of the application, like `rails server` or `rails console`, -you should do it from application's directory (typically test/dummy). - EOT - exit(1) -end +Rails::Engine::CommandsTasks.new(ARGV).run_command!(command) diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb new file mode 100644 index 0000000000..fa3ee59b7d --- /dev/null +++ b/railties/lib/rails/engine/commands_tasks.rb @@ -0,0 +1,116 @@ +require 'rails/commands/rake_proxy' + +module Rails + class Engine + class CommandsTasks # :nodoc: + include Rails::RakeProxy + + attr_reader :argv + + HELP_MESSAGE = <<-EOT +Usage: rails COMMAND [ARGS] + +The common Rails commands available for engines are: + generate Generate new code (short-cut alias: "g") + destroy Undo code generated with "generate" (short-cut alias: "d") + test Run tests (short-cut alias: "t") + +All commands can be run with -h for more information. + +If you want to run any commands that need to be run in context +of the application, like `rails server` or `rails console`, +you should do it from application's directory (typically test/dummy). + +In addition to those commands, there are: + EOT + + COMMAND_WHITELIST = %w(generate destroy version help test) + + def initialize(argv) + @argv = argv + end + + def run_command!(command) + command = parse_command(command) + + if COMMAND_WHITELIST.include?(command) + send(command) + else + run_rake_task(command) + end + end + + def generate + generate_or_destroy(:generate) + end + + def destroy + generate_or_destroy(:destroy) + end + + def test + require_command!("test") + end + + def version + argv.unshift '--version' + require_command!("application") + end + + def help + write_help_message + write_commands(formatted_rake_tasks) + end + + private + + def require_command!(command) + require "rails/commands/#{command}" + end + + def generate_or_destroy(command) + load_generators + require_command!(command) + end + + def load_generators + require 'rails/generators' + require ENGINE_PATH + + engine = ::Rails::Engine.find(ENGINE_ROOT) + Rails::Generators.namespace = engine.railtie_namespace + engine.load_generators + end + + def write_help_message + puts HELP_MESSAGE + end + + def write_commands(commands) + width = commands.map { |name, _| name.size }.max || 10 + commands.each { |command| printf(" %-#{width}s %s\n", *command) } + end + + def parse_command(command) + case command + when '--version', '-v' + 'version' + when '--help', '-h' + 'help' + else + command + end + end + + def rake_tasks + return @rake_tasks if defined?(@rake_tasks) + + load_generators + Rake::TaskManager.record_task_metadata = true + Rake.application.init('rails') + Rake.application.load_rakefile + @rake_tasks = Rake.application.tasks.select(&:comment) + end + end + end +end diff --git a/railties/lib/rails/gem_version.rb b/railties/lib/rails/gem_version.rb index f4d5b660dc..93e0151602 100644 --- a/railties/lib/rails/gem_version.rb +++ b/railties/lib/rails/gem_version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c629459d95..8f8c2ec9e1 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -51,6 +51,9 @@ module Rails class_option :skip_active_record, type: :boolean, aliases: '-O', default: false, desc: 'Skip Active Record files' + class_option :skip_puma, type: :boolean, aliases: '-P', default: false, + desc: 'Skip Puma related files' + class_option :skip_action_cable, type: :boolean, aliases: '-C', default: false, desc: 'Skip Action Cable files' @@ -113,6 +116,7 @@ module Rails def gemfile_entries [rails_gemfile_entry, database_gemfile_entry, + webserver_gemfile_entry, assets_gemfile_entry, javascript_gemfile_entry, jbuilder_gemfile_entry, @@ -171,6 +175,12 @@ module Rails "Use #{options[:database]} as the database for Active Record" end + def webserver_gemfile_entry + return [] if options[:skip_puma] + comment = 'Use Puma as the app server' + GemfileEntry.new('puma', nil, comment) + end + def include_all_railties? options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none? end @@ -307,7 +317,7 @@ module Rails end def javascript_gemfile_entry - if options[:skip_javascript] + if options[:skip_javascript] || options[:skip_sprockets] [] else gems = [coffee_gemfile_entry, javascript_runtime_gemfile_entry] @@ -316,7 +326,7 @@ module Rails unless options[:skip_turbolinks] gems << GemfileEntry.version("turbolinks", nil, - "Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks") + "Turbolinks makes following links in your web application faster. Read more: https://github.com/turbolinks/turbolinks") end gems @@ -342,10 +352,9 @@ module Rails def cable_gemfile_entry return [] if options[:skip_action_cable] - comment = 'Action Cable dependencies for the Redis adapter' + comment = 'Use Redis adapter to run Action Cable in production' gems = [] - gems << GemfileEntry.new("em-hiredis", '~> 0.3.0', comment) - gems << GemfileEntry.new("redis", '~> 3.0', comment) + gems << GemfileEntry.new("redis", '~> 3.0', comment, {}, true) gems end @@ -381,6 +390,10 @@ module Rails !options[:skip_spring] && !options.dev? && Process.respond_to?(:fork) && !RUBY_PLATFORM.include?("cygwin") end + def os_supports_listen_out_of_the_box? + RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + end + def run_bundle bundle_command('install') if bundle_install? end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 775750d86b..248ad20019 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -79,6 +79,7 @@ module Rails template "environment.rb" template "secrets.yml" template "cable.yml" unless options[:skip_action_cable] + template "puma.rb" unless options[:skip_puma] directory "environments" directory "initializers" diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index da5af4eefc..c3fad31f23 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -12,9 +12,6 @@ source 'https://rubygems.org' <% end -%> <% end -%> -# Use Puma as the app server -gem 'puma' - # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' @@ -41,9 +38,15 @@ group :development do gem 'web-console', '~> 3.0' <%- end -%> <%- end -%> +<% if os_supports_listen_out_of_the_box? -%> + gem 'listen', '~> 3.0.5' +<% end -%> <% if spring_install? -%> # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' +<% if os_supports_listen_out_of_the_box? -%> + gem 'spring-watcher-listen', '~> 2.0.0' +<% end -%> <% end -%> end <% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml index 004adb7b3c..aa4e832748 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml @@ -4,9 +4,7 @@ production: url: redis://localhost:6379/1 development: - adapter: redis - url: redis://localhost:6379/2 + adapter: async test: - adapter: redis - url: redis://localhost:6379/3 + adapter: async diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index 5ca549a8c8..f2c4922e7d 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -1,4 +1,4 @@ -# MySQL. Versions 4.1 and 5.0 are recommended. +# MySQL. Versions 5.0 and up are supported. # # Install the MySQL driver: # gem install activerecord-jdbcmysql-adapter diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml index 9e99264d33..80ceb9df92 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Configure Using Gemfile # gem 'activerecord-jdbcpostgresql-adapter' diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml index 119c2fe2c3..193423e84a 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -1,4 +1,4 @@ -# MySQL. Versions 5.0+ are recommended. +# MySQL. Versions 5.0 and up are supported. # # Install the MySQL driver # gem install mysql2 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml index feb25bbc6b..bd5c0b10f6 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml @@ -1,4 +1,4 @@ -# PostgreSQL. Versions 8.2 and up are supported. +# PostgreSQL. Versions 9.1 and up are supported. # # Install the pg driver: # gem install pg @@ -19,7 +19,7 @@ default: &default encoding: unicode # For details on connection pooling, see rails configuration guide # http://guides.rubyonrails.org/configuring.html#database-pooling - pool: 5 + pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt index fd41372d9c..3451ade158 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt @@ -58,5 +58,5 @@ Rails.application.configure do # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - # config.file_watcher = ActiveSupport::EventedFileUpdateChecker + <%= '# ' unless os_supports_listen_out_of_the_box? %>config.file_watcher = ActiveSupport::EventedFileUpdateChecker end diff --git a/railties/lib/rails/generators/rails/app/templates/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb new file mode 100644 index 0000000000..1bf274bc66 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -0,0 +1,44 @@ +# Puma can serve each request in a thread from an internal thread pool. +# The `threads` method setting takes two numbers a minimum and maximum. +# Any libraries that use thread pools should be configured to match +# the maximum value specified for Puma. Default is set to 5 threads for minimum +# and maximum, this matches the default thread size of Active Record. +# +threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i +threads threads_count, threads_count + +# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# +port ENV.fetch("PORT") { 3000 } + +# Specifies the `environment` that Puma will run in. +# +environment ENV.fetch("RAILS_ENV") { "development" } + +# Specifies the number of `workers` to boot in clustered mode. +# Workers are forked webserver processes. If using threads and workers together +# the concurrency of the application would be max `threads` * `workers`. +# Workers do not work on JRuby or Windows (both of which do not support +# processes). +# +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } + +# Use the `preload_app!` method when specifying a `workers` number. +# This directive tells Puma to first boot the application and load code +# before forking the application. This takes advantage of Copy On Write +# process behavior so workers use less memory. If you use this option +# you need to make sure to reconnect any threads in the `on_worker_boot` +# block. +# +# preload_app! + +# The code in the `on_worker_boot` will be called if you are using +# clustered mode by specifying a number of `workers`. After each worker +# process is booted this block will be run, if you are using `preload_app!` +# option you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, Ruby +# cannot share connections between processes. +# +# on_worker_boot do +# ActiveRecord::Base.establish_connection if defined?(ActiveRecord) +# end diff --git a/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon-precomposed.png diff --git a/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/public/apple-touch-icon.png diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt index 7fe4e5034d..83807f14b4 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt @@ -1,5 +1,6 @@ <%= wrap_in_modules <<-rb.strip_heredoc class ApplicationController < ActionController::#{api? ? "API" : "Base"} + #{ api? ? '# ' : '' }protect_from_forgery :with => :exception end rb %> diff --git a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml index 2656767eb4..0681780c97 100644 --- a/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml +++ b/railties/lib/rails/generators/test_unit/model/templates/fixtures.yml @@ -6,7 +6,7 @@ <%- if attribute.password_digest? -%> password_digest: <%%= BCrypt::Password.create('secret') %> <%- elsif attribute.reference? -%> - <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default) %> + <%= yaml_key_value(attribute.column_name.sub(/_id$/, ''), attribute.default || name) %> <%- else -%> <%= yaml_key_value(attribute.column_name, attribute.default) %> <%- end -%> diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index aa7d3ca6c6..b63d3a58d2 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -72,10 +72,6 @@ module Rails instrumenter.finish 'request.action_dispatch', request: request end - def development? - Rails.env.development? - end - def logger Rails.logger end diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index d60eaf6f4f..d3e33584d7 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -3,6 +3,7 @@ require 'rake' # Load Rails Rakefile extensions %w( annotations + dev framework initializers log diff --git a/railties/lib/rails/tasks/dev.rake b/railties/lib/rails/tasks/dev.rake new file mode 100644 index 0000000000..4593100465 --- /dev/null +++ b/railties/lib/rails/tasks/dev.rake @@ -0,0 +1,14 @@ +namespace :dev do + desc 'Toggle development mode caching on/off' + task :cache do + if File.exist? 'tmp/caching-dev.txt' + File.delete 'tmp/caching-dev.txt' + puts 'Development mode is no longer being cached.' + else + FileUtils.touch 'tmp/caching-dev.txt' + puts 'Development mode is now being cached.' + end + + FileUtils.touch 'tmp/restart.txt' + end +end diff --git a/railties/lib/rails/tasks/engine.rake b/railties/lib/rails/tasks/engine.rake index c51524f8f6..e678103f63 100644 --- a/railties/lib/rails/tasks/engine.rake +++ b/railties/lib/rails/tasks/engine.rake @@ -4,8 +4,8 @@ task "load_app" do end task :environment => "app:environment" - if !defined?(ENGINE_PATH) || !ENGINE_PATH - ENGINE_PATH = find_engine_path(APP_RAKEFILE) + if !defined?(ENGINE_ROOT) || !ENGINE_ROOT + ENGINE_ROOT = find_engine_path(APP_RAKEFILE) end end diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index 904b9d9ad6..7601836809 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -45,7 +45,7 @@ namespace :rails do @app_generator ||= begin require 'rails/generators' require 'rails/generators/rails/app/app_generator' - gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, + gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true, api: !!Rails.application.config.api_only }, destination_root: Rails.root File.exist?(Rails.root.join("config", "application.rb")) ? gen.send(:app_const) : gen.send(:valid_const?) diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index 1815c2fdc7..939fa59c75 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,7 +1,35 @@ -desc 'Print out all defined routes in match order, with names. Target specific controller with CONTROLLER=x.' +require 'active_support/deprecation' +require 'active_support/core_ext/string/strip' # for strip_heredoc +require 'optparse' + +desc 'Print out all defined routes in match order, with names. Target specific controller with --controller option - or its -c shorthand.' task routes: :environment do all_routes = Rails.application.routes.routes require 'action_dispatch/routing/inspector' inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) - puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, ENV['CONTROLLER']) + if ARGV.any?{ |argv| argv.start_with? 'CONTROLLER' } + puts <<-eow.strip_heredoc + Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1. + Please use `bin/rails routes -c controller_name` instead. + eow + end + + routes_filter = nil + routes_filter = { controller: ENV['CONTROLLER'] } if ENV['CONTROLLER'] + + OptionParser.new do |opts| + opts.banner = "Usage: rails routes [options]" + opts.on("-c", "--controller [CONTROLLER]") do |controller| + routes_filter = { controller: controller } + end + + opts.on("-g", "--grep [PATTERN]") do |pattern| + routes_filter = pattern + end + + end.parse!(ARGV.reject { |x| x == "routes" }) + + puts inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, routes_filter) + + exit 0 # ensure extra arguments aren't interpreted as Rake tasks end diff --git a/railties/lib/rails/test_unit/line_filtering.rb b/railties/lib/rails/test_unit/line_filtering.rb index dab4d3631d..dd9732bb12 100644 --- a/railties/lib/rails/test_unit/line_filtering.rb +++ b/railties/lib/rails/test_unit/line_filtering.rb @@ -13,9 +13,12 @@ module Rails end class CompositeFilter # :nodoc: + attr_reader :named_filter + def initialize(runnable, filter, patterns) @runnable = runnable - @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact + @named_filter = derive_named_filter(filter) + @filters = [ @named_filter, *derive_line_filters(patterns) ].compact end # Minitest uses === to find matching filters. @@ -24,9 +27,14 @@ module Rails end private - def derive_regexp(filter) - # Regexp filtering copied from Minitest. - filter =~ %r%/(.*)/% ? Regexp.new($1) : filter + def derive_named_filter(filter) + if filter.respond_to?(:named_filter) + filter.named_filter + elsif filter =~ %r%/(.*)/% # Regexp filtering copied from Minitest. + Regexp.new $1 + elsif filter.is_a?(String) + filter + end end def derive_line_filters(patterns) diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index 8c3ab65c51..a07c51a60f 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -28,7 +28,7 @@ module ApplicationTests assert_not File.exist?("tmp/restart.txt") `bin/setup 2>&1` assert_equal 0, File.size("log/test.log") - assert_equal '["articles", "schema_migrations", "active_record_internal_metadatas"]', list_tables.call + assert_equal '["articles", "schema_migrations", "ar_internal_metadata"]', list_tables.call assert File.exist?("tmp/restart.txt") end end diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index c000a70382..a229609e84 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -222,14 +222,14 @@ module ApplicationTests assert_equal '["posts"]', list_tables[] `bin/rails db:schema:load` - assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas"]', list_tables[] + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata"]', list_tables[] app_file 'db/structure.sql', <<-SQL CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255)); SQL `bin/rails db:structure:load` - assert_equal '["posts", "comments", "schema_migrations", "active_record_internal_metadatas", "users"]', list_tables[] + assert_equal '["posts", "comments", "schema_migrations", "ar_internal_metadata", "users"]', list_tables[] end end diff --git a/railties/test/application/rake/dev_test.rb b/railties/test/application/rake/dev_test.rb new file mode 100644 index 0000000000..43d7a5e156 --- /dev/null +++ b/railties/test/application/rake/dev_test.rb @@ -0,0 +1,34 @@ +require 'isolation/abstract_unit' + +module ApplicationTests + module RakeTests + class RakeDevTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + def setup + build_app + end + + def teardown + teardown_app + end + + test 'dev:cache creates file and outputs message' do + Dir.chdir(app_path) do + output = `rake dev:cache` + assert File.exist?('tmp/caching-dev.txt') + assert_match(/Development mode is now being cached/, output) + end + end + + test 'dev:cache deletes file and outputs message' do + Dir.chdir(app_path) do + `rails dev:cache` # Create caching file. + output = `rails dev:cache` # Delete caching file. + assert_not File.exist?('tmp/caching-dev.txt') + assert_match(/Development mode is no longer being cached/, output) + end + end + end + end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index b979ad64d1..3d3e47de8d 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -141,8 +141,67 @@ module ApplicationTests end RUBY - ENV['CONTROLLER'] = 'cart' - output = Dir.chdir(app_path){ `bin/rails routes` } + output = Dir.chdir(app_path){ `bin/rails routes CONTROLLER=cart` } + assert_equal ["Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.", + "Please use `bin/rails routes -c controller_name` instead.", + "Prefix Verb URI Pattern Controller#Action", + " cart GET /cart(.:format) cart#show\n"].join("\n"), output + + output = Dir.chdir(app_path){ `bin/rails routes -c cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_with_namespaced_controller_environment + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + namespace :admin do + resource :post + end + end + RUBY + expected_output = [" Prefix Verb URI Pattern Controller#Action", + " admin_post POST /admin/post(.:format) admin/posts#create", + " new_admin_post GET /admin/post/new(.:format) admin/posts#new", + "edit_admin_post GET /admin/post/edit(.:format) admin/posts#edit", + " GET /admin/post(.:format) admin/posts#show", + " PATCH /admin/post(.:format) admin/posts#update", + " PUT /admin/post(.:format) admin/posts#update", + " DELETE /admin/post(.:format) admin/posts#destroy\n"].join("\n") + + output = Dir.chdir(app_path){ `bin/rails routes -c Admin::PostController` } + assert_equal expected_output, output + + output = Dir.chdir(app_path){ `bin/rails routes -c PostController` } + assert_equal expected_output, output + end + + def test_rake_routes_with_global_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + output = Dir.chdir(app_path){ `bin/rails routes -g show` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + end + + def test_rake_routes_with_controller_search_key + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get '/cart', to: 'cart#show' + get '/basketball', to: 'basketball#index' + end + RUBY + + output = Dir.chdir(app_path){ `bin/rails routes -c cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path){ `bin/rails routes -c Cart` } + assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output + + output = Dir.chdir(app_path){ `bin/rails routes -c CartController` } assert_equal "Prefix Verb URI Pattern Controller#Action\n cart GET /cart(.:format) cart#show\n", output end @@ -232,12 +291,11 @@ module ApplicationTests assert_no_match(/Errors running/, output) end - 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" - + def test_scaffold_with_references_columns_tests_pass_by_default output = Dir.chdir(app_path) do - `bin/rails generate scaffold LineItems product:references cart:belongs_to; + `bin/rails generate model Product; + bin/rails generate model Cart; + bin/rails generate scaffold LineItems product:references cart:belongs_to; RAILS_ENV=test bin/rails db:migrate test` end diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index a7eb0feb11..7ecadb60ca 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -363,7 +363,7 @@ module ApplicationTests end RUBY - run_test_command('test/models/account_test.rb:4:9 test/models/post_test:4:9').tap do |output| + run_test_command('test/models/account_test.rb:4:9 test/models/post_test.rb:4:9').tap do |output| assert_match 'AccountTest:FirstFilter', output assert_match 'AccountTest:SecondFilter', output assert_match 'PostTest:FirstFilter', output @@ -382,6 +382,54 @@ module ApplicationTests end end + def test_line_filters_trigger_only_one_runnable + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'truth' do + assert true + end + end + + class SecondPostTest < ActiveSupport::TestCase + test 'truth' do + assert false, 'ran second runnable' + end + end + RUBY + + # Pass seed guaranteeing failure. + run_test_command('test/models/post_test.rb:4 --seed 30410').tap do |output| + assert_no_match 'ran second runnable', output + assert_match '1 runs, 1 assertions', output + end + end + + def test_line_filter_with_minitest_string_filter + app_file 'test/models/post_test.rb', <<-RUBY + require 'test_helper' + + class PostTest < ActiveSupport::TestCase + test 'by line' do + puts 'by line' + assert true + end + + test 'by name' do + puts 'by name' + assert true + end + end + RUBY + + run_test_command('test/models/post_test.rb:4 -n test_by_name').tap do |output| + assert_match 'by line', output + assert_match 'by name', output + assert_match '2 runs, 2 assertions', output + end + end + def test_shows_filtered_backtrace_by_default create_backtrace_test diff --git a/railties/test/commands/dev_cache_test.rb b/railties/test/commands/dev_cache_test.rb deleted file mode 100644 index 1b7a72e7fc..0000000000 --- a/railties/test/commands/dev_cache_test.rb +++ /dev/null @@ -1,32 +0,0 @@ -require_relative '../isolation/abstract_unit' - -module CommandsTests - class DevCacheTest < ActiveSupport::TestCase - include ActiveSupport::Testing::Isolation - - def setup - build_app - end - - def teardown - teardown_app - end - - test 'dev:cache creates file and outputs message' do - Dir.chdir(app_path) do - output = `rails dev:cache` - assert File.exist?('tmp/caching-dev.txt') - assert_match(%r{Development mode is now being cached}, output) - end - end - - test 'dev:cache deletes file and outputs message' do - Dir.chdir(app_path) do - `rails dev:cache` # Create caching file. - output = `rails dev:cache` # Delete caching file. - assert_not File.exist?('tmp/caching-dev.txt') - assert_match(%r{Development mode is no longer being cached}, output) - end - end - end -end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index d9eb7770f3..1ea5661006 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -52,6 +52,16 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase assert_file "app/controllers/application_controller.rb", /ActionController::API/ end + def test_generator_if_skip_action_cable_is_given + run_generator [destination_root, "--skip-action-cable"] + assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ + assert_no_file "config/cable.yml" + assert_no_file "app/channels" + assert_file "Gemfile" do |content| + assert_no_match(/redis/, content) + end + end + private def default_files diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 53f8ed6af0..be05e779ea 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -27,6 +27,7 @@ DEFAULT_APP_FILES = %w( config/initializers config/locales config/cable.yml + config/puma.rb db lib lib/tasks @@ -337,6 +338,14 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_if_skip_puma_is_given + run_generator [destination_root, "--skip-puma"] + assert_no_file "config/puma.rb" + assert_file "Gemfile" do |content| + assert_no_match(/puma/, content) + end + end + def test_generator_if_skip_active_record_is_given run_generator [destination_root, "--skip-active-record"] assert_no_file "config/database.yml" @@ -376,9 +385,10 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_match(/#\s+require\s+["']sprockets\/railtie["']/, content) end assert_file "Gemfile" do |content| + assert_no_match(/jquery-rails/, content) assert_no_match(/sass-rails/, content) assert_no_match(/uglifier/, content) - assert_match(/coffee-rails/, content) + assert_no_match(/coffee-rails/, content) end assert_file "config/environments/development.rb" do |content| assert_no_match(/config\.assets\.debug = true/, content) @@ -400,27 +410,13 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_no_match(/action_cable_meta_tag/, content) end assert_file "Gemfile" do |content| - assert_no_match(/em-hiredis/, content) - assert_no_match(/redis/, content) - end - end - - def test_generator_if_skip_action_cable_is_given_for_an_api_app - run_generator [destination_root, "--skip-action-cable", "--api"] - assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ - assert_no_file "config/cable.yml" - assert_no_file "app/assets/javascripts/cable.coffee" - assert_no_file "app/channels" - assert_file "Gemfile" do |content| - assert_no_match(/em-hiredis/, content) assert_no_match(/redis/, content) end end def test_action_cable_redis_gems run_generator - assert_gem 'em-hiredis' - assert_gem 'redis' + assert_file "Gemfile", /^# gem 'redis'/ end def test_inclusion_of_javascript_runtime @@ -483,6 +479,29 @@ class AppGeneratorTest < Rails::Generators::TestCase end end + def test_inclusion_of_listen_related_gems + run_generator + if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + assert_gem 'listen' + assert_gem 'spring-watcher-listen' + else + assert_file 'Gemfile' do |content| + assert_no_match(/listen/, content) + end + end + end + + def test_evented_file_update_checker_config + run_generator + assert_file 'config/environments/development.rb' do |content| + if RbConfig::CONFIG['host_os'] =~ /darwin|linux/ + assert_match(/^\s*config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + else + assert_match(/^\s*# config.file_watcher = ActiveSupport::EventedFileUpdateChecker/, content) + end + end + end + def test_template_from_dir_pwd FileUtils.cd(Rails.root) assert_match(/It works from file!/, run_generator([destination_root, "-m", "lib/template.rb"])) diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 814f4c050e..c8c8f0aa3b 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -298,18 +298,18 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_fixtures_use_the_references_ids run_generator ["LineItem", "product:references", "cart:belongs_to"] - assert_file "test/fixtures/line_items.yml", /product: \n cart: / + assert_file "test/fixtures/line_items.yml", /product: one\n cart: one/ assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product"=>nil, "cart"=>nil}, "two"=>{"product"=>nil, "cart"=>nil}}) + {"one"=>{"product"=>"one", "cart"=>"one"}, "two"=>{"product"=>"two", "cart"=>"two"}}) end def test_fixtures_use_the_references_ids_and_type run_generator ["LineItem", "product:references{polymorphic}", "cart:belongs_to"] - assert_file "test/fixtures/line_items.yml", /product: \n product_type: Product\n cart: / + assert_file "test/fixtures/line_items.yml", /product: one\n product_type: Product\n cart: one/ assert_generated_fixture("test/fixtures/line_items.yml", - {"one"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}, - "two"=>{"product"=>nil, "product_type"=>"Product", "cart"=>nil}}) + {"one"=>{"product"=>"one", "product_type"=>"Product", "cart"=>"one"}, + "two"=>{"product"=>"two", "product_type"=>"Product", "cart"=>"two"}}) end def test_fixtures_respect_reserved_yml_keywords diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index ef33cd4ff7..6e5132e849 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -240,7 +240,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--mountable"] FileUtils.cd destination_root quietly { system 'bundle install' } - output = `bundle exec rake db:migrate 2>&1` + output = `bin/rails db:migrate 2>&1` assert $?.success?, "Command failed: #{output}" end @@ -335,7 +335,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "hyphenated-name/lib/hyphenated/name/engine.rb", /module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Hyphenated::Name\n end\n end\nend/ assert_file "hyphenated-name/lib/hyphenated/name.rb", /require "hyphenated\/name\/engine"/ assert_file "hyphenated-name/test/dummy/config/routes.rb", /mount Hyphenated::Name::Engine => "\/hyphenated-name"/ - assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "hyphenated-name/app/controllers/hyphenated/name/application_controller.rb", /module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery :with => :exception\n end\n end\nend\n/ assert_file "hyphenated-name/app/models/hyphenated/name/application_record.rb", /module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/ assert_file "hyphenated-name/app/jobs/hyphenated/name/application_job.rb", /module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ assert_file "hyphenated-name/app/mailers/hyphenated/name/application_mailer.rb", /module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/ @@ -357,7 +357,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "my_hyphenated-name/lib/my_hyphenated/name/engine.rb", /module MyHyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace MyHyphenated::Name\n end\n end\nend/ assert_file "my_hyphenated-name/lib/my_hyphenated/name.rb", /require "my_hyphenated\/name\/engine"/ assert_file "my_hyphenated-name/test/dummy/config/routes.rb", /mount MyHyphenated::Name::Engine => "\/my_hyphenated-name"/ - assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\nend/ + assert_file "my_hyphenated-name/app/controllers/my_hyphenated/name/application_controller.rb", /module MyHyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery :with => :exception\n end\n end\nend\n/ assert_file "my_hyphenated-name/app/models/my_hyphenated/name/application_record.rb", /module MyHyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\nend/ assert_file "my_hyphenated-name/app/jobs/my_hyphenated/name/application_job.rb", /module MyHyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ assert_file "my_hyphenated-name/app/mailers/my_hyphenated/name/application_mailer.rb", /module MyHyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\nend/ @@ -379,7 +379,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase assert_file "deep-hyphenated-name/lib/deep/hyphenated/name/engine.rb", /module Deep\n module Hyphenated\n module Name\n class Engine < ::Rails::Engine\n isolate_namespace Deep::Hyphenated::Name\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/lib/deep/hyphenated/name.rb", /require "deep\/hyphenated\/name\/engine"/ assert_file "deep-hyphenated-name/test/dummy/config/routes.rb", /mount Deep::Hyphenated::Name::Engine => "\/deep-hyphenated-name"/ - assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n end\n end\n end\nend/ + assert_file "deep-hyphenated-name/app/controllers/deep/hyphenated/name/application_controller.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationController < ActionController::Base\n protect_from_forgery :with => :exception\n end\n end\n end\nend\n/ assert_file "deep-hyphenated-name/app/models/deep/hyphenated/name/application_record.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationRecord < ActiveRecord::Base\n self\.abstract_class = true\n end\n end\n end\nend/ assert_file "deep-hyphenated-name/app/jobs/deep/hyphenated/name/application_job.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationJob < ActiveJob::Base/ assert_file "deep-hyphenated-name/app/mailers/deep/hyphenated/name/application_mailer.rb", /module Deep\n module Hyphenated\n module Name\n class ApplicationMailer < ActionMailer::Base\n default from: 'from@example.com'\n layout 'mailer'\n end\n end\n end\nend/ diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 6f7a83cae0..5e45120704 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -486,7 +486,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end @@ -500,7 +500,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/8 runs, 13 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end @@ -514,7 +514,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end @@ -528,7 +528,7 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase Dir.chdir(engine_path) do quietly do `bin/rails g scaffold User name:string age:integer; - bundle exec rake db:migrate` + bin/rails db:migrate` end assert_match(/6 runs, 8 assertions, 0 failures, 0 errors/, `bin/rails test 2>&1`) end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index dddf8bd257..e7a261fa1f 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -154,6 +154,8 @@ module TestHelpers config.action_controller.allow_forgery_protection = false config.log_level = :info RUBY + + remove_from_env_config('development', 'config.file_watcher.*') end def teardown_app @@ -272,10 +274,17 @@ module TestHelpers end def remove_from_config(str) - file = "#{app_path}/config/application.rb" + remove_from_file("#{app_path}/config/application.rb", str) + end + + def remove_from_env_config(env, str) + remove_from_file("#{app_path}/config/environments/#{env}.rb", str) + end + + def remove_from_file(file, str) contents = File.read(file) - contents.sub!(/#{str}/, "") - File.open(file, "w+") { |f| f.puts contents } + contents.sub!(/#{str}/, '') + File.write(file, contents) end def app_file(path, contents, mode = 'w') diff --git a/tasks/release.rb b/tasks/release.rb index c7704aa865..de9c51a140 100644 --- a/tasks/release.rb +++ b/tasks/release.rb @@ -13,6 +13,7 @@ directory "pkg" task :clean do rm_f gem + sh "cd #{framework} && bundle exec rake package:clean" unless framework == "rails" end task :update_versions do @@ -48,6 +49,7 @@ directory "pkg" task gem => %w(update_versions pkg) do cmd = "" cmd << "cd #{framework} && " unless framework == "rails" + cmd << "bundle exec rake package && " unless framework == "rails" cmd << "gem build #{gemspec} && mv #{framework}-#{version}.gem #{root}/pkg/" sh cmd end diff --git a/version.rb b/version.rb index f4d5b660dc..93e0151602 100644 --- a/version.rb +++ b/version.rb @@ -8,7 +8,7 @@ module Rails MAJOR = 5 MINOR = 0 TINY = 0 - PRE = "beta1.1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end |