diff options
Diffstat (limited to 'railties/lib/rails')
39 files changed, 388 insertions, 212 deletions
diff --git a/railties/lib/rails/app_rails_loader.rb b/railties/lib/rails/app_loader.rb index 9a7c6c5f2d..a9fe21824e 100644 --- a/railties/lib/rails/app_rails_loader.rb +++ b/railties/lib/rails/app_loader.rb @@ -2,7 +2,7 @@ require 'pathname' require 'rails/version' module Rails - module AppRailsLoader + module AppLoader # :nodoc: extend self RUBY = Gem.ruby @@ -29,7 +29,7 @@ generate it and add it to source control: EOS - def exec_app_rails + def exec_app original_cwd = Dir.pwd loop do diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index a65f8f2ad9..8075068b3f 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -7,8 +7,7 @@ require 'active_support/message_verifier' require 'rails/engine' module Rails - # In Rails 3.0, a Rails::Application object was introduced which is nothing more than - # an Engine but with the responsibility of coordinating the whole boot process. + # An Engine with the responsibility of coordinating the whole boot process. # # == Initialization # diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index dc3ec4274b..4fc7a1db62 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -11,12 +11,12 @@ module Rails :eager_load, :exceptions_app, :file_watcher, :filter_parameters, :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, :railties_order, :relative_url_root, :secret_key_base, :secret_token, - :serve_static_files, :ssl_options, :static_cache_control, :session_options, - :time_zone, :reload_classes_only_on_change, + :serve_static_files, :ssl_options, :static_cache_control, :static_index, + :session_options, :time_zone, :reload_classes_only_on_change, :beginning_of_week, :filter_redirect, :x attr_writer :log_level - attr_reader :encoding + attr_reader :encoding, :api_only def initialize(*) super @@ -28,6 +28,7 @@ module Rails @helpers_paths = [] @serve_static_files = true @static_cache_control = nil + @static_index = "index" @force_ssl = false @ssl_options = {} @session_store = :cookie_store @@ -48,6 +49,7 @@ module Rails @eager_load = nil @secret_token = nil @secret_key_base = nil + @api_only = false @x = Custom.new end @@ -59,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 02eea82b0c..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 + 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/cli.rb b/railties/lib/rails/cli.rb index dd70c272c6..a8794bc0de 100644 --- a/railties/lib/rails/cli.rb +++ b/railties/lib/rails/cli.rb @@ -1,8 +1,8 @@ -require 'rails/app_rails_loader' +require 'rails/app_loader' # If we are inside a Rails application this method performs an exec and thus # the rest of this script is not run. -Rails::AppRailsLoader.exec_app_rails +Rails::AppLoader.exec_app require 'rails/ruby_version_check' Signal.trap("INT") { puts; exit(1) } diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index 546d3725d8..c1bd4072ac 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -78,7 +78,6 @@ module Rails def middleware middlewares = [] - middlewares << [::Rack::ContentLength] # FIXME: add Rack::Lock in the case people are using webrick. # This is to remain backwards compatible for those who are diff --git a/railties/lib/rails/commands/test.rb b/railties/lib/rails/commands/test.rb index 598e224a6f..fe5307788a 100644 --- a/railties/lib/rails/commands/test.rb +++ b/railties/lib/rails/commands/test.rb @@ -1,5 +1,5 @@ -require "rails/test_unit/runner" +require "rails/test_unit/minitest_plugin" $: << File.expand_path("../../test", APP_PATH) -Rails::TestRunner.run(ARGV) +exit Minitest.run(ARGV) 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/console/app.rb b/railties/lib/rails/console/app.rb index 2a69c26deb..ac5836a588 100644 --- a/railties/lib/rails/console/app.rb +++ b/railties/lib/rails/console/app.rb @@ -18,6 +18,11 @@ module Rails app = Rails.application session = ActionDispatch::Integration::Session.new(app) yield session if block_given? + + # This makes app.url_for and app.foo_path available in the console + session.extend(app.routes.url_helpers) + session.extend(app.routes.mounted_helpers) + session end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 9c287b3804..1dede32dd4 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -6,7 +6,7 @@ require 'pathname' module Rails # <tt>Rails::Engine</tt> allows you to wrap a specific Rails application or subset of # functionality and share it with other applications or within a larger packaged application. - # Since Rails 3.0, every <tt>Rails::Application</tt> is just an engine, which allows for simple + # Every <tt>Rails::Application</tt> is just an engine, which allows for simple # feature and application sharing. # # Any <tt>Rails::Engine</tt> is also a <tt>Rails::Railtie</tt>, so the same @@ -15,10 +15,9 @@ module Rails # # == Creating an Engine # - # In Rails versions prior to 3.0, your gems automatically behaved as engines, however, - # this coupled Rails to Rubygems. Since Rails 3.0, if you want a gem to automatically - # behave as an engine, you have to specify an +Engine+ for it somewhere inside - # your plugin's +lib+ folder (similar to how we specify a +Railtie+): + # If you want a gem to behave as an engine, you have to specify an +Engine+ + # for it somewhere inside your plugin's +lib+ folder (similar to how we + # specify a +Railtie+): # # # lib/my_engine.rb # module MyEngine @@ -69,10 +68,9 @@ module Rails # # == Paths # - # Since Rails 3.0, applications and engines have more flexible path configuration (as - # opposed to the previous hardcoded path configuration). This means that you are not - # required to place your controllers at <tt>app/controllers</tt>, but in any place - # which you find convenient. + # Applications and engines have flexible path configuration, meaning that you + # are not required to place your controllers at <tt>app/controllers</tt>, but + # in any place which you find convenient. # # For example, let's suppose you want to place your controllers in <tt>lib/controllers</tt>. # You can set that as an option: diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index a7da92168d..b430cf1909 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -33,6 +33,7 @@ module Rails scaffold_controller: '-c', stylesheets: '-y', stylesheet_engine: '-se', + scaffold_stylesheet: '-ss', template_engine: '-e', test_framework: '-t' }, @@ -44,6 +45,7 @@ module Rails DEFAULT_OPTIONS = { rails: { + api: false, assets: true, force_plural: false, helper: true, @@ -56,12 +58,14 @@ module Rails scaffold_controller: :scaffold_controller, stylesheets: true, stylesheet_engine: :css, + scaffold_stylesheet: true, test_framework: false, template_engine: :erb } } 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 @@ -99,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 119a7cb829..249fe96772 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -174,6 +174,10 @@ module Rails options[value] ? '# ' : '' end + def keeps? + !options[:skip_keeps] + end + def sqlite3? !options[:skip_active_record] && options[:database] == 'sqlite3' end @@ -204,11 +208,13 @@ module Rails if options.dev? [ GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH), + GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'), GemfileEntry.github('arel', 'rails/arel') ] elsif options.edge? [ GemfileEntry.github('rails', 'rails/rails'), + GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'), GemfileEntry.github('arel', 'rails/arel') ] else @@ -260,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 @@ -355,7 +363,7 @@ module Rails end def keep_file(destination) - create_file("#{destination}/.keep") unless options[:skip_keeps] + create_file("#{destination}/.keep") if keeps? end end end diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 01a8e2e9b4..7b527831b0 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -18,8 +18,8 @@ module Rails parse_attributes! if respond_to?(:attributes) end - # Defines the template that would be used for the migration file. - # The arguments include the source template file, the migration filename etc. + # Overrides <tt>Thor::Actions#template</tt> so it can tell if + # a template is currently being created. no_tasks do def template(source, *args, &block) inside_template do diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 1047a2c429..4b73313388 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -141,6 +141,7 @@ module Rails end def tmp + empty_directory_with_keep_file "tmp" empty_directory "tmp/cache" empty_directory "tmp/cache/assets" end @@ -174,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 @@ -184,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! @@ -251,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' @@ -269,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 c11bb58bfa..b083381255 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -21,14 +21,24 @@ source 'https://rubygems.org' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development -group :development, :test do +<%- if options.api? -%> +# Use ActiveModelSerializers to serialize JSON responses +gem 'active_model_serializers', '~> 0.10.0.rc2' + +# 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 gem 'byebug' +end +group :development do # Access an IRB console on exception pages or by using <%%= console %> in views <%- if options.dev? || options.edge? -%> - gem 'web-console', github: "rails/web-console" + gem 'web-console', github: 'rails/web-console' <%- else -%> gem 'web-console', '~> 2.0' <%- end -%> @@ -36,8 +46,8 @@ group :development, :test do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' <% end -%> -<% end -%> end +<% end -%> <% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 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/bin/setup b/railties/lib/rails/generators/rails/app/templates/bin/setup index 3a5a2cc1e3..0d41f2fe4c 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup @@ -25,5 +25,5 @@ chdir APP_ROOT do system 'ruby bin/rake log:clear tmp:clear' puts "\n== Restarting application server ==" - touch 'tmp/restart.txt' + system 'ruby bin/rake restart' 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/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index 7c6f2098b8..1b8cf8a9fa 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -15,5 +15,8 @@ <% end -%> # Ignore all logfiles and tempfiles. /log/* +/tmp/* +<% if keeps? -%> !/log/.keep -/tmp +!/tmp/.keep +<% end -%> 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/USAGE b/railties/lib/rails/generators/rails/scaffold/USAGE index 1b2a944103..d2e495758d 100644 --- a/railties/lib/rails/generators/rails/scaffold/USAGE +++ b/railties/lib/rails/generators/rails/scaffold/USAGE @@ -36,6 +36,6 @@ Description: Examples: `rails generate scaffold post` - `rails generate scaffold post title body:text published:boolean` + `rails generate scaffold post title:string body:text published:boolean` `rails generate scaffold purchase amount:decimal tracking_id:integer:uniq` `rails generate scaffold user email:uniq password:digest` diff --git a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb index e89789e72b..17c32bfdb3 100644 --- a/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold/scaffold_generator.rb @@ -10,10 +10,11 @@ module Rails class_option :stylesheet_engine, desc: "Engine for Stylesheets" class_option :assets, type: :boolean class_option :resource_route, type: :boolean + class_option :scaffold_stylesheet, type: :boolean def handle_skip @options = @options.merge(stylesheets: false) unless options[:assets] - @options = @options.merge(stylesheet_engine: false) unless options[:stylesheets] + @options = @options.merge(stylesheet_engine: false) unless options[:stylesheets] && options[:scaffold_stylesheet] end hook_for :scaffold_controller, required: true 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/controller/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb index 509bd60564..7106096b60 100644 --- a/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/controller/templates/functional_test.rb @@ -2,6 +2,12 @@ require 'test_helper' <% module_namespacing do -%> class <%= class_name %>ControllerTest < ActionController::TestCase +<% if defined?(ENGINE_ROOT) -%> + setup do + @routes = Engine.routes + end + +<% end -%> <% if actions.empty? -%> # test "the truth" do # assert true 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 2e1f55f2a6..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,13 +8,26 @@ 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 + def fixture_name + @fixture_name ||= + if defined?(ENGINE_ROOT) + "%s_%s" % [namespaced_path, table_name] + else + table_name + end + end + private def attributes_hash 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/generators/test_unit/scaffold/templates/functional_test.rb b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb index 76313575e9..d12a5ba733 100644 --- a/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb +++ b/railties/lib/rails/generators/test_unit/scaffold/templates/functional_test.rb @@ -3,7 +3,10 @@ require 'test_helper' <% module_namespacing do -%> class <%= controller_class_name %>ControllerTest < ActionController::TestCase setup do - @<%= singular_table_name %> = <%= table_name %>(:one) + @<%= singular_table_name %> = <%= fixture_name %>(:one) +<% if defined?(ENGINE_ROOT) -%> + @routes = Engine.routes +<% end -%> end test "should get index" do diff --git a/railties/lib/rails/generators/testing/assertions.rb b/railties/lib/rails/generators/testing/assertions.rb index bd069e4bd0..17af6eddfa 100644 --- a/railties/lib/rails/generators/testing/assertions.rb +++ b/railties/lib/rails/generators/testing/assertions.rb @@ -23,7 +23,7 @@ module Rails # end # end def assert_file(relative, *contents) - absolute = File.expand_path(relative, destination_root).shellescape + absolute = File.expand_path(relative, destination_root) assert File.exist?(absolute), "Expected file #{relative.inspect} to exist, but does not" read = File.read(absolute) if block_given? || !contents.empty? diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index a1c805f8aa..904b9d9ad6 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -32,35 +32,37 @@ namespace :rails do FileUtils.cp_r src_name, dst_name end end - end + end end namespace :update do - def invoke_from_app_generator(method) - app_generator.send(method) - end + class RailsUpdate + def self.invoke_from_app_generator(method) + app_generator.send(method) + end - def app_generator - @app_generator ||= begin - require 'rails/generators' - require 'rails/generators/rails/app/app_generator' - gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, - destination_root: Rails.root - File.exist?(Rails.root.join("config", "application.rb")) ? - gen.send(:app_const) : gen.send(:valid_const?) - gen + def self.app_generator + @app_generator ||= begin + require 'rails/generators' + require 'rails/generators/rails/app/app_generator' + gen = Rails::Generators::AppGenerator.new ["rails"], { with_dispatchers: true }, + destination_root: Rails.root + File.exist?(Rails.root.join("config", "application.rb")) ? + gen.send(:app_const) : gen.send(:valid_const?) + gen + end end end # desc "Update config/boot.rb from your current rails install" task :configs do - invoke_from_app_generator :create_boot_file - invoke_from_app_generator :update_config_files + RailsUpdate.invoke_from_app_generator :create_boot_file + RailsUpdate.invoke_from_app_generator :update_config_files end # desc "Adds new executables to the application bin/ directory" task :bin do - invoke_from_app_generator :create_bin_files + RailsUpdate.invoke_from_app_generator :create_bin_files end end end diff --git a/railties/lib/rails/tasks/restart.rake b/railties/lib/rails/tasks/restart.rake index 1e8940b675..f36c86d81b 100644 --- a/railties/lib/rails/tasks/restart.rake +++ b/railties/lib/rails/tasks/restart.rake @@ -1,4 +1,5 @@ desc "Restart app by touching tmp/restart.txt" task :restart do + FileUtils.mkdir_p('tmp') FileUtils.touch('tmp/restart.txt') end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index a83e39faee..828039dc43 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -3,13 +3,14 @@ abort("Abort testing: Your Rails environment is running in production mode!") if Rails.env.production? require "rails/test_unit/minitest_plugin" -require 'active_support/testing/autorun' require 'active_support/test_case' require 'action_controller' require 'action_controller/test_case' require 'action_dispatch/testing/integration' require 'rails/generators/test_case' +require 'active_support/testing/autorun' + if defined?(ActiveRecord::Base) ActiveRecord::Migration.maintain_test_schema! diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 70ce9d3360..421f032d81 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -1,14 +1,51 @@ -require "minitest" +require "active_support/core_ext/module/attribute_accessors" require "rails/test_unit/reporter" +require "rails/test_unit/test_requirer" -def Minitest.plugin_rails_init(options) - self.reporter << Rails::TestUnitReporter.new(options[:io], options) - if $rails_test_runner && (method = $rails_test_runner.find_method) - options[:filter] = method +module Minitest + def self.plugin_rails_options(opts, options) + opts.separator "" + opts.separator "Usage: bin/rails test [options] [files or directories]" + opts.separator "You can run a single test by appending a line number to a filename:" + opts.separator "" + opts.separator " bin/rails test test/models/user_test.rb:27" + opts.separator "" + opts.separator "You can run multiple files and directories at the same time:" + opts.separator "" + opts.separator " bin/rails test test/controllers test/integration/login_test.rb" + opts.separator "" + + opts.separator "Rails options:" + opts.on("-e", "--environment [ENV]", + "Run tests in the ENV environment") do |env| + options[:environment] = env.strip + end + + opts.on("-b", "--backtrace", + "Show the complete backtrace") do + options[:full_backtrace] = true + end + + options[:patterns] = opts.order! end - if !($rails_test_runner && $rails_test_runner.show_backtrace?) - Minitest.backtrace_filter = Rails.backtrace_cleaner + def self.plugin_rails_init(options) + self.run_with_rails_extension = true + + ENV["RAILS_ENV"] = options[:environment] || "test" + + Rails::TestRequirer.require_files options[:patterns] unless run_with_autorun + + unless options[:full_backtrace] || ENV["BACKTRACE"] + # Plugin can run without Rails loaded, check before filtering. + Minitest.backtrace_filter = Rails.backtrace_cleaner if Rails.respond_to?(:backtrace_cleaner) + end + + self.reporter << Rails::TestUnitReporter.new(options[:io], options) end + + mattr_accessor(:run_with_autorun) { false } + mattr_accessor(:run_with_rails_extension) { false } end + Minitest.extensions << 'rails' diff --git a/railties/lib/rails/test_unit/reporter.rb b/railties/lib/rails/test_unit/reporter.rb index 64e99626eb..faf551f381 100644 --- a/railties/lib/rails/test_unit/reporter.rb +++ b/railties/lib/rails/test_unit/reporter.rb @@ -1,7 +1,11 @@ +require "active_support/core_ext/class/attribute" require "minitest" module Rails class TestUnitReporter < Minitest::StatisticsReporter + class_attribute :executable + self.executable = "bin/rails test" + def report return if results.empty? io.puts @@ -15,8 +19,12 @@ module Rails filtered_results.reject!(&:skipped?) unless options[:verbose] filtered_results.map do |result| location, line = result.method(result.name).source_location - "bin/rails test #{location}:#{line}" + "#{self.executable} #{relative_path_for(location)}:#{line}" end.join "\n" end + + def relative_path_for(file) + file.sub(/^#{Rails.root}\/?/, '') + end end end diff --git a/railties/lib/rails/test_unit/runner.rb b/railties/lib/rails/test_unit/runner.rb deleted file mode 100644 index 5573fa6904..0000000000 --- a/railties/lib/rails/test_unit/runner.rb +++ /dev/null @@ -1,137 +0,0 @@ -require "optparse" -require "rake/file_list" -require "method_source" - -module Rails - class TestRunner - class Options - def self.parse(args) - options = { backtrace: !ENV["BACKTRACE"].nil?, name: nil, environment: "test" } - - opt_parser = ::OptionParser.new do |opts| - opts.banner = "Usage: bin/rails test [options] [file or directory]" - - opts.separator "" - opts.on("-e", "--environment [ENV]", - "Run tests in the ENV environment") do |env| - options[:environment] = env.strip - end - opts.separator "" - opts.separator "Filter options:" - opts.separator "" - opts.separator <<-DESC - You can run a single test by appending the line number to filename: - - bin/rails test test/models/user_test.rb:27 - - DESC - - opts.on("-n", "--name [NAME]", - "Only run tests matching NAME") do |name| - options[:name] = name - end - opts.on("-p", "--pattern [PATTERN]", - "Only run tests matching PATTERN") do |pattern| - options[:name] = "/#{pattern}/" - end - - opts.separator "" - opts.separator "Output options:" - - opts.on("-b", "--backtrace", - "Show the complete backtrace") do - options[:backtrace] = true - end - - opts.separator "" - opts.separator "Common options:" - - opts.on_tail("-h", "--help", "Show this message") do - puts opts - exit - end - end - - opt_parser.order!(args) - - options[:patterns] = [] - while arg = args.shift - if (file_and_line = arg.split(':')).size > 1 - options[:filename], options[:line] = file_and_line - options[:filename] = File.expand_path options[:filename] - options[:line] &&= options[:line].to_i - else - arg = arg.gsub(':', '') - if Dir.exist?("#{arg}") - options[:patterns] << File.expand_path("#{arg}/**/*_test.rb") - elsif File.file?(arg) - options[:patterns] << File.expand_path(arg) - end - end - end - options - end - end - - def initialize(options = {}) - @options = options - end - - def self.run(arguments) - options = Rails::TestRunner::Options.parse(arguments) - Rails::TestRunner.new(options).run - end - - def run - $rails_test_runner = self - ENV["RAILS_ENV"] = @options[:environment] - run_tests - end - - def find_method - return @options[:name] if @options[:name] - return unless @options[:line] - method = test_methods.find do |location, test_method, start_line, end_line| - location == @options[:filename] && - (start_line..end_line).include?(@options[:line].to_i) - end - method[1] if method - end - - def show_backtrace? - @options[:backtrace] - end - - def test_files - return [@options[:filename]] if @options[:filename] - if @options[:patterns] && @options[:patterns].count > 0 - pattern = @options[:patterns] - else - pattern = "test/**/*_test.rb" - end - Rake::FileList[pattern] - end - - private - def run_tests - test_files.to_a.each do |file| - require File.expand_path file - end - end - - def test_methods - methods_map = [] - suites = Minitest::Runnable.runnables.shuffle - suites.each do |suite_class| - suite_class.runnable_methods.each do |test_method| - method = suite_class.instance_method(test_method) - location = method.source_location - start_line = location.last - end_line = method.source.split("\n").size + start_line - 1 - methods_map << [File.expand_path(location.first), test_method, start_line, end_line] - end - end - methods_map - end - end -end diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb new file mode 100644 index 0000000000..84c2256729 --- /dev/null +++ b/railties/lib/rails/test_unit/test_requirer.rb @@ -0,0 +1,28 @@ +require 'active_support/core_ext/object/blank' +require 'rake/file_list' + +module Rails + class TestRequirer # :nodoc: + class << self + def require_files(patterns) + patterns = expand_patterns(patterns) + + Rake::FileList[patterns.compact.presence || 'test/**/*_test.rb'].to_a.each do |file| + require File.expand_path(file) + end + end + + private + def expand_patterns(patterns) + patterns.map do |arg| + arg = arg.gsub(/:(\d+)?$/, '') + if Dir.exist?(arg) + "#{arg}/**/*_test.rb" + elsif File.file?(arg) + arg + end + end + end + end + end +end diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake index 0f26621b59..dda492f974 100644 --- a/railties/lib/rails/test_unit/testing.rake +++ b/railties/lib/rails/test_unit/testing.rake @@ -1,12 +1,13 @@ -require "rails/test_unit/runner" +gem 'minitest' +require 'minitest' +require 'rails/test_unit/minitest_plugin' task default: :test desc "Runs all tests in test folder" task :test do $: << "test" - args = ARGV[0] == "test" ? ARGV[1..-1] : [] - Rails::TestRunner.run(args) + Minitest.run(['test']) end namespace :test do @@ -23,22 +24,22 @@ namespace :test do ["models", "helpers", "controllers", "mailers", "integration", "jobs"].each do |name| task name => "test:prepare" do $: << "test" - Rails::TestRunner.run(["test/#{name}"]) + Minitest.run(["test/#{name}"]) end end task :generators => "test:prepare" do $: << "test" - Rails::TestRunner.run(["test/lib/generators"]) + Minitest.run(["test/lib/generators"]) end task :units => "test:prepare" do $: << "test" - Rails::TestRunner.run(["test/models", "test/helpers", "test/unit"]) + Minitest.run(["test/models", "test/helpers", "test/unit"]) end task :functionals => "test:prepare" do $: << "test" - Rails::TestRunner.run(["test/controllers", "test/mailers", "test/functional"]) + Minitest.run(["test/controllers", "test/mailers", "test/functional"]) end end |