diff options
161 files changed, 2036 insertions, 317 deletions
diff --git a/actionmailer/bin/test b/actionmailer/bin/test new file mode 100755 index 0000000000..404cabba51 --- /dev/null +++ b/actionmailer/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +exit Minitest.run(ARGV) diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 66f1d8c6a6..7b2f150c67 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,9 @@ +* Add support for API only apps. + ActionController::API is added as a replacement of + ActionController::Base for this kind of applications. + + *Santiago Pastorino & Jorge Bejar* + * Remove `assigns` and `assert_template`. Both methods have been extracted into a gem at https://github.com/rails/rails-controller-testing. diff --git a/actionpack/bin/test b/actionpack/bin/test new file mode 100755 index 0000000000..404cabba51 --- /dev/null +++ b/actionpack/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +exit Minitest.run(ARGV) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index a1893ce920..89fc4520d3 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -7,6 +7,7 @@ require 'action_controller/metal/strong_parameters' module ActionController extend ActiveSupport::Autoload + autoload :API autoload :Base autoload :Caching autoload :Metal @@ -15,7 +16,6 @@ module ActionController autoload :FormBuilder autoload_under "metal" do - autoload :Compatibility autoload :ConditionalGet autoload :Cookies autoload :DataStreaming @@ -25,6 +25,7 @@ module ActionController autoload :Head autoload :Helpers autoload :HttpAuthentication + autoload :BasicImplicitRender autoload :ImplicitRender autoload :Instrumentation autoload :MimeResponds diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb new file mode 100644 index 0000000000..d8149e0232 --- /dev/null +++ b/actionpack/lib/action_controller/api.rb @@ -0,0 +1,145 @@ +require 'action_view' +require 'action_controller' +require 'action_controller/log_subscriber' + +module ActionController + # API Controller is a lightweight version of <tt>ActionController::Base</tt>, + # created for applications that don't require all functionality that a complete + # \Rails controller provides, allowing you to create controllers with just the + # features that you need for API only applications. + # + # An API Controller is different from a normal controller in the sense that + # by default it doesn't include a number of features that are usually required + # by browser access only: layouts and templates rendering, cookies, sessions, + # flash, assets, and so on. This makes the entire controller stack thinner, + # suitable for API applications. It doesn't mean you won't have such + # features if you need them: they're all available for you to include in + # your application, they're just not part of the default API Controller stack. + # + # By default, only the ApplicationController in a \Rails application inherits + # from <tt>ActionController::API</tt>. All other controllers in turn inherit + # from ApplicationController. + # + # A sample controller could look like this: + # + # class PostsController < ApplicationController + # def index + # @posts = Post.all + # render json: @posts + # end + # end + # + # Request, response and parameters objects all work the exact same way as + # <tt>ActionController::Base</tt>. + # + # == Renders + # + # The default API Controller stack includes all renderers, which means you + # can use <tt>render :json</tt> and brothers freely in your controllers. Keep + # in mind that templates are not going to be rendered, so you need to ensure + # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in + # all actions, otherwise it will return 204 No Content response. + # + # def show + # @post = Post.find(params[:id]) + # render json: @post + # end + # + # == Redirects + # + # Redirects are used to move from one action to another. You can use the + # <tt>redirect</tt> method in your controllers in the same way as + # <tt>ActionController::Base</tt>. For example: + # + # def create + # redirect_to root_url and return if not_authorized? + # # do stuff here + # end + # + # == Adding new behavior + # + # In some scenarios you may want to add back some functionality provided by + # <tt>ActionController::Base</tt> that is not present by default in + # <tt>ActionController::API</tt>, for instance <tt>MimeResponds</tt>. This + # module gives you the <tt>respond_to</tt> and <tt>respond_with</tt> methods. + # Adding it is quite simple, you just need to include the module in a specific + # controller or in <tt>ApplicationController</tt> in case you want it + # available to your entire app: + # + # class ApplicationController < ActionController::API + # include ActionController::MimeResponds + # end + # + # class PostsController < ApplicationController + # def index + # @posts = Post.all + # + # respond_to do |format| + # format.json { render json: @posts } + # format.xml { render xml: @posts } + # end + # end + # end + # + # Quite straightforward. Make sure to check <tt>ActionController::Base</tt> + # available modules if you want to include any other functionality that is + # not provided by <tt>ActionController::API</tt> out of the box. + class API < Metal + abstract! + + # Shortcut helper that returns all the ActionController::API modules except the ones passed in the argument: + # + # class MetalController + # ActionController::API.without_modules(:Redirecting, :DataStreaming).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier + # to create an api controller class, instead of listing the modules required manually. + def self.without_modules(*modules) + modules = modules.map do |m| + m.is_a?(Symbol) ? ActionController.const_get(m) : m + end + + MODULES - modules + end + + MODULES = [ + AbstractController::Rendering, + + UrlFor, + Redirecting, + Rendering, + Renderers::All, + ConditionalGet, + RackDelegation, + BasicImplicitRender, + StrongParameters, + + ForceSSL, + DataStreaming, + + # Before callbacks should also be executed the earliest as possible, so + # also include them at the bottom. + AbstractController::Callbacks, + + # Append rescue at the bottom to wrap as much as possible. + Rescue, + + # Add instrumentations hooks at the bottom, to ensure they instrument + # all the methods properly. + Instrumentation, + + # Params wrapper should come before instrumentation so they are + # properly showed in logs + ParamsWrapper + ] + + MODULES.each do |mod| + include mod + end + + ActiveSupport.run_load_hooks(:action_controller, self) + end +end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index bfae372f53..2c3b3f4e05 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -50,9 +50,9 @@ module ActionController # # == Parameters # - # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method - # which returns a hash. For example, an action that was performed through <tt>/posts?category=All&limit=5</tt> will include - # <tt>{ "category" => "All", "limit" => "5" }</tt> in params. + # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are + # available through the params method which returns a hash. For example, an action that was performed through + # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in params. # # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as: # diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb new file mode 100644 index 0000000000..6c6f8381ff --- /dev/null +++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb @@ -0,0 +1,11 @@ +module ActionController + module BasicImplicitRender + def send_action(method, *args) + super.tap { default_render unless performed? } + end + + def default_render(*args) + head :no_content + end + end +end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 1573ea7099..d66b2214ce 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,17 +1,14 @@ module ActionController module ImplicitRender - def send_action(method, *args) - ret = super - default_render unless performed? - ret - end + + include BasicImplicitRender def default_render(*args) if template_exists?(action_name.to_s, _prefixes, variants: request.variant) render(*args) else logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger - head :no_content + super end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index d34149143f..96f161fb09 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -165,7 +165,7 @@ module ActionController # class BooksControllerTest < ActionController::TestCase # def test_create # # Simulate a POST response with the given HTTP parameters. - # post(:create, book: { title: "Love Hina" }) + # post(:create, params: { book: { title: "Love Hina" }}) # # # Assert that the controller tried to redirect us to # # the created book's URI. diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 3aa0eef4ad..b098ea389f 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -13,7 +13,7 @@ module ActionDispatch # located at `public/assets/application.js` if the file exists. If the file # does not exist, a 404 "File not Found" response will be returned. class FileHandler - def initialize(root, cache_control, index = 'index') + def initialize(root, cache_control, index: 'index') @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ headers = cache_control && { 'Cache-Control' => cache_control } @@ -21,7 +21,6 @@ module ActionDispatch @index = index end - # Takes a path to a file. If the file is found, has valid encoding, and has # correct read permissions, the return value is a URI-escaped string # representing the filename. Otherwise, false is returned. @@ -105,9 +104,9 @@ module ActionDispatch # produce a directory traversal using this middleware. Only 'GET' and 'HEAD' # requests will result in a file being returned. class Static - def initialize(app, path, cache_control = nil, index = 'index') + def initialize(app, path, cache_control = nil, index: 'index') @app = app - @file_handler = FileHandler.new(path, cache_control, index) + @file_handler = FileHandler.new(path, cache_control, index: index) end def call(env) diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 15980db39b..7cfe4693c1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1042,7 +1042,7 @@ module ActionDispatch class Resource #:nodoc: attr_reader :controller, :path, :options, :param - def initialize(entities, options = {}) + def initialize(entities, api_only = false, options = {}) @name = entities.to_s @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @@ -1050,10 +1050,15 @@ module ActionDispatch @param = (options[:param] || :id).to_sym @options = options @shallow = false + @api_only = api_only end def default_actions - [:index, :create, :new, :show, :update, :destroy, :edit] + if @api_only + [:index, :create, :show, :update, :destroy] + else + [:index, :create, :new, :show, :update, :destroy, :edit] + end end def actions @@ -1120,7 +1125,7 @@ module ActionDispatch end class SingletonResource < Resource #:nodoc: - def initialize(entities, options) + def initialize(entities, api_only, options) super @as = nil @controller = (options[:controller] || plural).to_s @@ -1128,7 +1133,11 @@ module ActionDispatch end def default_actions - [:show, :create, :update, :destroy, :new, :edit] + if @api_only + [:show, :create, :update, :destroy] + else + [:show, :create, :update, :destroy, :new, :edit] + end end def plural @@ -1178,7 +1187,7 @@ module ActionDispatch return self end - resource_scope(:resource, SingletonResource.new(resources.pop, options)) do + resource_scope(:resource, SingletonResource.new(resources.pop, api_only?, options)) do yield if block_given? concerns(options[:concerns]) if options[:concerns] @@ -1336,7 +1345,7 @@ module ActionDispatch return self end - resource_scope(:resources, Resource.new(resources.pop, options)) do + resource_scope(:resources, Resource.new(resources.pop, api_only?, options)) do yield if block_given? concerns(options[:concerns]) if options[:concerns] @@ -1768,6 +1777,10 @@ module ActionDispatch delete :destroy if parent_resource.actions.include?(:destroy) end end + + def api_only? + @set.api_only? + end end # Routing Concerns allow you to declare common routes that can be reused diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index cf35e85b40..454593b59f 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -319,17 +319,23 @@ module ActionDispatch end def self.new_with_config(config) + route_set_config = DEFAULT_CONFIG + + # engines apparently don't have this set if config.respond_to? :relative_url_root - new Config.new config.relative_url_root - else - # engines apparently don't have this set - new + route_set_config.relative_url_root = config.relative_url_root + end + + if config.respond_to? :api_only + route_set_config.api_only = config.api_only end + + new route_set_config end - Config = Struct.new :relative_url_root + Config = Struct.new :relative_url_root, :api_only - DEFAULT_CONFIG = Config.new(nil) + DEFAULT_CONFIG = Config.new(nil, false) def initialize(config = DEFAULT_CONFIG) self.named_routes = NamedRouteCollection.new @@ -352,6 +358,10 @@ module ActionDispatch @config.relative_url_root end + def api_only? + @config.api_only + end + def request_class ActionDispatch::Request end diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 1690bdd542..cc610b6d75 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -232,6 +232,10 @@ class Rack::TestCase < ActionDispatch::IntegrationTest end module ActionController + class API + extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes) + end + class Base # This stub emulates the Railtie including the URL helpers from a Rails application extend AbstractController::Railties::RoutesHelpers.with(SharedTestRoutes) diff --git a/actionpack/test/controller/api/conditional_get_test.rb b/actionpack/test/controller/api/conditional_get_test.rb new file mode 100644 index 0000000000..d1eb27bf24 --- /dev/null +++ b/actionpack/test/controller/api/conditional_get_test.rb @@ -0,0 +1,57 @@ +require 'abstract_unit' +require 'active_support/core_ext/integer/time' +require 'active_support/core_ext/numeric/time' + +class ConditionalGetApiController < ActionController::API + before_action :handle_last_modified_and_etags, only: :two + + def one + if stale?(last_modified: Time.now.utc.beginning_of_day, etag: [:foo, 123]) + render text: "Hi!" + end + end + + def two + render text: "Hi!" + end + + private + + def handle_last_modified_and_etags + fresh_when(last_modified: Time.now.utc.beginning_of_day, etag: [ :foo, 123 ]) + end +end + +class ConditionalGetApiTest < ActionController::TestCase + tests ConditionalGetApiController + + def setup + @last_modified = Time.now.utc.beginning_of_day.httpdate + end + + def test_request_gets_last_modified + get :two + assert_equal @last_modified, @response.headers['Last-Modified'] + assert_response :success + end + + def test_request_obeys_last_modified + @request.if_modified_since = @last_modified + get :two + assert_response :not_modified + end + + def test_last_modified_works_with_less_than_too + @request.if_modified_since = 5.years.ago.httpdate + get :two + assert_response :success + end + + def test_request_not_modified + @request.if_modified_since = @last_modified + get :one + assert_equal 304, @response.status.to_i + assert @response.body.blank? + assert_equal @last_modified, @response.headers['Last-Modified'] + end +end diff --git a/actionpack/test/controller/api/data_streaming_test.rb b/actionpack/test/controller/api/data_streaming_test.rb new file mode 100644 index 0000000000..0e7d97d1f4 --- /dev/null +++ b/actionpack/test/controller/api/data_streaming_test.rb @@ -0,0 +1,26 @@ +require 'abstract_unit' + +module TestApiFileUtils + def file_path() File.expand_path(__FILE__) end + def file_data() @data ||= File.open(file_path, 'rb') { |f| f.read } end +end + +class DataStreamingApiController < ActionController::API + include TestApiFileUtils + + def one; end + def two + send_data(file_data, {}) + end +end + +class DataStreamingApiTest < ActionController::TestCase + include TestApiFileUtils + tests DataStreamingApiController + + def test_data + response = process('two') + assert_kind_of String, response.body + assert_equal file_data, response.body + end +end diff --git a/actionpack/test/controller/api/force_ssl_test.rb b/actionpack/test/controller/api/force_ssl_test.rb new file mode 100644 index 0000000000..8578340d82 --- /dev/null +++ b/actionpack/test/controller/api/force_ssl_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' + +class ForceSSLApiController < ActionController::API + force_ssl + + def one; end + def two + head :ok + end +end + +class ForceSSLApiTest < ActionController::TestCase + tests ForceSSLApiController + + def test_redirects_to_https + get :two + assert_response 301 + assert_equal "https://test.host/force_ssl_api/two", redirect_to_url + end +end diff --git a/actionpack/test/controller/api/implicit_render_test.rb b/actionpack/test/controller/api/implicit_render_test.rb new file mode 100644 index 0000000000..26f9cd8f78 --- /dev/null +++ b/actionpack/test/controller/api/implicit_render_test.rb @@ -0,0 +1,15 @@ +require 'abstract_unit' + +class ImplicitRenderAPITestController < ActionController::API + def empty_action + end +end + +class ImplicitRenderAPITest < ActionController::TestCase + tests ImplicitRenderAPITestController + + def test_implicit_no_content_response + get :empty_action + assert_response :no_content + end +end diff --git a/actionpack/test/controller/api/params_wrapper_test.rb b/actionpack/test/controller/api/params_wrapper_test.rb new file mode 100644 index 0000000000..e40a39d829 --- /dev/null +++ b/actionpack/test/controller/api/params_wrapper_test.rb @@ -0,0 +1,26 @@ +require 'abstract_unit' + +class ParamsWrapperForApiTest < ActionController::TestCase + class UsersController < ActionController::API + attr_accessor :last_parameters + + wrap_parameters :person, format: [:json] + + def test + self.last_parameters = params.except(:controller, :action) + head :ok + end + end + + class Person; end + + tests UsersController + + def test_specify_wrapper_name + @request.env['CONTENT_TYPE'] = 'application/json' + post :test, params: { 'username' => 'sikachu' } + + expected = { 'username' => 'sikachu', 'person' => { 'username' => 'sikachu' }} + assert_equal expected, @controller.last_parameters + end +end diff --git a/actionpack/test/controller/api/redirect_to_test.rb b/actionpack/test/controller/api/redirect_to_test.rb new file mode 100644 index 0000000000..18877c4b3a --- /dev/null +++ b/actionpack/test/controller/api/redirect_to_test.rb @@ -0,0 +1,19 @@ +require 'abstract_unit' + +class RedirectToApiController < ActionController::API + def one + redirect_to action: "two" + end + + def two; end +end + +class RedirectToApiTest < ActionController::TestCase + tests RedirectToApiController + + def test_redirect_to + get :one + assert_response :redirect + assert_equal "http://test.host/redirect_to_api/two", redirect_to_url + end +end diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb new file mode 100644 index 0000000000..9405538833 --- /dev/null +++ b/actionpack/test/controller/api/renderers_test.rb @@ -0,0 +1,38 @@ +require 'abstract_unit' +require 'active_support/core_ext/hash/conversions' + +class RenderersApiController < ActionController::API + class Model + def to_json(options = {}) + { a: 'b' }.to_json(options) + end + + def to_xml(options = {}) + { a: 'b' }.to_xml(options) + end + end + + def one + render json: Model.new + end + + def two + render xml: Model.new + end +end + +class RenderersApiTest < ActionController::TestCase + tests RenderersApiController + + def test_render_json + get :one + assert_response :success + assert_equal({ a: 'b' }.to_json, @response.body) + end + + def test_render_xml + get :two + assert_response :success + assert_equal({ a: 'b' }.to_xml, @response.body) + end +end diff --git a/actionpack/test/controller/api/url_for_test.rb b/actionpack/test/controller/api/url_for_test.rb new file mode 100644 index 0000000000..0d8691a091 --- /dev/null +++ b/actionpack/test/controller/api/url_for_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' + +class UrlForApiController < ActionController::API + def one; end + def two; end +end + +class UrlForApiTest < ActionController::TestCase + tests UrlForApiController + + def setup + super + @request.host = 'www.example.com' + end + + def test_url_for + get :one + assert_equal "http://www.example.com/url_for_api/one", @controller.url_for + end +end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index cabacad940..c9c43de37d 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -24,6 +24,11 @@ class TestControllerWithExtraEtags < ActionController::Base end end +class ImplicitRenderTestController < ActionController::Base + def empty_action + end +end + class TestController < ActionController::Base protect_from_forgery @@ -463,6 +468,15 @@ class MetalRenderTest < ActionController::TestCase end end +class ImplicitRenderTest < ActionController::TestCase + tests ImplicitRenderTestController + + def test_implicit_no_content_response + get :empty_action + assert_response :no_content + end +end + class HeadRenderTest < ActionController::TestCase tests TestController diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 62c99a2edc..77f86b7a62 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -167,6 +167,46 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/session/reset', reset_session_path end + def test_session_singleton_resource_for_api_app + self.class.stub_controllers do |_| + config = ActionDispatch::Routing::RouteSet::Config.new + config.api_only = true + + routes = ActionDispatch::Routing::RouteSet.new(config) + + routes.draw do + resource :session do + get :create + post :reset + end + end + @app = RoutedRackApp.new routes + end + + get '/session' + assert_equal 'sessions#create', @response.body + assert_equal '/session', session_path + + post '/session' + assert_equal 'sessions#create', @response.body + + put '/session' + assert_equal 'sessions#update', @response.body + + delete '/session' + assert_equal 'sessions#destroy', @response.body + + post '/session/reset' + assert_equal 'sessions#reset', @response.body + assert_equal '/session/reset', reset_session_path + + get '/session/new' + assert_equal 'Not Found', @response.body + + get '/session/edit' + assert_equal 'Not Found', @response.body + end + def test_session_info_nested_singleton_resource draw do resource :session do @@ -509,6 +549,41 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal '/projects/1/edit', edit_project_path(:id => '1') end + def test_projects_for_api_app + self.class.stub_controllers do |_| + config = ActionDispatch::Routing::RouteSet::Config.new + config.api_only = true + + routes = ActionDispatch::Routing::RouteSet.new(config) + routes.draw do + resources :projects, controller: :project + end + @app = RoutedRackApp.new routes + end + + get '/projects' + assert_equal 'project#index', @response.body + assert_equal '/projects', projects_path + + post '/projects' + assert_equal 'project#create', @response.body + + get '/projects.xml' + assert_equal 'project#index', @response.body + assert_equal '/projects.xml', projects_path(format: 'xml') + + get '/projects/1' + assert_equal 'project#show', @response.body + assert_equal '/projects/1', project_path(id: '1') + + get '/projects/1.xml' + assert_equal 'project#show', @response.body + assert_equal '/projects/1.xml', project_path(id: '1', format: 'xml') + + get '/projects/1/edit' + assert_equal 'Not Found', @response.body + end + def test_projects_with_post_action_and_new_path_on_collection draw do resources :projects, :controller => :project do diff --git a/actionpack/test/dispatch/show_exceptions_test.rb b/actionpack/test/dispatch/show_exceptions_test.rb index 72eaa916bc..cbb12a2209 100644 --- a/actionpack/test/dispatch/show_exceptions_test.rb +++ b/actionpack/test/dispatch/show_exceptions_test.rb @@ -11,7 +11,7 @@ class ShowExceptionsTest < ActionDispatch::IntegrationTest when "/bad_params" raise ActionDispatch::ParamsParser::ParseError.new("", StandardError.new) when "/method_not_allowed" - raise ActionController::MethodNotAllowed + raise ActionController::MethodNotAllowed, 'PUT' when "/unknown_http_method" raise ActionController::UnknownHttpMethod when "/not_found_original_exception" diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index e729cc44f9..95971b3a0e 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -263,7 +263,7 @@ class StaticTest < ActiveSupport::TestCase end def test_non_default_static_index - @app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60", "other-index") + @app = ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60", index: "other-index") assert_html "/other-index.html", get("/other-index.html") assert_html "/other-index.html", get("/other-index") assert_html "/other-index.html", get("/") diff --git a/actionview/bin/test b/actionview/bin/test new file mode 100755 index 0000000000..404cabba51 --- /dev/null +++ b/actionview/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +exit Minitest.run(ARGV) diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 1f103786cb..b29eb48425 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -70,7 +70,8 @@ module ActionView def dependencies DependencyTracker.find_dependencies(name, template) rescue ActionView::MissingTemplate - [] # File doesn't exist, so no dependencies + logger.try :error, " '#{name}' file doesn't exist, so no dependencies" + [] end def nested_dependencies diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index 9a26cba574..5dc7950d6b 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -48,8 +48,10 @@ module ActionView end end - rake_tasks do - load "action_view/tasks/dependencies.rake" + rake_tasks do |app| + unless app.config.api_only + load "action_view/tasks/dependencies.rake" + end end end end diff --git a/actionview/test/template/digestor_test.rb b/actionview/test/template/digestor_test.rb index c2b8439df3..f0afcdb5ae 100644 --- a/actionview/test/template/digestor_test.rb +++ b/actionview/test/template/digestor_test.rb @@ -111,6 +111,18 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_logging_of_missing_template_for_dependencies + assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do + dependencies("messages/something_missing") + end + end + + def test_logging_of_missing_template_for_nested_dependencies + assert_logged "'messages/something_missing' file doesn't exist, so no dependencies" do + nested_dependencies("messages/something_missing") + end + end + def test_nested_template_directory assert_digest_difference("messages/show") do change_template("messages/actions/_move") @@ -298,6 +310,14 @@ class TemplateDigestorTest < ActionView::TestCase ActionView::Digestor.digest({ name: template_name, finder: finder }.merge(options)) end + def dependencies(template_name) + ActionView::Digestor.new({ name: template_name, finder: finder }).dependencies + end + + def nested_dependencies(template_name) + ActionView::Digestor.new({ name: template_name, finder: finder }).nested_dependencies + end + def finder @finder ||= FixtureFinder.new end diff --git a/activemodel/bin/test b/activemodel/bin/test new file mode 100755 index 0000000000..404cabba51 --- /dev/null +++ b/activemodel/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +exit Minitest.run(ARGV) diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 02b2096ef6..14906e441b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,20 @@ +* Make `remove_foreign_key` reversible. Any foreign key options must be + specified, similar to `remove_column`. + + *Aster Ryan* + +* Add `:enum_prefix`/`:enum_suffix` option to `enum` definition. + + Fixes #17511 and #17415 + + *Igor Kapkov* + +* Correctly handle decimal arrays with defaults in the schema dumper. + + Fixes #20515. + + *Sean Griffin & jmondo* + * Deprecate the PG `:point` type in favor of a new one which will return `Point` objects instead of an `Array` diff --git a/activerecord/bin/test b/activerecord/bin/test new file mode 100755 index 0000000000..f8adf2aabc --- /dev/null +++ b/activerecord/bin/test @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +module Minitest + def self.plugin_active_record_options(opts, options) + opts.separator "" + opts.separator "Active Record options:" + opts.on("-a", "--adapter [ADAPTER]", + "Run tests using a specific adapter (sqlite3, sqlite3_mem, mysql, mysql2, postgresql)") do |adapter| + ENV["ARCONN"] = adapter.strip + end + + opts + end +end + +Minitest.extensions.unshift 'active_record' + +exit Minitest.run(ARGV) 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 dd7dcb9cdd..c8be038d76 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -777,7 +777,10 @@ module ActiveRecord execute schema_creation.accept(at) end - # Removes the given foreign key from the table. + # Removes the given foreign key from the table. Any option parameters provided + # will be used to re-add the foreign key in case of a migration rollback. + # It is recommended that you provide any options used when creating the foreign + # key so that the migration can be reverted properly. # # Removes the foreign key on +accounts.branch_id+. # @@ -791,6 +794,7 @@ module ActiveRecord # # remove_foreign_key :accounts, name: :special_fk_name # + # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key. def remove_foreign_key(from_table, options_or_to_table = {}) return unless supports_foreign_keys? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index 3de794f797..25961a9869 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -45,6 +45,11 @@ module ActiveRecord delimiter == other.delimiter end + def type_cast_for_schema(value) + return super unless value.is_a?(::Array) + "[" + value.map { |v| subtype.type_cast_for_schema(v) }.join(", ") + "]" + end + private def type_cast_array(value, method) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 2b99899e42..ad33c84dbc 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -75,6 +75,22 @@ module ActiveRecord # # Conversation.where("status <> ?", Conversation.statuses[:archived]) # + # You can use <tt>:enum_prefix</tt>/<tt>:enum_suffix</tt> option then you need + # to define multiple enums with same values. If option value is <tt>true</tt>, + # the methods are prefixed/suffixed with the name of the enum. + # + # class Invoice < ActiveRecord::Base + # enum verification: [:done, :fail], enum_prefix: true + # end + # + # It is also possible to supply a custom prefix. + # + # class Invoice < ActiveRecord::Base + # enum verification: [:done, :fail], enum_prefix: :verification_status + # end + # + # Note that <tt>:enum_prefix</tt>/<tt>:enum_postfix</tt> are reserved keywords + # and can not be used as an enum name. module Enum def self.extended(base) # :nodoc: @@ -121,6 +137,8 @@ module ActiveRecord def enum(definitions) klass = self + enum_prefix = definitions.delete(:enum_prefix) + enum_suffix = definitions.delete(:enum_suffix) definitions.each do |name, values| # statuses = { } enum_values = ActiveSupport::HashWithIndifferentAccess.new @@ -138,19 +156,31 @@ module ActiveRecord _enum_methods_module.module_eval do pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index pairs.each do |value, i| + if enum_prefix == true + prefix = "#{name}_" + elsif enum_prefix + prefix = "#{enum_prefix}_" + end + if enum_suffix == true + suffix = "_#{name}" + elsif enum_suffix + suffix = "_#{enum_suffix}" + end + + value_method_name = "#{prefix}#{value}#{suffix}" enum_values[value] = i # def active?() status == 0 end - klass.send(:detect_enum_conflict!, name, "#{value}?") - define_method("#{value}?") { self[name] == value.to_s } + klass.send(:detect_enum_conflict!, name, "#{value_method_name}?") + define_method("#{value_method_name}?") { self[name] == value.to_s } # def active!() update! status: :active end - klass.send(:detect_enum_conflict!, name, "#{value}!") - define_method("#{value}!") { update! name => value } + klass.send(:detect_enum_conflict!, name, "#{value_method_name}!") + define_method("#{value_method_name}!") { update! name => value } # scope :active, -> { where status: 0 } - klass.send(:detect_enum_conflict!, name, value, true) - klass.scope value, -> { klass.where name => value } + klass.send(:detect_enum_conflict!, name, value_method_name, true) + klass.scope value_method_name, -> { klass.where name => value } end end defined_enums[name.to_s] = enum_values diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 7760fd33b7..738fab2bd9 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -668,7 +668,7 @@ module ActiveRecord row[association.foreign_type] = $1 end - fk_type = association.active_record.type_for_attribute(fk_name).type + fk_type = reflection_class.type_for_attribute(fk_name).type row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) end when :has_many diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 36256415df..ee4545ed71 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -184,6 +184,16 @@ module ActiveRecord [:remove_foreign_key, [from_table, options]] end + def invert_remove_foreign_key(args) + from_table, to_table, remove_options = args + raise ActiveRecord::IrreversibleMigration, "remove_foreign_key is only reversible if given a second table" if to_table.nil? || to_table.is_a?(Hash) + + reversed_args = [from_table, to_table] + reversed_args << remove_options if remove_options + + [:add_foreign_key, reversed_args] + end + # Forwards any missing method call to the \target. def method_missing(method, *args, &block) if @delegate.respond_to?(method) diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 437ba31711..0a6e4ac0bd 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -211,8 +211,7 @@ module ActiveRecord def becomes(klass) became = klass.new became.instance_variable_set("@attributes", @attributes) - changed_attributes = @changed_attributes if defined?(@changed_attributes) - became.instance_variable_set("@changed_attributes", changed_attributes || {}) + became.instance_variable_set("@changed_attributes", attributes_changed_by_setter) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) became.instance_variable_set("@errors", errors) diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 5af64b717a..da6b8447d3 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -156,8 +156,8 @@ end_warning ActiveSupport.on_load(:active_record) do ActionDispatch::Reloader.send(hook) do if ActiveRecord::Base.connected? - ActiveRecord::Base.clear_reloadable_connections! ActiveRecord::Base.clear_cache! + ActiveRecord::Base.clear_reloadable_connections! end end end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index c2567311bd..ba75ffa5a1 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -119,9 +119,9 @@ module ActiveRecord end def replace_named_bind_variables(statement, bind_vars) #:nodoc: - statement.gsub(/(:?):([a-zA-Z]\w*)/) do + statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| if $1 == ':' # skip postgresql casts - $& # return the whole match + match # return the whole match elsif bind_vars.include?(match = $2.to_sym) replace_bind_variable(bind_vars[match]) else diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 57eb5d0e18..f0fd95ac16 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/connection_helper' -class ActiveSchemaTest < ActiveRecord::TestCase +class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase include ConnectionHelper def setup diff --git a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb index 345122b1ad..98d44315dd 100644 --- a/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql/case_sensitivity_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class MysqlCaseSensitivityTest < ActiveRecord::TestCase +class MysqlCaseSensitivityTest < ActiveRecord::MysqlTestCase class CollationTest < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/mysql/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb index c8dd49d00a..f2117a97e6 100644 --- a/activerecord/test/cases/adapters/mysql/charset_collation_test.rb +++ b/activerecord/test/cases/adapters/mysql/charset_collation_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class CharsetCollationTest < ActiveRecord::TestCase +class MysqlCharsetCollationTest < ActiveRecord::MysqlTestCase include SchemaDumpingHelper self.use_transactional_tests = false diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index 9903cd3c0b..ddbc007b87 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'support/connection_helper' require 'support/ddl_helper' -class MysqlConnectionTest < ActiveRecord::TestCase +class MysqlConnectionTest < ActiveRecord::MysqlTestCase include ConnectionHelper include DdlHelper diff --git a/activerecord/test/cases/adapters/mysql/consistency_test.rb b/activerecord/test/cases/adapters/mysql/consistency_test.rb index ae190b728d..743f6436e4 100644 --- a/activerecord/test/cases/adapters/mysql/consistency_test.rb +++ b/activerecord/test/cases/adapters/mysql/consistency_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class MysqlConsistencyTest < ActiveRecord::TestCase +class MysqlConsistencyTest < ActiveRecord::MysqlTestCase self.use_transactional_tests = false class Consistency < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/mysql/enum_test.rb b/activerecord/test/cases/adapters/mysql/enum_test.rb index f4e7a3ef0a..ef8ee0a6e3 100644 --- a/activerecord/test/cases/adapters/mysql/enum_test.rb +++ b/activerecord/test/cases/adapters/mysql/enum_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class MysqlEnumTest < ActiveRecord::TestCase +class MysqlEnumTest < ActiveRecord::MysqlTestCase class EnumTest < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index 48ceef365e..b804cb45b9 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -4,7 +4,7 @@ require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters - class MysqlAdapterTest < ActiveRecord::TestCase + class MysqlAdapterTest < ActiveRecord::MysqlTestCase include DdlHelper def setup diff --git a/activerecord/test/cases/adapters/mysql/quoting_test.rb b/activerecord/test/cases/adapters/mysql/quoting_test.rb index a2206153e9..a296cf9d31 100644 --- a/activerecord/test/cases/adapters/mysql/quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql/quoting_test.rb @@ -1,21 +1,15 @@ require "cases/helper" -module ActiveRecord - module ConnectionAdapters - class MysqlAdapter - class QuotingTest < ActiveRecord::TestCase - def setup - @conn = ActiveRecord::Base.connection - end +class MysqlQuotingTest < ActiveRecord::MysqlTestCase + def setup + @conn = ActiveRecord::Base.connection + end - def test_type_cast_true - assert_equal 1, @conn.type_cast(true) - end + def test_type_cast_true + assert_equal 1, @conn.type_cast(true) + end - def test_type_cast_false - assert_equal 0, @conn.type_cast(false) - end - end - end + def test_type_cast_false + assert_equal 0, @conn.type_cast(false) end end diff --git a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb index ec1c394f40..4ea1d9ad36 100644 --- a/activerecord/test/cases/adapters/mysql/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql/reserved_word_test.rb @@ -1,29 +1,29 @@ require "cases/helper" -class Group < ActiveRecord::Base - Group.table_name = 'group' - belongs_to :select - has_one :values -end +# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with +# reserved word names (ie: group, order, values, etc...) +class MysqlReservedWordTest < ActiveRecord::MysqlTestCase + class Group < ActiveRecord::Base + Group.table_name = 'group' + belongs_to :select + has_one :values + end -class Select < ActiveRecord::Base - Select.table_name = 'select' - has_many :groups -end + class Select < ActiveRecord::Base + Select.table_name = 'select' + has_many :groups + end -class Values < ActiveRecord::Base - Values.table_name = 'values' -end + class Values < ActiveRecord::Base + Values.table_name = 'values' + end -class Distinct < ActiveRecord::Base - Distinct.table_name = 'distinct' - has_and_belongs_to_many :selects - has_many :values, :through => :groups -end + class Distinct < ActiveRecord::Base + Distinct.table_name = 'distinct' + has_and_belongs_to_many :selects + has_many :values, :through => :groups + end -# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with -# reserved word names (ie: group, order, values, etc...) -class MysqlReservedWordTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/adapters/mysql/schema_test.rb b/activerecord/test/cases/adapters/mysql/schema_test.rb index b7f9c2ce84..2e18f609fd 100644 --- a/activerecord/test/cases/adapters/mysql/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/schema_test.rb @@ -4,7 +4,7 @@ require 'models/comment' module ActiveRecord module ConnectionAdapters - class MysqlSchemaTest < ActiveRecord::TestCase + class MysqlSchemaTest < ActiveRecord::MysqlTestCase fixtures :posts def setup diff --git a/activerecord/test/cases/adapters/mysql/sp_test.rb b/activerecord/test/cases/adapters/mysql/sp_test.rb index 3ca2917ca4..a3d5110032 100644 --- a/activerecord/test/cases/adapters/mysql/sp_test.rb +++ b/activerecord/test/cases/adapters/mysql/sp_test.rb @@ -1,11 +1,11 @@ require "cases/helper" require 'models/topic' -class StoredProcedureTest < ActiveRecord::TestCase +class StoredProcedureTest < ActiveRecord::MysqlTestCase fixtures :topics # Test that MySQL allows multiple results for stored procedures - if Mysql.const_defined?(:CLIENT_MULTI_RESULTS) + if defined?(Mysql) && Mysql.const_defined?(:CLIENT_MULTI_RESULTS) def test_multi_results_from_find_by_sql topics = Topic.find_by_sql 'CALL topics();' assert_equal 1, topics.size diff --git a/activerecord/test/cases/adapters/mysql/sql_types_test.rb b/activerecord/test/cases/adapters/mysql/sql_types_test.rb index 1ddb1b91c9..25b28de7f0 100644 --- a/activerecord/test/cases/adapters/mysql/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql/sql_types_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class SqlTypesTest < ActiveRecord::TestCase +class MysqlSqlTypesTest < ActiveRecord::MysqlTestCase def test_binary_types assert_equal 'varbinary(64)', type_to_sql(:binary, 64) assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb index 209a0cf464..6be36566de 100644 --- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -1,23 +1,19 @@ require 'cases/helper' -module ActiveRecord::ConnectionAdapters - class MysqlAdapter - class StatementPoolTest < ActiveRecord::TestCase - if Process.respond_to?(:fork) - def test_cache_is_per_pid - cache = StatementPool.new nil, 10 - cache['foo'] = 'bar' - assert_equal 'bar', cache['foo'] +class MysqlStatementPoolTest < ActiveRecord::MysqlTestCase + if Process.respond_to?(:fork) + def test_cache_is_per_pid + cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new nil, 10 + cache['foo'] = 'bar' + assert_equal 'bar', cache['foo'] - pid = fork { - lookup = cache['foo']; - exit!(!lookup) - } + pid = fork { + lookup = cache['foo']; + exit!(!lookup) + } - Process.waitpid pid - assert $?.success?, 'process should exit successfully' - end - end + Process.waitpid pid + assert $?.success?, 'process should exit successfully' end end end diff --git a/activerecord/test/cases/adapters/mysql/table_options_test.rb b/activerecord/test/cases/adapters/mysql/table_options_test.rb index 0e5b0e8aec..99df6d6cba 100644 --- a/activerecord/test/cases/adapters/mysql/table_options_test.rb +++ b/activerecord/test/cases/adapters/mysql/table_options_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class MysqlTableOptionsTest < ActiveRecord::TestCase +class MysqlTableOptionsTest < ActiveRecord::MysqlTestCase include SchemaDumpingHelper def setup diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb index e9edc53f93..ed9398a918 100644 --- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class UnsignedTypeTest < ActiveRecord::TestCase +class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase self.use_transactional_tests = false class UnsignedType < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 0ea556d4fa..6558d60aa1 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/connection_helper' -class ActiveSchemaTest < ActiveRecord::TestCase +class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase include ConnectionHelper def setup diff --git a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb index 5e8065d80d..abdf3dbf5b 100644 --- a/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/bind_parameter_test.rb @@ -4,7 +4,7 @@ require 'models/topic' module ActiveRecord module ConnectionAdapters class Mysql2Adapter - class BindParameterTest < ActiveRecord::TestCase + class BindParameterTest < ActiveRecord::Mysql2TestCase fixtures :topics def test_update_question_marks diff --git a/activerecord/test/cases/adapters/mysql2/boolean_test.rb b/activerecord/test/cases/adapters/mysql2/boolean_test.rb index 0d81dd6eee..8575df9e43 100644 --- a/activerecord/test/cases/adapters/mysql2/boolean_test.rb +++ b/activerecord/test/cases/adapters/mysql2/boolean_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class Mysql2BooleanTest < ActiveRecord::TestCase +class Mysql2BooleanTest < ActiveRecord::Mysql2TestCase self.use_transactional_tests = false class BooleanType < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb index ccf3d84a44..963116f08a 100644 --- a/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb +++ b/activerecord/test/cases/adapters/mysql2/case_sensitivity_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class Mysql2CaseSensitivityTest < ActiveRecord::TestCase +class Mysql2CaseSensitivityTest < ActiveRecord::Mysql2TestCase class CollationTest < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb index c8dd49d00a..4fd34def15 100644 --- a/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb +++ b/activerecord/test/cases/adapters/mysql2/charset_collation_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class CharsetCollationTest < ActiveRecord::TestCase +class Mysql2CharsetCollationTest < ActiveRecord::Mysql2TestCase include SchemaDumpingHelper self.use_transactional_tests = false diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index c4e0278c89..000bcadebe 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/connection_helper' -class MysqlConnectionTest < ActiveRecord::TestCase +class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase include ConnectionHelper fixtures :comments diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb index 6dd9a5ec87..bd732b5eca 100644 --- a/activerecord/test/cases/adapters/mysql2/enum_test.rb +++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class Mysql2EnumTest < ActiveRecord::TestCase +class Mysql2EnumTest < ActiveRecord::Mysql2TestCase class EnumTest < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/mysql2/explain_test.rb b/activerecord/test/cases/adapters/mysql2/explain_test.rb index 2b01d941b8..4fc7414b18 100644 --- a/activerecord/test/cases/adapters/mysql2/explain_test.rb +++ b/activerecord/test/cases/adapters/mysql2/explain_test.rb @@ -5,7 +5,7 @@ require 'models/computer' module ActiveRecord module ConnectionAdapters class Mysql2Adapter - class ExplainTest < ActiveRecord::TestCase + class ExplainTest < ActiveRecord::Mysql2TestCase fixtures :developers def test_explain_for_one_query diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index 799e60a683..ffb4e2c5cf 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -1,29 +1,29 @@ require "cases/helper" -class Group < ActiveRecord::Base - Group.table_name = 'group' - belongs_to :select - has_one :values -end +# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with +# reserved word names (ie: group, order, values, etc...) +class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase + class Group < ActiveRecord::Base + Group.table_name = 'group' + belongs_to :select + has_one :values + end -class Select < ActiveRecord::Base - Select.table_name = 'select' - has_many :groups -end + class Select < ActiveRecord::Base + Select.table_name = 'select' + has_many :groups + end -class Values < ActiveRecord::Base - Values.table_name = 'values' -end + class Values < ActiveRecord::Base + Values.table_name = 'values' + end -class Distinct < ActiveRecord::Base - Distinct.table_name = 'distinct' - has_and_belongs_to_many :selects - has_many :values, :through => :groups -end + class Distinct < ActiveRecord::Base + Distinct.table_name = 'distinct' + has_and_belongs_to_many :selects + has_many :values, :through => :groups + end -# a suite of tests to ensure the ConnectionAdapters#MysqlAdapter can handle tables with -# reserved word names (ie: group, order, values, etc...) -class MysqlReservedWordTest < ActiveRecord::TestCase def setup @connection = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index 417ccf6d11..396f235e77 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -1,47 +1,42 @@ require "cases/helper" -module ActiveRecord - module ConnectionAdapters - class AbstractMysqlAdapter - class SchemaMigrationsTest < ActiveRecord::TestCase - def test_renaming_index_on_foreign_key - connection.add_index "engines", "car_id" - connection.add_foreign_key :engines, :cars, name: "fk_engines_cars" - - connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed") - assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name) - ensure - connection.remove_foreign_key :engines, name: "fk_engines_cars" - end - - def test_initializes_schema_migrations_for_encoding_utf8mb4 - smtn = ActiveRecord::Migrator.schema_migrations_table_name - connection.drop_table smtn, if_exists: true - - database_name = connection.current_database - database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'") - - original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"] - original_collation = database_info["DEFAULT_COLLATION_NAME"] - - execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4") - - connection.initialize_schema_migrations_table - - assert connection.column_exists?(smtn, :version, :string, limit: AbstractMysqlAdapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN) - ensure - execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}") - end - - private - def connection - @connection ||= ActiveRecord::Base.connection - end - - def execute(sql) - connection.execute(sql) - end - end - end +class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase + def test_renaming_index_on_foreign_key + connection.add_index "engines", "car_id" + connection.add_foreign_key :engines, :cars, name: "fk_engines_cars" + + connection.rename_index("engines", "index_engines_on_car_id", "idx_renamed") + assert_equal ["idx_renamed"], connection.indexes("engines").map(&:name) + ensure + connection.remove_foreign_key :engines, name: "fk_engines_cars" + end + + def test_initializes_schema_migrations_for_encoding_utf8mb4 + smtn = ActiveRecord::Migrator.schema_migrations_table_name + connection.drop_table smtn, if_exists: true + + database_name = connection.current_database + database_info = connection.select_one("SELECT * FROM information_schema.schemata WHERE schema_name = '#{database_name}'") + + original_charset = database_info["DEFAULT_CHARACTER_SET_NAME"] + original_collation = database_info["DEFAULT_COLLATION_NAME"] + + execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET utf8mb4") + + connection.initialize_schema_migrations_table + + limit = ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MAX_INDEX_LENGTH_FOR_CHARSETS_OF_4BYTES_MAXLEN + assert connection.column_exists?(smtn, :version, :string, limit: limit) + ensure + execute("ALTER DATABASE #{database_name} DEFAULT CHARACTER SET #{original_charset} COLLATE #{original_collation}") + end + + private + def connection + @connection ||= ActiveRecord::Base.connection + end + + def execute(sql) + connection.execute(sql) end end diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 47707b7d4f..880a2123d2 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -4,7 +4,7 @@ require 'models/comment' module ActiveRecord module ConnectionAdapters - class Mysql2SchemaTest < ActiveRecord::TestCase + class Mysql2SchemaTest < ActiveRecord::Mysql2TestCase fixtures :posts def setup diff --git a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb index 1ddb1b91c9..ae505d29c9 100644 --- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class SqlTypesTest < ActiveRecord::TestCase +class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase def test_binary_types assert_equal 'varbinary(64)', type_to_sql(:binary, 64) assert_equal 'varbinary(4095)', type_to_sql(:binary, 4095) diff --git a/activerecord/test/cases/adapters/mysql2/table_options_test.rb b/activerecord/test/cases/adapters/mysql2/table_options_test.rb index 0e5b0e8aec..af121ee7d9 100644 --- a/activerecord/test/cases/adapters/mysql2/table_options_test.rb +++ b/activerecord/test/cases/adapters/mysql2/table_options_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class MysqlTableOptionsTest < ActiveRecord::TestCase +class Mysql2TableOptionsTest < ActiveRecord::Mysql2TestCase include SchemaDumpingHelper def setup diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index e9edc53f93..9e06db2519 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class UnsignedTypeTest < ActiveRecord::TestCase +class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase self.use_transactional_tests = false class UnsignedType < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index 3808db5141..dc7ba314c6 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -1,6 +1,6 @@ require 'cases/helper' -class PostgresqlActiveSchemaTest < ActiveRecord::TestCase +class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase def setup ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do def execute(sql, name = nil) sql end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 6edbd9c3a6..380a90d765 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -1,10 +1,9 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlArrayTest < ActiveRecord::TestCase +class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper include InTimeZone - OID = ActiveRecord::ConnectionAdapters::PostgreSQL::OID class PgArray < ActiveRecord::Base self.table_name = 'pg_arrays' @@ -212,8 +211,9 @@ class PostgresqlArrayTest < ActiveRecord::TestCase def test_quoting_non_standard_delimiters strings = ["hello,", "world;"] - comma_delim = OID::Array.new(ActiveRecord::Type::String.new, ',') - semicolon_delim = OID::Array.new(ActiveRecord::Type::String.new, ';') + oid = ActiveRecord::ConnectionAdapters::PostgreSQL::OID + comma_delim = oid::Array.new(ActiveRecord::Type::String.new, ',') + semicolon_delim = oid::Array.new(ActiveRecord::Type::String.new, ';') assert_equal %({"hello,",world;}), comma_delim.serialize(strings) assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings) diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb index 1a5ff4316c..6f72fa6e0f 100644 --- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'support/connection_helper' require 'support/schema_dumping_helper' -class PostgresqlBitStringTest < ActiveRecord::TestCase +class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper include SchemaDumpingHelper diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index 16db5ab83d..b6bb1929e6 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class PostgresqlByteaTest < ActiveRecord::TestCase +class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase class ByteaDataType < ActiveRecord::Base self.table_name = 'bytea_data_type' end diff --git a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb index 5a9796887c..bc12df668d 100644 --- a/activerecord/test/cases/adapters/postgresql/change_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/change_schema_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' module ActiveRecord class Migration - class PGChangeSchemaTest < ActiveRecord::TestCase + class PGChangeSchemaTest < ActiveRecord::PostgreSQLTestCase attr_reader :connection def setup diff --git a/activerecord/test/cases/adapters/postgresql/cidr_test.rb b/activerecord/test/cases/adapters/postgresql/cidr_test.rb index 6cb11d17b4..52f2a0096c 100644 --- a/activerecord/test/cases/adapters/postgresql/cidr_test.rb +++ b/activerecord/test/cases/adapters/postgresql/cidr_test.rb @@ -3,8 +3,8 @@ require "ipaddr" module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapter - class CidrTest < ActiveRecord::TestCase + class PostgreSQLAdapter < AbstractAdapter + class CidrTest < ActiveRecord::PostgreSQLTestCase test "type casting IPAddr for database" do type = OID::Cidr.new ip = IPAddr.new("255.0.0.0/8") diff --git a/activerecord/test/cases/adapters/postgresql/citext_test.rb b/activerecord/test/cases/adapters/postgresql/citext_test.rb index f706847890..bd62041e79 100644 --- a/activerecord/test/cases/adapters/postgresql/citext_test.rb +++ b/activerecord/test/cases/adapters/postgresql/citext_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' require 'support/schema_dumping_helper' if ActiveRecord::Base.connection.supports_extensions? - class PostgresqlCitextTest < ActiveRecord::TestCase + class PostgresqlCitextTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Citext < ActiveRecord::Base self.table_name = 'citexts' diff --git a/activerecord/test/cases/adapters/postgresql/collation_test.rb b/activerecord/test/cases/adapters/postgresql/collation_test.rb index 17ef5f304c..8470329c35 100644 --- a/activerecord/test/cases/adapters/postgresql/collation_test.rb +++ b/activerecord/test/cases/adapters/postgresql/collation_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlCollationTest < ActiveRecord::TestCase +class PostgresqlCollationTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper def setup diff --git a/activerecord/test/cases/adapters/postgresql/composite_test.rb b/activerecord/test/cases/adapters/postgresql/composite_test.rb index 16e3f90a47..1de87e5f01 100644 --- a/activerecord/test/cases/adapters/postgresql/composite_test.rb +++ b/activerecord/test/cases/adapters/postgresql/composite_test.rb @@ -40,7 +40,7 @@ end # "unknown OID 5653508: failed to recognize type of 'address'. It will be treated as String." # To take full advantage of composite types, we suggest you register your own +OID::Type+. # See PostgresqlCompositeWithCustomOIDTest -class PostgresqlCompositeTest < ActiveRecord::TestCase +class PostgresqlCompositeTest < ActiveRecord::PostgreSQLTestCase include PostgresqlCompositeBehavior def test_column @@ -77,7 +77,7 @@ class PostgresqlCompositeTest < ActiveRecord::TestCase end end -class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::TestCase +class PostgresqlCompositeWithCustomOIDTest < ActiveRecord::PostgreSQLTestCase include PostgresqlCompositeBehavior class FullAddressType < ActiveRecord::Type::Value diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 55ad76c8c0..820d41e13b 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'support/connection_helper' module ActiveRecord - class PostgresqlConnectionTest < ActiveRecord::TestCase + class PostgresqlConnectionTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper class NonExistentTable < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 2c14252ae4..232c25cb3b 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -11,7 +11,7 @@ end class PostgresqlLtree < ActiveRecord::Base end -class PostgresqlDataTypeTest < ActiveRecord::TestCase +class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false def setup @@ -69,7 +69,7 @@ class PostgresqlDataTypeTest < ActiveRecord::TestCase end end -class PostgresqlInternalDataTypeTest < ActiveRecord::TestCase +class PostgresqlInternalDataTypeTest < ActiveRecord::PostgreSQLTestCase include DdlHelper setup do diff --git a/activerecord/test/cases/adapters/postgresql/domain_test.rb b/activerecord/test/cases/adapters/postgresql/domain_test.rb index 26e064c937..6102ddacd1 100644 --- a/activerecord/test/cases/adapters/postgresql/domain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/domain_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/connection_helper' -class PostgresqlDomainTest < ActiveRecord::TestCase +class PostgresqlDomainTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper class PostgresqlDomain < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/postgresql/enum_test.rb b/activerecord/test/cases/adapters/postgresql/enum_test.rb index ed084483bc..6816a6514b 100644 --- a/activerecord/test/cases/adapters/postgresql/enum_test.rb +++ b/activerecord/test/cases/adapters/postgresql/enum_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/connection_helper' -class PostgresqlEnumTest < ActiveRecord::TestCase +class PostgresqlEnumTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper class PostgresqlEnum < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/postgresql/explain_test.rb b/activerecord/test/cases/adapters/postgresql/explain_test.rb index 6ffb4c9f33..4d0fd640aa 100644 --- a/activerecord/test/cases/adapters/postgresql/explain_test.rb +++ b/activerecord/test/cases/adapters/postgresql/explain_test.rb @@ -2,25 +2,19 @@ require "cases/helper" require 'models/developer' require 'models/computer' -module ActiveRecord - module ConnectionAdapters - class PostgreSQLAdapter - class ExplainTest < ActiveRecord::TestCase - fixtures :developers +class PostgreSQLExplainTest < ActiveRecord::PostgreSQLTestCase + fixtures :developers - def test_explain_for_one_query - explain = Developer.where(:id => 1).explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain - assert_match %(QUERY PLAN), explain - end + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain + assert_match %(QUERY PLAN), explain + end - def test_explain_with_eager_loading - explain = Developer.where(:id => 1).includes(:audit_logs).explain - assert_match %(QUERY PLAN), explain - assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain - assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain - end - end - end + def test_explain_with_eager_loading + explain = Developer.where(:id => 1).includes(:audit_logs).explain + assert_match %(QUERY PLAN), explain + assert_match %(EXPLAIN for: SELECT "developers".* FROM "developers" WHERE "developers"."id" = $1), explain + assert_match %(EXPLAIN for: SELECT "audit_logs".* FROM "audit_logs" WHERE "audit_logs"."developer_id" = 1), explain end end diff --git a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb index 06d427f464..9cfc133308 100644 --- a/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb +++ b/activerecord/test/cases/adapters/postgresql/extension_migration_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class PostgresqlExtensionMigrationTest < ActiveRecord::TestCase +class PostgresqlExtensionMigrationTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false class EnableHstore < ActiveRecord::Migration diff --git a/activerecord/test/cases/adapters/postgresql/full_text_test.rb b/activerecord/test/cases/adapters/postgresql/full_text_test.rb index b83063c94e..bde7513339 100644 --- a/activerecord/test/cases/adapters/postgresql/full_text_test.rb +++ b/activerecord/test/cases/adapters/postgresql/full_text_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlFullTextTest < ActiveRecord::TestCase +class PostgresqlFullTextTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Tsvector < ActiveRecord::Base; end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index 7057848eb0..0baf985654 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'support/connection_helper' require 'support/schema_dumping_helper' -class PostgresqlPointTest < ActiveRecord::TestCase +class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase include ConnectionHelper include SchemaDumpingHelper @@ -166,7 +166,7 @@ class PostgresqlPointTest < ActiveRecord::TestCase end end -class PostgresqlGeometricTest < ActiveRecord::TestCase +class PostgresqlGeometricTest < ActiveRecord::PostgreSQLTestCase class PostgresqlGeometric < ActiveRecord::Base; end setup do diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index ad9dd311a6..6a2d501646 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'support/schema_dumping_helper' if ActiveRecord::Base.connection.supports_extensions? - class PostgresqlHstoreTest < ActiveRecord::TestCase + class PostgresqlHstoreTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Hstore < ActiveRecord::Base self.table_name = 'hstores' diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb index d9d7832094..bfda933fa4 100644 --- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class PostgresqlInfinityTest < ActiveRecord::TestCase +class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase include InTimeZone class PostgresqlInfinity < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/postgresql/integer_test.rb b/activerecord/test/cases/adapters/postgresql/integer_test.rb index 679a0fc7b3..b4e55964b9 100644 --- a/activerecord/test/cases/adapters/postgresql/integer_test.rb +++ b/activerecord/test/cases/adapters/postgresql/integer_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require "active_support/core_ext/numeric/bytes" -class PostgresqlIntegerTest < ActiveRecord::TestCase +class PostgresqlIntegerTest < ActiveRecord::PostgreSQLTestCase class PgInteger < ActiveRecord::Base end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 6878516aeb..f242f32496 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -188,7 +188,7 @@ module PostgresqlJSONSharedTestCases end end -class PostgresqlJSONTest < ActiveRecord::TestCase +class PostgresqlJSONTest < ActiveRecord::PostgreSQLTestCase include PostgresqlJSONSharedTestCases def column_type @@ -196,7 +196,7 @@ class PostgresqlJSONTest < ActiveRecord::TestCase end end -class PostgresqlJSONBTest < ActiveRecord::TestCase +class PostgresqlJSONBTest < ActiveRecord::PostgreSQLTestCase include PostgresqlJSONSharedTestCases def column_type diff --git a/activerecord/test/cases/adapters/postgresql/ltree_test.rb b/activerecord/test/cases/adapters/postgresql/ltree_test.rb index ce0ad16557..56516c82b4 100644 --- a/activerecord/test/cases/adapters/postgresql/ltree_test.rb +++ b/activerecord/test/cases/adapters/postgresql/ltree_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlLtreeTest < ActiveRecord::TestCase +class PostgresqlLtreeTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class Ltree < ActiveRecord::Base self.table_name = 'ltrees' diff --git a/activerecord/test/cases/adapters/postgresql/money_test.rb b/activerecord/test/cases/adapters/postgresql/money_test.rb index cedd399380..e3670c203d 100644 --- a/activerecord/test/cases/adapters/postgresql/money_test.rb +++ b/activerecord/test/cases/adapters/postgresql/money_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlMoneyTest < ActiveRecord::TestCase +class PostgresqlMoneyTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class PostgresqlMoney < ActiveRecord::Base; end diff --git a/activerecord/test/cases/adapters/postgresql/network_test.rb b/activerecord/test/cases/adapters/postgresql/network_test.rb index 033695518e..fe6ee4e2d9 100644 --- a/activerecord/test/cases/adapters/postgresql/network_test.rb +++ b/activerecord/test/cases/adapters/postgresql/network_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlNetworkTest < ActiveRecord::TestCase +class PostgresqlNetworkTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class PostgresqlNetworkAddress < ActiveRecord::Base; end diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb index d8e01e3b89..ba7e7dc9a3 100644 --- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb +++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class PostgresqlNumberTest < ActiveRecord::TestCase +class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase class PostgresqlNumber < ActiveRecord::Base; end setup do diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb index 9a1b889d4d..6e6850c4a9 100644 --- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb +++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb @@ -4,7 +4,7 @@ require 'support/connection_helper' module ActiveRecord module ConnectionAdapters - class PostgreSQLAdapterTest < ActiveRecord::TestCase + class PostgreSQLAdapterTest < ActiveRecord::PostgreSQLTestCase include DdlHelper include ConnectionHelper diff --git a/activerecord/test/cases/adapters/postgresql/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index e4420d9d13..5e6f4dbbb8 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -4,7 +4,7 @@ require 'ipaddr' module ActiveRecord module ConnectionAdapters class PostgreSQLAdapter - class QuotingTest < ActiveRecord::TestCase + class QuotingTest < ActiveRecord::PostgreSQLTestCase def setup @conn = ActiveRecord::Base.connection end diff --git a/activerecord/test/cases/adapters/postgresql/range_test.rb b/activerecord/test/cases/adapters/postgresql/range_test.rb index bbf96278b0..02b1083430 100644 --- a/activerecord/test/cases/adapters/postgresql/range_test.rb +++ b/activerecord/test/cases/adapters/postgresql/range_test.rb @@ -1,12 +1,12 @@ require "cases/helper" require 'support/connection_helper' -if ActiveRecord::Base.connection.supports_ranges? +if ActiveRecord::Base.connection.respond_to?(:supports_ranges?) && ActiveRecord::Base.connection.supports_ranges? class PostgresqlRange < ActiveRecord::Base self.table_name = "postgresql_ranges" end - class PostgresqlRangeTest < ActiveRecord::TestCase + class PostgresqlRangeTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false include ConnectionHelper diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb index 7200ed2771..c895ab9db5 100644 --- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb @@ -1,7 +1,7 @@ require 'cases/helper' require 'support/connection_helper' -class PostgreSQLReferentialIntegrityTest < ActiveRecord::TestCase +class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false include ConnectionHelper diff --git a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb index f507328868..bd64bae308 100644 --- a/activerecord/test/cases/adapters/postgresql/rename_table_test.rb +++ b/activerecord/test/cases/adapters/postgresql/rename_table_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class PostgresqlRenameTableTest < ActiveRecord::TestCase +class PostgresqlRenameTableTest < ActiveRecord::PostgreSQLTestCase def setup @connection = ActiveRecord::Base.connection @connection.create_table :before_rename, force: true diff --git a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb index 359a45bbd1..fa6584eae5 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_authorization_test.rb @@ -3,7 +3,7 @@ require "cases/helper" class SchemaThing < ActiveRecord::Base end -class SchemaAuthorizationTest < ActiveRecord::TestCase +class SchemaAuthorizationTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false TABLE_NAME = 'schema_things' diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index f925dcad97..9aba2b5976 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -2,7 +2,7 @@ require "cases/helper" require 'models/default' require 'support/schema_dumping_helper' -class SchemaTest < ActiveRecord::TestCase +class SchemaTest < ActiveRecord::PostgreSQLTestCase self.use_transactional_tests = false SCHEMA_NAME = 'test_schema' @@ -441,7 +441,7 @@ class SchemaTest < ActiveRecord::TestCase end end -class SchemaForeignKeyTest < ActiveRecord::TestCase +class SchemaForeignKeyTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper setup do @@ -466,7 +466,7 @@ class SchemaForeignKeyTest < ActiveRecord::TestCase end end -class DefaultsUsingMultipleSchemasAndDomainTest < ActiveSupport::TestCase +class DefaultsUsingMultipleSchemasAndDomainTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection @connection.execute "DROP SCHEMA IF EXISTS schema_1 CASCADE" diff --git a/activerecord/test/cases/adapters/postgresql/serial_test.rb b/activerecord/test/cases/adapters/postgresql/serial_test.rb index 458a8dae6c..7d30db247b 100644 --- a/activerecord/test/cases/adapters/postgresql/serial_test.rb +++ b/activerecord/test/cases/adapters/postgresql/serial_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class PostgresqlSerialTest < ActiveRecord::TestCase +class PostgresqlSerialTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class PostgresqlSerial < ActiveRecord::Base; end @@ -30,7 +30,7 @@ class PostgresqlSerialTest < ActiveRecord::TestCase end end -class PostgresqlBigSerialTest < ActiveRecord::TestCase +class PostgresqlBigSerialTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class PostgresqlBigSerial < ActiveRecord::Base; end diff --git a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb index 1497b0abc7..5aab246c99 100644 --- a/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/postgresql/statement_pool_test.rb @@ -13,7 +13,7 @@ module ActiveRecord end end - class StatementPoolTest < ActiveRecord::TestCase + class StatementPoolTest < ActiveRecord::PostgreSQLTestCase if Process.respond_to?(:fork) def test_cache_is_per_pid cache = StatementPool.new nil, 10 diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index a639f98272..4c4866b46b 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' require 'models/developer' require 'models/topic' -class PostgresqlTimestampTest < ActiveRecord::TestCase +class PostgresqlTimestampTest < ActiveRecord::PostgreSQLTestCase class PostgresqlTimestampWithZone < ActiveRecord::Base; end self.use_transactional_tests = false @@ -43,7 +43,7 @@ class PostgresqlTimestampTest < ActiveRecord::TestCase end end -class TimestampTest < ActiveRecord::TestCase +class PostgresqlTimestampFixtureTest < ActiveRecord::PostgreSQLTestCase fixtures :topics def test_group_by_date diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb index c0907b8f21..77a99ca778 100644 --- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb +++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb @@ -1,6 +1,6 @@ require 'cases/helper' -class PostgresqlTypeLookupTest < ActiveRecord::TestCase +class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase setup do @connection = ActiveRecord::Base.connection end diff --git a/activerecord/test/cases/adapters/postgresql/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb index 3fdb6888d9..095c1826e5 100644 --- a/activerecord/test/cases/adapters/postgresql/utils_test.rb +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -1,6 +1,7 @@ require 'cases/helper' +require 'active_record/connection_adapters/postgresql/utils' -class PostgreSQLUtilsTest < ActiveSupport::TestCase +class PostgreSQLUtilsTest < ActiveRecord::PostgreSQLTestCase Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name include ActiveRecord::ConnectionAdapters::PostgreSQL::Utils @@ -20,7 +21,7 @@ class PostgreSQLUtilsTest < ActiveSupport::TestCase end end -class PostgreSQLNameTest < ActiveSupport::TestCase +class PostgreSQLNameTest < ActiveRecord::PostgreSQLTestCase Name = ActiveRecord::ConnectionAdapters::PostgreSQL::Name test "represents itself as schema.name" do diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index e9379a1019..7127d69e9e 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -11,7 +11,7 @@ module PostgresqlUUIDHelper end end -class PostgresqlUUIDTest < ActiveRecord::TestCase +class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase include PostgresqlUUIDHelper include SchemaDumpingHelper @@ -135,7 +135,7 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase end end -class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase +class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase include PostgresqlUUIDHelper include SchemaDumpingHelper @@ -210,7 +210,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::TestCase end end -class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase +class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase include PostgresqlUUIDHelper include SchemaDumpingHelper @@ -244,7 +244,7 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::TestCase end end -class PostgresqlUUIDTestInverseOf < ActiveRecord::TestCase +class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase include PostgresqlUUIDHelper class UuidPost < ActiveRecord::Base diff --git a/activerecord/test/cases/adapters/postgresql/view_test.rb b/activerecord/test/cases/adapters/postgresql/view_test.rb index 8a8e1d3b17..2dd6ec5fe6 100644 --- a/activerecord/test/cases/adapters/postgresql/view_test.rb +++ b/activerecord/test/cases/adapters/postgresql/view_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require "cases/view_test" -class UpdateableViewTest < ActiveRecord::TestCase +class UpdateableViewTest < ActiveRecord::PostgreSQLTestCase fixtures :books class PrintedBook < ActiveRecord::Base @@ -46,8 +46,9 @@ class UpdateableViewTest < ActiveRecord::TestCase end end -if ActiveRecord::Base.connection.supports_materialized_views? -class MaterializedViewTest < ActiveRecord::TestCase +if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) && + ActiveRecord::Base.connection.supports_materialized_views? +class MaterializedViewTest < ActiveRecord::PostgreSQLTestCase include ViewBehavior private diff --git a/activerecord/test/cases/adapters/postgresql/xml_test.rb b/activerecord/test/cases/adapters/postgresql/xml_test.rb index b097deb2f4..add32699fa 100644 --- a/activerecord/test/cases/adapters/postgresql/xml_test.rb +++ b/activerecord/test/cases/adapters/postgresql/xml_test.rb @@ -1,7 +1,7 @@ require 'cases/helper' require 'support/schema_dumping_helper' -class PostgresqlXMLTest < ActiveRecord::TestCase +class PostgresqlXMLTest < ActiveRecord::PostgreSQLTestCase include SchemaDumpingHelper class XmlDataType < ActiveRecord::Base self.table_name = 'xml_data_type' diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb index 39e4620bc9..58a9469ce5 100644 --- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb @@ -1,7 +1,7 @@ require "cases/helper" require 'support/schema_dumping_helper' -class SQLite3CollationTest < ActiveRecord::TestCase +class SQLite3CollationTest < ActiveRecord::SQLite3TestCase include SchemaDumpingHelper def setup diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 13b754d226..34e3b2e023 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -1,6 +1,6 @@ require "cases/helper" -class CopyTableTest < ActiveRecord::TestCase +class CopyTableTest < ActiveRecord::SQLite3TestCase fixtures :customers def setup diff --git a/activerecord/test/cases/adapters/sqlite3/explain_test.rb b/activerecord/test/cases/adapters/sqlite3/explain_test.rb index 7d66c44798..2aec322582 100644 --- a/activerecord/test/cases/adapters/sqlite3/explain_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/explain_test.rb @@ -5,7 +5,7 @@ require 'models/computer' module ActiveRecord module ConnectionAdapters class SQLite3Adapter - class ExplainTest < ActiveRecord::TestCase + class ExplainTest < ActiveRecord::SQLite3TestCase fixtures :developers def test_explain_for_one_query diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 243f65df98..87a892db37 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -6,7 +6,7 @@ require 'securerandom' module ActiveRecord module ConnectionAdapters class SQLite3Adapter - class QuotingTest < ActiveRecord::TestCase + class QuotingTest < ActiveRecord::SQLite3TestCase def setup @conn = Base.sqlite3_connection :database => ':memory:', :adapter => 'sqlite3', diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 6be9795603..7996e7ad50 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -5,7 +5,7 @@ require 'support/ddl_helper' module ActiveRecord module ConnectionAdapters - class SQLite3AdapterTest < ActiveRecord::TestCase + class SQLite3AdapterTest < ActiveRecord::SQLite3TestCase include DdlHelper self.use_transactional_tests = false diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb index deedf67c8e..887dcfc96c 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_create_folder_test.rb @@ -3,7 +3,7 @@ require 'models/owner' module ActiveRecord module ConnectionAdapters - class SQLite3CreateFolder < ActiveRecord::TestCase + class SQLite3CreateFolder < ActiveRecord::SQLite3TestCase def test_sqlite_creates_directory Dir.mktmpdir do |dir| dir = Pathname.new(dir) diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index fd0044ac05..ef324183a7 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -2,7 +2,7 @@ require 'cases/helper' module ActiveRecord::ConnectionAdapters class SQLite3Adapter - class StatementPoolTest < ActiveRecord::TestCase + class StatementPoolTest < ActiveRecord::SQLite3TestCase if Process.respond_to?(:fork) def test_cache_is_per_pid @@ -22,4 +22,3 @@ module ActiveRecord::ConnectionAdapters end end end - diff --git a/activerecord/test/cases/enum_test.rb b/activerecord/test/cases/enum_test.rb index 3641826daa..769b171717 100644 --- a/activerecord/test/cases/enum_test.rb +++ b/activerecord/test/cases/enum_test.rb @@ -14,16 +14,26 @@ class EnumTest < ActiveRecord::TestCase assert_not @book.proposed? assert @book.read? + assert @book.in_english? + assert @book.author_visibility_visible? + assert @book.illustrator_visibility_visible? + assert @book.with_medium_font_size? end test "query state with strings" do assert_equal "published", @book.status assert_equal "read", @book.read_status + assert_equal "english", @book.language + assert_equal "visible", @book.author_visibility + assert_equal "visible", @book.illustrator_visibility end test "find via scope" do assert_equal @book, Book.published.first assert_equal @book, Book.read.first + assert_equal @book, Book.in_english.first + assert_equal @book, Book.author_visibility_visible.first + assert_equal @book, Book.illustrator_visibility_visible.first end test "find via where with values" do @@ -72,6 +82,10 @@ class EnumTest < ActiveRecord::TestCase test "update by declaration" do @book.written! assert @book.written? + @book.in_english! + assert @book.in_english? + @book.author_visibility_visible! + assert @book.author_visibility_visible? end test "update by setter" do @@ -96,42 +110,61 @@ class EnumTest < ActiveRecord::TestCase test "enum changed attributes" do old_status = @book.status + old_language = @book.language @book.status = :proposed + @book.language = :spanish assert_equal old_status, @book.changed_attributes[:status] + assert_equal old_language, @book.changed_attributes[:language] end test "enum changes" do old_status = @book.status + old_language = @book.language @book.status = :proposed + @book.language = :spanish assert_equal [old_status, 'proposed'], @book.changes[:status] + assert_equal [old_language, 'spanish'], @book.changes[:language] end test "enum attribute was" do old_status = @book.status + old_language = @book.language @book.status = :published + @book.language = :spanish assert_equal old_status, @book.attribute_was(:status) + assert_equal old_language, @book.attribute_was(:language) end test "enum attribute changed" do @book.status = :proposed + @book.language = :french assert @book.attribute_changed?(:status) + assert @book.attribute_changed?(:language) end test "enum attribute changed to" do @book.status = :proposed + @book.language = :french assert @book.attribute_changed?(:status, to: 'proposed') + assert @book.attribute_changed?(:language, to: 'french') end test "enum attribute changed from" do old_status = @book.status + old_language = @book.language @book.status = :proposed + @book.language = :french assert @book.attribute_changed?(:status, from: old_status) + assert @book.attribute_changed?(:language, from: old_language) end test "enum attribute changed from old status to new status" do old_status = @book.status + old_language = @book.language @book.status = :proposed + @book.language = :french assert @book.attribute_changed?(:status, from: old_status, to: 'proposed') + assert @book.attribute_changed?(:language, from: old_language, to: 'french') end test "enum didn't change" do @@ -201,11 +234,15 @@ class EnumTest < ActiveRecord::TestCase test "building new objects with enum scopes" do assert Book.written.build.written? assert Book.read.build.read? + assert Book.in_spanish.build.in_spanish? + assert Book.illustrator_visibility_invisible.build.illustrator_visibility_invisible? end test "creating new objects with enum scopes" do assert Book.written.create.written? assert Book.read.create.read? + assert Book.in_spanish.create.in_spanish? + assert Book.illustrator_visibility_invisible.create.illustrator_visibility_invisible? end test "_before_type_cast returns the enum label (required for form fields)" do @@ -355,4 +392,17 @@ class EnumTest < ActiveRecord::TestCase book2 = klass.single.create! assert book2.single? end + + test "query state by predicate with prefix" do + assert @book.author_visibility_visible? + assert_not @book.author_visibility_invisible? + assert @book.illustrator_visibility_visible? + assert_not @book.illustrator_visibility_invisible? + end + + test "query state by predicate with custom prefix" do + assert @book.in_english? + assert_not @book.in_spanish? + assert_not @book.in_french? + end end diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 47532c84e8..03a187ae92 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -16,6 +16,7 @@ require 'models/joke' require 'models/matey' require 'models/parrot' require 'models/pirate' +require 'models/doubloon' require 'models/post' require 'models/randomly_named_c1' require 'models/reply' @@ -903,3 +904,12 @@ class FixturesWithDefaultScopeTest < ActiveRecord::TestCase assert_equal "special", bulbs(:special).name end end + +class FixturesWithAbstractBelongsTo < ActiveRecord::TestCase + fixtures :pirates, :doubloons + + test "creates fixtures with belongs_to associations defined in abstract base classes" do + assert_not_nil doubloons(:blackbeards_doubloon) + assert_equal pirates(:blackbeard), doubloons(:blackbeards_doubloon).pirate + end +end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 3844b1a92e..24d3c085a7 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -281,17 +281,42 @@ module ActiveRecord assert_equal [:remove_foreign_key, [:dogs, :people]], enable end + def test_invert_remove_foreign_key + enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people] + assert_equal [:add_foreign_key, [:dogs, :people]], enable + end + def test_invert_add_foreign_key_with_column enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id"] assert_equal [:remove_foreign_key, [:dogs, column: "owner_id"]], enable end + def test_invert_remove_foreign_key_with_column + enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, column: "owner_id"] + assert_equal [:add_foreign_key, [:dogs, :people, column: "owner_id"]], enable + end + def test_invert_add_foreign_key_with_column_and_name enable = @recorder.inverse_of :add_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"] assert_equal [:remove_foreign_key, [:dogs, name: "fk"]], enable end - def test_remove_foreign_key_is_irreversible + def test_invert_remove_foreign_key_with_column_and_name + enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"] + assert_equal [:add_foreign_key, [:dogs, :people, column: "owner_id", name: "fk"]], enable + end + + def test_invert_remove_foreign_key_with_primary_key + enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, primary_key: "person_id"] + assert_equal [:add_foreign_key, [:dogs, :people, primary_key: "person_id"]], enable + end + + def test_invert_remove_foreign_key_with_on_delete_on_update + enable = @recorder.inverse_of :remove_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade] + assert_equal [:add_foreign_key, [:dogs, :people, on_delete: :nullify, on_update: :cascade]], enable + end + + def test_invert_remove_foreign_key_is_irreversible_without_to_table assert_raises ActiveRecord::IrreversibleMigration do @recorder.inverse_of :remove_foreign_key, [:dogs, column: "owner_id"] end @@ -299,6 +324,10 @@ module ActiveRecord assert_raises ActiveRecord::IrreversibleMigration do @recorder.inverse_of :remove_foreign_key, [:dogs, name: "fk"] end + + assert_raises ActiveRecord::IrreversibleMigration do + @recorder.inverse_of :remove_foreign_key, [:dogs] + end end end end diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index e6f0fe6f75..51170c533b 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -253,6 +253,11 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{t\.integer\s+"big_int_data_points\",\s+limit: 8,\s+array: true}, output end + def test_schema_dump_allows_array_of_decimal_defaults + output = standard_dump + assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \[1.23, 3.45\],\s+array: true}, output + end + if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_includes_extensions connection = ActiveRecord::Base.connection diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 7c92453ee3..a31c3c828c 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -274,4 +274,17 @@ class SerializedAttributeTest < ActiveRecord::TestCase assert_equal({}, topic.content) end + + def test_values_cast_from_nil_are_persisted_as_nil + # This is required to fulfil the following contract, which must be universally + # true in Active Record: + # + # model.attribute = value + # assert_equal model.attribute, model.tap(&:save).reload.attribute + Topic.serialize(:content, Hash) + topic = Topic.create!(content: {}) + topic2 = Topic.create!(content: nil) + + assert_equal [topic, topic2], Topic.where(content: nil) + end end diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index a5e6738f14..7761ea5612 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -65,6 +65,30 @@ module ActiveRecord end end + class PostgreSQLTestCase < TestCase + def self.run(*args) + super if current_adapter?(:PostgreSQLAdapter) + end + end + + class Mysql2TestCase < TestCase + def self.run(*args) + super if current_adapter?(:Mysql2Adapter) + end + end + + class MysqlTestCase < TestCase + def self.run(*args) + super if current_adapter?(:MysqlAdapter) + end + end + + class SQLite3TestCase < TestCase + def self.run(*args) + super if current_adapter?(:SQLite3Adapter) + end + end + class SQLCounter class << self attr_accessor :ignored_sql, :log, :log_all diff --git a/activerecord/test/fixtures/books.yml b/activerecord/test/fixtures/books.yml index 380dd3dfca..93cfabd61c 100644 --- a/activerecord/test/fixtures/books.yml +++ b/activerecord/test/fixtures/books.yml @@ -5,6 +5,10 @@ awdr: format: "paperback" status: :published read_status: :read + language: :english + author_visibility: :visible + illustrator_visibility: :visible + font_size: :medium rfr: author_id: 1 diff --git a/activerecord/test/fixtures/doubloons.yml b/activerecord/test/fixtures/doubloons.yml new file mode 100644 index 0000000000..efd1643971 --- /dev/null +++ b/activerecord/test/fixtures/doubloons.yml @@ -0,0 +1,3 @@ +blackbeards_doubloon: + pirate: blackbeard + weight: 2 diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb index 2170018068..24bfe47bbf 100644 --- a/activerecord/test/models/book.rb +++ b/activerecord/test/models/book.rb @@ -10,6 +10,10 @@ class Book < ActiveRecord::Base enum status: [:proposed, :written, :published] enum read_status: {unread: 0, reading: 2, read: 3} enum nullable_status: [:single, :married] + enum language: [:english, :spanish, :french], enum_prefix: :in + enum author_visibility: [:visible, :invisible], enum_prefix: true + enum illustrator_visibility: [:visible, :invisible], enum_prefix: true + enum font_size: [:small, :medium, :large], enum_prefix: :with, enum_suffix: true def published! super diff --git a/activerecord/test/models/doubloon.rb b/activerecord/test/models/doubloon.rb new file mode 100644 index 0000000000..2b11d128e2 --- /dev/null +++ b/activerecord/test/models/doubloon.rb @@ -0,0 +1,12 @@ +class AbstractDoubloon < ActiveRecord::Base + # This has functionality that might be shared by multiple classes. + + self.abstract_class = true + belongs_to :pirate +end + +class Doubloon < AbstractDoubloon + # This uses an abstract class that defines attributes and associations. + + self.table_name = 'doubloons' +end diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 872fa595b4..df0362573b 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -107,5 +107,6 @@ _SQL create_table :bigint_array, force: true do |t| t.integer :big_int_data_points, limit: 8, array: true + t.decimal :decimal_array_default, array: true, default: [1.23, 3.45] end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 66f8f1611d..dffccc9326 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -100,6 +100,10 @@ ActiveRecord::Schema.define do t.column :status, :integer, default: 0 t.column :read_status, :integer, default: 0 t.column :nullable_status, :integer + t.column :language, :integer, default: 0 + t.column :author_visibility, :integer, default: 0 + t.column :illustrator_visibility, :integer, default: 0 + t.column :font_size, :integer, default: 0 end create_table :booleans, force: true do |t| @@ -266,6 +270,11 @@ ActiveRecord::Schema.define do t.string :alias end + create_table :doubloons, force: true do |t| + t.integer :pirate_id + t.integer :weight + end + create_table :edges, force: true, id: false do |t| t.column :source_id, :integer, null: false t.column :sink_id, :integer, null: false diff --git a/activesupport/bin/test b/activesupport/bin/test new file mode 100755 index 0000000000..404cabba51 --- /dev/null +++ b/activesupport/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +exit Minitest.run(ARGV) diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 49fd1723e2..be369d21c6 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -153,7 +153,7 @@ module ActiveSupport # titleize('TheManWithoutAPast') # => "The Man Without A Past" # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" def titleize(word) - humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize } + humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize } end # Creates the name of a table like Rails does for models to table names. diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb index 5aa5f46310..84c6b89340 100644 --- a/activesupport/lib/active_support/testing/autorun.rb +++ b/activesupport/lib/active_support/testing/autorun.rb @@ -2,4 +2,11 @@ gem 'minitest' require 'minitest' -Minitest.autorun +if Minitest.respond_to?(:run_with_rails_extension) + unless Minitest.run_with_rails_extension + Minitest.run_with_autorun = true + Minitest.autorun + end +else + Minitest.autorun +end diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 80b1bde1c7..ad069a112e 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -475,7 +475,8 @@ column names can not be derived from the table names, you can use the `:column` and `:primary_key` options. Rails will generate a name for every foreign key starting with -`fk_rails_` followed by 10 random characters. +`fk_rails_` followed by 10 character which is deterministically +generated from the `from_table` and `column`. There is a `:name` option to specify a different name if needed. NOTE: Active Record only supports single column foreign keys. `execute` and diff --git a/guides/source/api_app.md b/guides/source/api_app.md new file mode 100644 index 0000000000..0a6335ed88 --- /dev/null +++ b/guides/source/api_app.md @@ -0,0 +1,435 @@ +Using Rails for API-only Apps +============================= + +In this guide you will learn: + +- What Rails provides for API-only applications +- How to configure Rails to start without any browser features +- How to decide which middlewares you will want to include +- How to decide which modules to use in your controller + +endprologue. + +### What is an API app? + +Traditionally, when people said that they used Rails as an “API”, they +meant providing a programmatically accessible API alongside their web +application.\ +For example, GitHub provides [an API](http://developer.github.com) that +you can use from your own custom clients. + +With the advent of client-side frameworks, more developers are using +Rails to build a backend that is shared between their web application +and other native applications. + +For example, Twitter uses its [public API](https://dev.twitter.com) in +its web application, which is built as a static site that consumes JSON +resources. + +Instead of using Rails to generate dynamic HTML that will communicate +with the server through forms and links, many developers are treating +their web application as just another client, delivered as static HTML, +CSS and JavaScript, and consuming a simple JSON API + +This guide covers building a Rails application that serves JSON +resources to an API client **or** client-side framework. + +### Why use Rails for JSON APIs? + +The first question a lot of people have when thinking about building a +JSON API using Rails is: “isn’t using Rails to spit out some JSON +overkill? Shouldn’t I just use something like Sinatra?” + +For very simple APIs, this may be true. However, even in very HTML-heavy +applications, most of an application’s logic is actually outside of the +view layer. + +The reason most people use Rails is that it provides a set of defaults +that allows us to get up and running quickly without having to make a +lot of trivial decisions. + +Let’s take a look at some of the things that Rails provides out of the +box that are still applicable to API applications. + +Handled at the middleware layer: + +- Reloading: Rails applications support transparent reloading. This + works even if your application gets big and restarting the server + for every request becomes non-viable. +- Development Mode: Rails application come with smart defaults for + development, making development pleasant without compromising + production-time performance. +- Test Mode: Ditto test mode. +- Logging: Rails applications log every request, with a level of + verbosity appropriate for the current mode. Rails logs in + development include information about the request environment, + database queries, and basic performance information. +- Security: Rails detects and thwarts [IP spoofing + attacks](http://en.wikipedia.org/wiki/IP_address_spoofing) and + handles cryptographic signatures in a [timing + attack](http://en.wikipedia.org/wiki/Timing_attack) aware way. Don’t + know what an IP spoofing attack or a timing attack is? Exactly. +- Parameter Parsing: Want to specify your parameters as JSON instead + of as a URL-encoded String? No problem. Rails will decode the JSON + for you and make it available in *params*. Want to use nested + URL-encoded params? That works too. +- Conditional GETs: Rails handles conditional *GET*, (*ETag* and + *Last-Modified*), processing request headers and returning the + correct response headers and status code. All you need to do is use + the + [stale?](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) + check in your controller, and Rails will handle all of the HTTP + details for you. +- Caching: If you use *dirty?* with public cache control, Rails will + automatically cache your responses. You can easily configure the + cache store. +- HEAD requests: Rails will transparently convert *HEAD* requests into + *GET* requests, and return just the headers on the way out. This + makes *HEAD* work reliably in all Rails APIs. + +While you could obviously build these up in terms of existing Rack +middlewares, I think this list demonstrates that the default Rails +middleware stack provides a lot of value, even if you’re “just +generating JSON”. + +Handled at the ActionPack layer: + +- Resourceful Routing: If you’re building a RESTful JSON API, you want + to be using the Rails router. Clean and conventional mapping from + HTTP to controllers means not having to spend time thinking about + how to model your API in terms of HTTP. +- URL Generation: The flip side of routing is URL generation. A good + API based on HTTP includes URLs (see [the GitHub gist + API](http://developer.github.com/v3/gists/) for an example). +- Header and Redirection Responses: *head :no\_content* and + *redirect\_to user\_url(current\_user)* come in handy. Sure, you + could manually add the response headers, but why? +- Caching: Rails provides page, action and fragment caching. Fragment + caching is especially helpful when building up a nested JSON object. +- Basic, Digest and Token Authentication: Rails comes with + out-of-the-box support for three kinds of HTTP authentication. +- Instrumentation: Rails 3.0 added an instrumentation API that will + trigger registered handlers for a variety of events, such as action + processing, sending a file or data, redirection, and database + queries. The payload of each event comes with relevant information + (for the action processing event, the payload includes the + controller, action, params, request format, request method and the + request’s full path). +- Generators: This may be passé for advanced Rails users, but it can + be nice to generate a resource and get your model, controller, test + stubs, and routes created for you in a single command. +- Plugins: Many third-party libraries come with support for Rails that + reduces or eliminates the cost of setting up and gluing together the + library and the web framework. This includes things like overriding + default generators, adding rake tasks, and honoring Rails choices + (like the logger and cache backend). + +Of course, the Rails boot process also glues together all registered +components. For example, the Rails boot process is what uses your +*config/database.yml* file when configuring ActiveRecord. + +**The short version is**: you may not have thought about which parts of +Rails are still applicable even if you remove the view layer, but the +answer turns out to be “most of it”. + +### The Basic Configuration + +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. + +You can generate a new api Rails app: + +<shell>\ +\$ rails new my\_api --api\ +</shell> + +This will do three main things for you: + +- Configure your application to start with a more limited set of + middleware than normal. Specifically, it will not include any + middleware primarily useful for browser applications (like cookie + support) by default. +- Make *ApplicationController* inherit from *ActionController::API* + instead of *ActionController::Base*. As with middleware, this will + leave out any *ActionController* modules that provide functionality + primarily used by browser applications. +- Configure the generators to skip generating views, helpers and + assets when you generate a new resource. + +If you want to take an existing app and make it an API app, follow the +following steps. + +In *config/application.rb* add the following line at the top of the +*Application* class: + +<ruby>\ +config.api\_only!\ +</ruby> + +Change *app/controllers/application\_controller.rb*: + +<ruby> + +1. instead of\ + class ApplicationController \< ActionController::Base\ + end + +<!-- --> + +1. do\ + class ApplicationController \< ActionController::API\ + end\ + </ruby> + +### Choosing Middlewares + +An API application comes with the following middlewares by default. + +- *Rack::Cache*: Caches responses with public *Cache-Control* headers + using HTTP caching semantics. See below for more information. +- *Rack::Sendfile*: Uses a front-end server’s file serving support + from your Rails application. +- *Rack::Lock*: If your application is not marked as threadsafe + (*config.threadsafe!*), this middleware will add a mutex around your + requests. +- *ActionDispatch::RequestId*: +- *Rails::Rack::Logger*: +- *Rack::Runtime*: Adds a header to the response listing the total + runtime of the request. +- *ActionDispatch::ShowExceptions*: Rescue exceptions and re-dispatch + them to an exception handling application +- *ActionDispatch::DebugExceptions*: Log exceptions +- *ActionDispatch::RemoteIp*: Protect against IP spoofing attacks +- *ActionDispatch::Reloader*: In development mode, support code + reloading. +- *ActionDispatch::ParamsParser*: Parse XML, YAML and JSON parameters + when the request’s *Content-Type* is one of those. +- *ActionDispatch::Head*: Dispatch *HEAD* requests as *GET* requests, + and return only the status code and headers. +- *Rack::ConditionalGet*: Supports the *stale?* feature in Rails + controllers. +- *Rack::ETag*: Automatically set an *ETag* on all string responses. + This means that if the same response is returned from a controller + for the same URL, the server will return a *304 Not Modified*, even + if no additional caching steps are taken. This is primarily a + client-side optimization; it reduces bandwidth costs but not server + processing time. + +Other plugins, including *ActiveRecord*, may add additional middlewares. +In general, these middlewares are agnostic to the type of app you are +building, and make sense in an API-only Rails application. + +You can get a list of all middlewares in your application via: + +<shell>\ +\$ rake middleware\ +</shell> + +#### Using Rack::Cache + +When used with Rails, *Rack::Cache* uses the Rails cache store for its +entity and meta stores. This means that if you use memcache, for your +Rails app, for instance, the built-in HTTP cache will use memcache. + +To make use of *Rack::Cache*, you will want to use *stale?* in your +controller. Here’s an example of *stale?* in use. + +<ruby>\ +def show\ + @post = Post.find(params[:id]) + +if stale?(:last\_modified =\> `post.updated_at) + render json: `post\ + end\ +end\ +</ruby> + +The call to *stale?* will compare the *If-Modified-Since* header in the +request with *@post.updated\_at*. If the header is newer than the last +modified, this action will return a *304 Not Modified* response. +Otherwise, it will render the response and include a *Last-Modified* +header with the response. + +Normally, this mechanism is used on a per-client basis. *Rack::Cache* +allows us to share this caching mechanism across clients. We can enable +cross-client caching in the call to *stale?* + +<ruby>\ +def show\ + @post = Post.find(params[:id]) + +if stale?(:last\_modified =\> `post.updated_at, :public => true) + render json: `post\ + end\ +end\ +</ruby> + +This means that *Rack::Cache* will store off *Last-Modified* value for a +URL in the Rails cache, and add an *If-Modified-Since* header to any +subsequent inbound requests for the same URL. + +Think of it as page caching using HTTP semantics. + +NOTE: The *Rack::Cache* middleware is always outside of the *Rack::Lock* +mutex, even in single-threaded apps. + +#### Using Rack::Sendfile + +When you use the *send\_file* method in a Rails controller, it sets the +*X-Sendfile* header. *Rack::Sendfile* is responsible for actually +sending the file. + +If your front-end server supports accelerated file sending, +*Rack::Sendfile* will offload the actual file sending work to the +front-end server. + +You can configure the name of the header that your front-end server uses +for this purposes using *config.action\_dispatch.x\_sendfile\_header* in +the appropriate environment config file. + +You can learn more about how to use *Rack::Sendfile* with popular +front-ends in [the Rack::Sendfile +documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile) + +The values for popular servers once they are configured to support +accelerated file sending: + +<ruby> + +1. Apache and lighttpd\ + config.action\_dispatch.x\_sendfile\_header = “X-Sendfile” + +<!-- --> + +1. nginx\ + config.action\_dispatch.x\_sendfile\_header = “X-Accel-Redirect”\ + </ruby> + +Make sure to configure your server to support these options following +the instructions in the *Rack::Sendfile* documentation. + +NOTE: The *Rack::Sendfile* middleware is always outside of the +*Rack::Lock* mutex, even in single-threaded apps. + +#### Using ActionDispatch::ParamsParser + +*ActionDispatch::ParamsParser* will take parameters from the client in +JSON and make them available in your controller as *params*. + +To use this, your client will need to make a request with JSON-encoded +parameters and specify the *Content-Type* as *application/json*. + +Here’s an example in jQuery: + +<plain>\ +jQuery.ajax({\ + type: ‘POST’,\ + url: ‘/people’\ + dataType: ‘json’,\ + contentType: ‘application/json’,\ + data: JSON.stringify({ person: { firstName: “Yehuda”, lastName: “Katz” +} }), + +success: function(json) { }\ +});\ +</plain> + +*ActionDispatch::ParamsParser* will see the *Content-Type* and your +params will be *{ :person =\> { :firstName =\> “Yehuda”, :lastName =\> +“Katz” } }*. + +#### Other Middlewares + +Rails ships with a number of other middlewares that you might want to +use in an API app, especially if one of your API clients is the browser: + +- *Rack::MethodOverride*: Allows the use of the *\_method* hack to + route POST requests to other verbs. +- *ActionDispatch::Cookies*: Supports the *cookie* method in + *ActionController*, including support for signed and encrypted + cookies. +- *ActionDispatch::Flash*: Supports the *flash* mechanism in + *ActionController*. +- *ActionDispatch::BestStandards*: Tells Internet Explorer to use the + most standards-compliant available renderer. In production mode, if + ChromeFrame is available, use ChromeFrame. +- Session Management: If a *config.session\_store* is supplied, this + middleware makes the session available as the *session* method in + *ActionController*. + +Any of these middlewares can be adding via: + +<ruby>\ +config.middleware.use Rack::MethodOverride\ +</ruby> + +#### Removing Middlewares + +If you don’t want to use a middleware that is included by default in the +API-only middleware set, you can remove it using +*config.middleware.delete*: + +<ruby>\ +config.middleware.delete ::Rack::Sendfile\ +</ruby> + +Keep in mind that removing these features may remove support for certain +features in *ActionController*. + +### Choosing Controller Modules + +An API application (using *ActionController::API*) comes with the +following controller modules by default: + +- *ActionController::UrlFor*: Makes *url\_for* and friends available +- *ActionController::Redirecting*: Support for *redirect\_to* +- *ActionController::Rendering*: Basic support for rendering +- *ActionController::Renderers::All*: Support for *render :json* and + friends +- *ActionController::ConditionalGet*: Support for *stale?* +- *ActionController::ForceSSL*: Support for *force\_ssl* +- *ActionController::RackDelegation*: Support for the *request* and + *response* methods returning *ActionDispatch::Request* and + *ActionDispatch::Response* objects. +- *ActionController::DataStreaming*: Support for *send\_file* and + *send\_data* +- *AbstractController::Callbacks*: Support for *before\_filter* and + friends +- *ActionController::Instrumentation*: Support for the instrumentation + hooks defined by *ActionController* (see [the + source](https://github.com/rails/rails/blob/master/actionpack/lib/action_controller/metal/instrumentation.rb) + for more). +- *ActionController::Rescue*: Support for *rescue\_from*. + +Other plugins may add additional modules. You can get a list of all +modules included into *ActionController::API* in the rails console: + +<shell>\ +\$ irb\ +\>\> ActionController::API.ancestors - +ActionController::Metal.ancestors\ +</shell> + +#### Adding Other Modules + +All ActionController modules know about their dependent modules, so you +can feel free to include any modules into your controllers, and all +dependencies will be included and set up as well. + +Some common modules you might want to add: + +- *AbstractController::Translation*: Support for the *l* and *t* + localization and translation methods. These delegate to + *I18n.translate* and *I18n.localize*. +- *ActionController::HTTPAuthentication::Basic* (or *Digest* + or +Token): Support for basic, digest or token HTTP authentication. +- *AbstractController::Layouts*: Support for layouts when rendering. +- *ActionController::MimeResponds*: Support for content negotiation + (*respond\_to*, *respond\_with*). +- *ActionController::Cookies*: Support for *cookies*, which includes + support for signed and encrypted cookies. This requires the cookie + middleware. + +The best place to add a module is in your *ApplicationController*. You +can also add modules to individual controllers. diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 315d8c14b6..0f5a9e4e39 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -64,10 +64,10 @@ $ cd commandsapp $ bin/rails server => Booting WEBrick => Rails 5.0.0 application starting in development on http://localhost:3000 -=> Call with -d to detach +=> Run `rails server -h` for more startup options => Ctrl-C to shutdown server [2013-08-07 02:00:01] INFO WEBrick 1.3.1 -[2013-08-07 02:00:01] INFO ruby 2.0.0 (2013-06-27) [x86_64-darwin11.2.0] +[2013-08-07 02:00:01] INFO ruby 2.2.2 (2015-06-27) [x86_64-darwin11.2.0] [2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000 ``` diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index e3856a285a..f3d3a83afc 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -191,34 +191,6 @@ $(document).ready -> Obviously, you'll want to be a bit more sophisticated than that, but it's a start. You can see more about the events [in the jquery-ujs wiki](https://github.com/rails/jquery-ujs/wiki/ajax). -Another possibility is returning javascript directly from the server side on -remote calls: - -```ruby -# articles_controller -def create - respond_to do |format| - if @article.save - format.html { ... } - format.js do - render js: <<-endjs - alert('Article saved successfully!'); - window.location = '#{article_path(@article)}'; - endjs - end - else - format.html { ... } - format.js do - render js: "alert('There are empty fields in the form!');" - end - end - end -end -``` - -NOTE: If javascript is disabled in the user browser, `format.html { ... }` -block should be executed as fallback. - ### form_tag [`form_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html#method-i-form_tag) diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index a0fb612cba..3536929d00 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,10 @@ +* Add support for API only apps. + Middleware stack was slimmed down and it has only the needed + middleware for API apps & generators generates the right files, + folders and configurations. + + *Santiago Pastorino & Jorge Bejar* + * Make generated scaffold functional tests work inside engines. *Yuji Yaginuma* diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 78a47fcda9..4fc7a1db62 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -16,7 +16,7 @@ module Rails :beginning_of_week, :filter_redirect, :x attr_writer :log_level - attr_reader :encoding + attr_reader :encoding, :api_only def initialize(*) super @@ -49,6 +49,7 @@ module Rails @eager_load = nil @secret_token = nil @secret_key_base = nil + @api_only = false @x = Custom.new end @@ -60,6 +61,11 @@ module Rails end end + def api_only=(value) + @api_only = value + generators.api_only = value + end + def paths @paths ||= begin paths = super diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 503977b395..6f9ccec137 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -18,7 +18,7 @@ module Rails middleware.use ::Rack::Sendfile, config.action_dispatch.x_sendfile_header if config.serve_static_files - middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control, config.static_index + middleware.use ::ActionDispatch::Static, paths["public"].first, config.static_cache_control, index: config.static_index end if rack_cache = load_rack_cache @@ -28,7 +28,7 @@ module Rails middleware.use ::Rack::Lock unless allow_concurrency? middleware.use ::Rack::Runtime - middleware.use ::Rack::MethodOverride + middleware.use ::Rack::MethodOverride unless config.api_only middleware.use ::ActionDispatch::RequestId # Must come after Rack::MethodOverride to properly log overridden methods @@ -42,9 +42,9 @@ module Rails end middleware.use ::ActionDispatch::Callbacks - middleware.use ::ActionDispatch::Cookies + middleware.use ::ActionDispatch::Cookies unless config.api_only - if config.session_store + if !config.api_only && config.session_store if config.force_ssl && !config.session_options.key?(:secure) config.session_options[:secure] = true end diff --git a/railties/lib/rails/configuration.rb b/railties/lib/rails/configuration.rb index 76364cea8f..d99d27a756 100644 --- a/railties/lib/rails/configuration.rb +++ b/railties/lib/rails/configuration.rb @@ -74,7 +74,7 @@ module Rails end class Generators #:nodoc: - attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging + attr_accessor :aliases, :options, :templates, :fallbacks, :colorize_logging, :api_only attr_reader :hidden_namespaces def initialize @@ -83,6 +83,7 @@ module Rails @fallbacks = {} @templates = [] @colorize_logging = true + @api_only = false @hidden_namespaces = [] end diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index 79088bbe3c..b430cf1909 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -45,6 +45,7 @@ module Rails DEFAULT_OPTIONS = { rails: { + api: false, assets: true, force_plural: false, helper: true, @@ -64,6 +65,7 @@ module Rails } def self.configure!(config) #:nodoc: + api_only! if config.api_only no_color! unless config.colorize_logging aliases.deep_merge! config.aliases options.deep_merge! config.options @@ -101,6 +103,21 @@ module Rails @fallbacks ||= {} end + # Configure generators for API only applications. It basically hides + # everything that is usually browser related, such as assets and session + # migration generators, and completely disable views, helpers and assets + # so generators such as scaffold won't create them. + def self.api_only! + hide_namespaces "assets", "helper", "css", "js" + + options[:rails].merge!( + api: true, + assets: false, + helper: false, + template_engine: nil + ) + end + # Remove the color from output. def self.no_color! Thor::Base.shell = Thor::Shell::Basic diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index c02b39d203..249fe96772 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -266,6 +266,8 @@ module Rails end def jbuilder_gemfile_entry + return [] if options[:api] + comment = 'Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder' GemfileEntry.version('jbuilder', '~> 2.0', comment) end diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 152c26860e..4b73313388 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -175,6 +175,9 @@ module Rails class_option :version, type: :boolean, aliases: "-v", group: :rails, desc: "Show Rails version number and quit" + class_option :api, type: :boolean, + desc: "Preconfigure smaller stack for API only apps" + def initialize(*args) super @@ -185,6 +188,10 @@ module Rails if !options[:skip_active_record] && !DATABASES.include?(options[:database]) raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}." end + + # Force sprockets to be skipped when generating API only apps. + # Can't modify options hash as it's frozen by default. + self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze if options[:api] end public_task :set_default_accessors! @@ -252,6 +259,28 @@ module Rails build(:vendor) end + def delete_app_assets_if_api_option + if options[:api] + remove_dir 'app/assets' + remove_dir 'lib/assets' + remove_dir 'tmp/cache/assets' + remove_dir 'vendor/assets' + end + end + + def delete_app_helpers_if_api_option + if options[:api] + remove_dir 'app/helpers' + remove_dir 'test/helpers' + end + end + + def delete_app_views_if_api_option + if options[:api] + remove_dir 'app/views' + end + end + def delete_js_folder_skipping_javascript if options[:skip_javascript] remove_dir 'app/assets/javascripts' @@ -270,6 +299,13 @@ module Rails end end + def delete_non_api_initializers_if_api_option + if options[:api] + remove_file 'config/initializers/session_store.rb' + remove_file 'config/initializers/cookies_serializer.rb' + end + end + def finish_template build(:leftovers) end diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 29203b9c37..606f1d4f96 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -21,6 +21,14 @@ source 'https://rubygems.org' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +<%- if options.api? -%> +# Use ActiveModelSerializers to serialize JSON responses +gem 'active_model_serializers', '~> 0.10.0.rc1' + +# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible +# gem 'rack-cors' + +<%- end -%> <% if RUBY_ENGINE == 'ruby' -%> group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console diff --git a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt index d83690e1b9..f726fd6305 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/controllers/application_controller.rb.tt @@ -1,5 +1,7 @@ -class ApplicationController < ActionController::Base +class ApplicationController < ActionController::<%= options[:api] ? "API" : "Base" %> +<%- unless options[:api] -%> # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception +<%- end -%> end diff --git a/railties/lib/rails/generators/rails/app/templates/config/application.rb b/railties/lib/rails/generators/rails/app/templates/config/application.rb index a2661bfb51..6b7d7abd0b 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/application.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/application.rb @@ -32,5 +32,12 @@ module <%= app_const_base %> # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de +<%- if options[:api] -%> + + # Only loads a smaller set of middleware suitable for API only apps. + # Middleware like session, flash, cookies can be added back manually. + # Skip views, helpers and assets when generating a new resource. + config.api_only = true +<%- end -%> end end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb new file mode 100644 index 0000000000..45c44d24f8 --- /dev/null +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/cors.rb @@ -0,0 +1,14 @@ +# Avoid CORS issues when API is called from the frontend app +# Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests + +# Read more: https://github.com/cyu/rack-cors + +# Rails.application.config.middleware.insert_before 0, "Rack::Cors" do +# allow do +# origins 'example.com' +# +# resource '*', +# headers: :any, +# methods: [:get, :post, :put, :patch, :delete, :options, :head] +# end +# end diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt index 94f612c3dd..cadc85cfac 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/wrap_parameters.rb.tt @@ -5,7 +5,7 @@ # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] if respond_to?(:wrap_parameters) + wrap_parameters format: [:json] end <%- unless options.skip_active_record? -%> diff --git a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb index c986f95e67..42705107ae 100644 --- a/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb +++ b/railties/lib/rails/generators/rails/resource_route/resource_route_generator.rb @@ -1,7 +1,6 @@ module Rails module Generators class ResourceRouteGenerator < NamedBase # :nodoc: - # Properly nests namespaces passed into a generator # # $ rails generate resource admin/users/products diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index c01b82884d..d0b8cad896 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -10,11 +10,14 @@ module Rails class_option :helper, type: :boolean class_option :orm, banner: "NAME", type: :string, required: true, desc: "ORM to generate the controller for" + class_option :api, type: :boolean, + desc: "Generates API controller" argument :attributes, type: :array, default: [], banner: "field:type field:type" def create_controller_files - template "controller.rb", File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") + template_file = options.api? ? "api_controller.rb" : "controller.rb" + template template_file, File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") end hook_for :template_engine, :test_framework, as: :scaffold diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb new file mode 100644 index 0000000000..bc3c9b3f6b --- /dev/null +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/api_controller.rb @@ -0,0 +1,61 @@ +<% if namespaced? -%> +require_dependency "<%= namespaced_file_path %>/application_controller" + +<% end -%> +<% module_namespacing do -%> +class <%= controller_class_name %>Controller < ApplicationController + before_action :set_<%= singular_table_name %>, only: [:show, :update, :destroy] + + # GET <%= route_url %> + def index + @<%= plural_table_name %> = <%= orm_class.all(class_name) %> + + render json: <%= "@#{plural_table_name}" %> + end + + # GET <%= route_url %>/1 + def show + render json: <%= "@#{singular_table_name}" %> + end + + # POST <%= route_url %> + def create + @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %> + + if @<%= orm_instance.save %> + render json: <%= "@#{singular_table_name}" %>, status: :created, location: <%= "@#{singular_table_name}" %> + else + render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity + end + end + + # PATCH/PUT <%= route_url %>/1 + def update + if @<%= orm_instance.update("#{singular_table_name}_params") %> + render json: <%= "@#{singular_table_name}" %> + else + render json: <%= "@#{orm_instance.errors}" %>, status: :unprocessable_entity + end + end + + # DELETE <%= route_url %>/1 + def destroy + @<%= orm_instance.destroy %> + end + + private + # Use callbacks to share common setup or constraints between actions. + def set_<%= singular_table_name %> + @<%= singular_table_name %> = <%= orm_class.find(class_name, "params[:id]") %> + end + + # Only allow a trusted parameter "white list" through. + def <%= "#{singular_table_name}_params" %> + <%- if attributes_names.empty? -%> + params[:<%= singular_table_name %>] + <%- else -%> + params.require(:<%= singular_table_name %>).permit(<%= attributes_names.map { |name| ":#{name}" }.join(', ') %>) + <%- end -%> + end +end +<% end -%> diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb index c36a64db31..d634584beb 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb @@ -8,10 +8,14 @@ module TestUnit # :nodoc: check_class_collision suffix: "ControllerTest" + class_option :api, type: :boolean, + desc: "Generates API functional tests" + argument :attributes, type: :array, default: [], banner: "field:type field:type" def create_test_files - template "functional_test.rb", + template_file = options.api? ? "api_functional_test.rb" : "functional_test.rb" + template template_file, File.join("test/controllers", controller_class_path, "#{controller_file_name}_controller_test.rb") end diff --git a/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb new file mode 100644 index 0000000000..896b38bc8f --- /dev/null +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/api_functional_test.rb @@ -0,0 +1,40 @@ +require 'test_helper' + +<% module_namespacing do -%> +class <%= controller_class_name %>ControllerTest < ActionController::TestCase + setup do + @<%= singular_table_name %> = <%= table_name %>(:one) + end + + test "should get index" do + get :index + assert_response :success + end + + test "should create <%= singular_table_name %>" do + assert_difference('<%= class_name %>.count') do + post :create, params: { <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + end + + assert_response 201 + end + + test "should show <%= singular_table_name %>" do + get :show, params: { id: <%= "@#{singular_table_name}" %> } + assert_response :success + end + + test "should update <%= singular_table_name %>" do + patch :update, params: { id: <%= "@#{singular_table_name}" %>, <%= "#{singular_table_name}: { #{attributes_hash} }" %> } + assert_response 200 + end + + test "should destroy <%= singular_table_name %>" do + assert_difference('<%= class_name %>.count', -1) do + delete :destroy, params: { id: <%= "@#{singular_table_name}" %> } + end + + assert_response 204 + end +end +<% end -%> diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 3b444b0932..828039dc43 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -9,10 +9,7 @@ require 'action_controller/test_case' require 'action_dispatch/testing/integration' require 'rails/generators/test_case' -unless Minitest.run_with_rails_extension - Minitest.run_with_autorun = true - require 'active_support/testing/autorun' -end +require 'active_support/testing/autorun' if defined?(ActiveRecord::Base) ActiveRecord::Migration.maintain_test_schema! diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 78ada58ec8..84cc6e120b 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -125,5 +125,40 @@ module ApplicationTests assert_equal expected, c.generators.options end end + + test "api only generators hide assets, helper, js and css namespaces and set api option" do + add_to_config <<-RUBY + config.api_only = true + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert Rails::Generators.hidden_namespaces.include?("assets") + assert Rails::Generators.hidden_namespaces.include?("helper") + assert Rails::Generators.hidden_namespaces.include?("js") + assert Rails::Generators.hidden_namespaces.include?("css") + assert Rails::Generators.options[:rails][:api] + assert_equal false, Rails::Generators.options[:rails][:assets] + assert_equal false, Rails::Generators.options[:rails][:helper] + assert_nil Rails::Generators.options[:rails][:template_engine] + end + + test "api only generators allow overriding generator options" do + add_to_config <<-RUBY + config.generators.helper = true + config.api_only = true + config.generators.template_engine = :my_template + RUBY + + # Initialize the application + require "#{app_path}/config/environment" + Rails.application.load_generators + + assert Rails::Generators.options[:rails][:api] + assert Rails::Generators.options[:rails][:helper] + assert_equal :my_template, Rails::Generators.options[:rails][:template_engine] + end end end diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 97b51911d9..af98e08d0e 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -129,6 +129,35 @@ module ApplicationTests assert_equal "false", last_response.body end + test "action_controller api executes using all the middleware stack" do + add_to_config "config.api_only = true" + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::API + end + RUBY + + app_file "app/controllers/omg_controller.rb", <<-RUBY + class OmgController < ApplicationController + def show + render json: { omg: 'omg' } + end + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + get "/:controller(/:action)" + end + RUBY + + require 'rack/test' + extend Rack::Test::Methods + + get 'omg/show' + assert_equal '{"omg":"omg"}', last_response.body + end + # AD test "action_dispatch extensions are applied to ActionDispatch" do add_to_config "config.action_dispatch.tld_length = 2" diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 04bd19784a..ce92ebbf66 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -50,6 +50,33 @@ module ApplicationTests ], middleware end + test "api middleware stack" do + add_to_config "config.api_only = true" + + boot! + + assert_equal [ + "Rack::Sendfile", + "ActionDispatch::Static", + "Rack::Lock", + "ActiveSupport::Cache::Strategy::LocalCache", + "Rack::Runtime", + "ActionDispatch::RequestId", + "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods + "ActionDispatch::ShowExceptions", + "ActionDispatch::DebugExceptions", + "ActionDispatch::RemoteIp", + "ActionDispatch::Reloader", + "ActionDispatch::Callbacks", + "ActiveRecord::ConnectionAdapters::ConnectionManagement", + "ActiveRecord::QueryCache", + "ActionDispatch::ParamsParser", + "Rack::Head", + "Rack::ConditionalGet", + "Rack::ETag" + ], middleware + end + test "Rack::Cache is not included by default" do boot! diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index dd26ec867d..4c1913f0cc 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -194,6 +194,25 @@ module ApplicationTests assert_no_match(/Errors running/, output) end + def test_api_scaffold_tests_pass_by_default + add_to_config <<-RUBY + config.api_only = true + RUBY + + app_file "app/controllers/application_controller.rb", <<-RUBY + class ApplicationController < ActionController::API + end + RUBY + + output = Dir.chdir(app_path) do + `rails generate scaffold user username:string password:string; + bundle exec rake db:migrate test` + end + + assert_match(/5 runs, 7 assertions, 0 failures, 0 errors/, output) + 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" diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb new file mode 100644 index 0000000000..9978ad0da1 --- /dev/null +++ b/railties/test/generators/api_app_generator_test.rb @@ -0,0 +1,96 @@ +require 'generators/generators_test_helper' +require 'rails/generators/rails/app/app_generator' + +class ApiAppGeneratorTest < Rails::Generators::TestCase + include GeneratorsTestHelper + tests Rails::Generators::AppGenerator + + arguments [destination_root, '--api'] + + def setup + Rails.application = TestApp::Application + super + + Kernel::silence_warnings do + Thor::Base.shell.send(:attr_accessor, :always_force) + @shell = Thor::Base.shell.new + @shell.send(:always_force=, true) + end + end + + def teardown + super + Rails.application = TestApp::Application.instance + end + + def test_skeleton_is_created + run_generator + + default_files.each { |path| assert_file path } + skipped_files.each { |path| assert_no_file path } + end + + def test_api_modified_files + run_generator + + assert_file "Gemfile" do |content| + assert_no_match(/gem 'coffee-rails'/, content) + assert_no_match(/gem 'jquery-rails'/, content) + assert_no_match(/gem 'sass-rails'/, content) + assert_no_match(/gem 'jbuilder'/, content) + assert_match(/gem 'active_model_serializers'/, content) + end + + assert_file "config/application.rb" do |content| + assert_match(/config.api_only = true/, content) + end + + assert_file "config/initializers/cors.rb" + + assert_file "config/initializers/wrap_parameters.rb" + + assert_file "app/controllers/application_controller.rb", /ActionController::API/ + end + + private + + def default_files + files = %W( + .gitignore + Gemfile + Rakefile + config.ru + app/controllers + app/mailers + app/models + config/environments + config/initializers + config/locales + db + lib + lib/tasks + log + test/fixtures + test/controllers + test/integration + test/models + tmp + vendor + ) + files.concat %w(bin/bundle bin/rails bin/rake) + files + end + + def skipped_files + %w(app/assets + app/helpers + app/views + config/initializers/assets.rb + config/initializers/cookies_serializer.rb + config/initializers/session_store.rb + lib/assets + vendor/assets + test/helpers + tmp/cache/assets) + end +end diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index 7c282377d7..5dae36b65e 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -185,4 +185,51 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase assert_match(/2 runs, 2 assertions, 0 failures, 0 errors/, `bundle exec rake test 2>&1`) end end + + def test_api_only_generates_a_proper_api_controller + run_generator ["User", "--api"] + + assert_file "app/controllers/users_controller.rb" do |content| + assert_match(/class UsersController < ApplicationController/, content) + assert_no_match(/respond_to/, content) + + assert_match(/before_action :set_user, only: \[:show, :update, :destroy\]/, content) + + assert_instance_method :index, content do |m| + assert_match(/@users = User\.all/, m) + assert_match(/render json: @users/, m) + end + + assert_instance_method :show, content do |m| + assert_match(/render json: @user/, m) + end + + assert_instance_method :create, content do |m| + assert_match(/@user = User\.new\(user_params\)/, m) + assert_match(/@user\.save/, m) + assert_match(/@user\.errors/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@user\.update\(user_params\)/, m) + assert_match(/@user\.errors/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@user\.destroy/, m) + end + end + end + + def test_api_controller_tests + run_generator ["User", "name:string", "age:integer", "organization:references{polymorphic}", "--api"] + + assert_file "test/controllers/users_controller_test.rb" do |content| + assert_match(/class UsersControllerTest < ActionController::TestCase/, content) + assert_match(/test "should get index"/, content) + assert_match(/post :create, params: \{ user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) + assert_match(/patch :update, params: \{ id: @user, user: \{ age: @user\.age, name: @user\.name, organization_id: @user\.organization_id, organization_type: @user\.organization_type \} \}/, content) + assert_no_match(/assert_redirected_to/, content) + end + end end diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb index 8d7fea8741..3401b96d7d 100644 --- a/railties/test/generators/scaffold_generator_test.rb +++ b/railties/test/generators/scaffold_generator_test.rb @@ -87,6 +87,76 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase assert_file "app/assets/stylesheets/product_lines.css" end + def test_api_scaffold_on_invoke + run_generator %w(product_line title:string product:belongs_to user:references --api --no-template-engine --no-helper --no-assets) + + # Model + assert_file "app/models/product_line.rb", /class ProductLine < ActiveRecord::Base/ + assert_file "test/models/product_line_test.rb", /class ProductLineTest < ActiveSupport::TestCase/ + assert_file "test/fixtures/product_lines.yml" + assert_migration "db/migrate/create_product_lines.rb", /belongs_to :product, index: true/ + assert_migration "db/migrate/create_product_lines.rb", /references :user, index: true/ + + # Route + assert_file "config/routes.rb" do |route| + assert_match(/resources :product_lines$/, route) + end + + # Controller + assert_file "app/controllers/product_lines_controller.rb" do |content| + assert_match(/class ProductLinesController < ApplicationController/, content) + assert_no_match(/respond_to/, content) + + assert_match(/before_action :set_product_line, only: \[:show, :update, :destroy\]/, content) + + assert_instance_method :index, content do |m| + assert_match(/@product_lines = ProductLine\.all/, m) + assert_match(/render json: @product_lines/, m) + end + + assert_instance_method :show, content do |m| + assert_match(/render json: @product_line/, m) + end + + assert_instance_method :create, content do |m| + assert_match(/@product_line = ProductLine\.new\(product_line_params\)/, m) + assert_match(/@product_line\.save/, m) + assert_match(/@product_line\.errors/, m) + end + + assert_instance_method :update, content do |m| + assert_match(/@product_line\.update\(product_line_params\)/, m) + assert_match(/@product_line\.errors/, m) + end + + assert_instance_method :destroy, content do |m| + assert_match(/@product_line\.destroy/, m) + end + end + + assert_file "test/controllers/product_lines_controller_test.rb" do |test| + assert_match(/class ProductLinesControllerTest < ActionController::TestCase/, test) + assert_match(/post :create, params: \{ product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_match(/patch :update, params: \{ id: @product_line, product_line: \{ product_id: @product_line\.product_id, title: @product_line\.title, user_id: @product_line\.user_id \} \}/, test) + assert_no_match(/assert_redirected_to/, test) + end + + # Views + assert_no_file "app/views/layouts/product_lines.html.erb" + + %w(index show new edit _form).each do |view| + assert_no_file "app/views/product_lines/#{view}.html.erb" + end + + # Helpers + assert_no_file "app/helpers/product_lines_helper.rb" + + # Assets + assert_no_file "app/assets/stylesheets/scaffold.css" + assert_no_file "app/assets/javascripts/product_lines.js" + assert_no_file "app/assets/stylesheets/product_lines.css" + end + def test_functional_tests_without_attributes run_generator ["product_line"] diff --git a/tools/test.rb b/tools/test.rb new file mode 100644 index 0000000000..50cce2081a --- /dev/null +++ b/tools/test.rb @@ -0,0 +1,10 @@ +$: << File.expand_path("test", COMPONENT_ROOT) +require File.expand_path("../../load_paths", __FILE__) +require "rails/test_unit/minitest_plugin" + +module Rails + # Necessary to get rerun-snippts working. + def self.root + @root ||= Pathname.new(COMPONENT_ROOT) + end +end |